From 51a5e305637aae12e27bfe0fea34181f255d0c23 Mon Sep 17 00:00:00 2001 From: zysim <9867871+zysim@users.noreply.github.com> Date: Mon, 21 Aug 2023 06:12:09 +0000 Subject: [PATCH] Add Account Recovery Model (#173) * Add model * Remove erroneous fields and field inits --- ...30806090228_AddAccountRecovery.Designer.cs | 264 ++++++++++++++++++ .../20230806090228_AddAccountRecovery.cs | 49 ++++ .../ApplicationContextModelSnapshot.cs | 49 ++++ .../Models/Entities/AccountRecovery.cs | 49 ++++ .../Models/Entities/ApplicationContext.cs | 1 + 5 files changed, 412 insertions(+) create mode 100644 LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.Designer.cs create mode 100644 LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.cs create mode 100644 LeaderboardBackend/Models/Entities/AccountRecovery.cs diff --git a/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.Designer.cs b/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.Designer.cs new file mode 100644 index 00000000..647add74 --- /dev/null +++ b/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.Designer.cs @@ -0,0 +1,264 @@ +// +using System; +using LeaderboardBackend.Models.Entities; +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 LeaderboardBackend.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20230806090228_AddAccountRecovery")] + partial class AddAccountRecovery + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "user_role", new[] { "registered", "confirmed", "administrator", "banned" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.AccountRecovery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("UsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("used_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_account_recoveries"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_account_recoveries_user_id"); + + b.ToTable("account_recoveries", (string)null); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LeaderboardId") + .HasColumnType("bigint") + .HasColumnName("leaderboard_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PlayersMax") + .HasColumnType("integer") + .HasColumnName("players_max"); + + b.Property("PlayersMin") + .HasColumnType("integer") + .HasColumnName("players_min"); + + b.Property("Rules") + .HasColumnType("text") + .HasColumnName("rules"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_categories"); + + b.HasIndex("LeaderboardId") + .HasDatabaseName("ix_categories_leaderboard_id"); + + b.ToTable("categories", (string)null); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Leaderboard", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Rules") + .HasColumnType("text") + .HasColumnName("rules"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_leaderboards"); + + b.ToTable("leaderboards", (string)null); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Run", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("bigint") + .HasColumnName("category_id"); + + b.Property("PlayedOn") + .HasColumnType("date") + .HasColumnName("played_on"); + + b.Property("SubmittedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("submitted_at"); + + b.HasKey("Id") + .HasName("pk_runs"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_runs_category_id"); + + b.ToTable("runs", (string)null); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasColumnName("email") + .UseCollation("case_insensitive"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Role") + .HasColumnType("user_role") + .HasColumnName("role"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username") + .UseCollation("case_insensitive"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("ix_users_email"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + Id = new Guid("421bb896-1990-48c6-8b0c-d69f56d6746a"), + Email = "omega@star.com", + Password = "$2a$11$tNvA94WqpJ.O7S7D6lVMn.E/UxcFYztl3BkcnBj/hgE8PY/8nCRQe", + Role = UserRole.Administrator, + Username = "Galactus" + }); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.AccountRecovery", b => + { + b.HasOne("LeaderboardBackend.Models.Entities.User", "User") + .WithMany("AccountRecoveries") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_recoveries_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Category", b => + { + b.HasOne("LeaderboardBackend.Models.Entities.Leaderboard", "Leaderboard") + .WithMany("Categories") + .HasForeignKey("LeaderboardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_categories_leaderboards_leaderboard_id"); + + b.Navigation("Leaderboard"); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Run", b => + { + b.HasOne("LeaderboardBackend.Models.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_runs_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Leaderboard", b => + { + b.Navigation("Categories"); + }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.User", b => + { + b.Navigation("AccountRecoveries"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.cs b/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.cs new file mode 100644 index 00000000..7ccca1b1 --- /dev/null +++ b/LeaderboardBackend/Migrations/20230806090228_AddAccountRecovery.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace LeaderboardBackend.Migrations +{ + /// + public partial class AddAccountRecovery : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "account_recoveries", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + used_at = table.Column(type: "timestamp with time zone", nullable: true), + expires_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_account_recoveries", x => x.id); + table.ForeignKey( + name: "fk_account_recoveries_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_account_recoveries_user_id", + table: "account_recoveries", + column: "user_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "account_recoveries"); + } + } +} diff --git a/LeaderboardBackend/Migrations/ApplicationContextModelSnapshot.cs b/LeaderboardBackend/Migrations/ApplicationContextModelSnapshot.cs index dd6aa8a1..cb09c1f9 100644 --- a/LeaderboardBackend/Migrations/ApplicationContextModelSnapshot.cs +++ b/LeaderboardBackend/Migrations/ApplicationContextModelSnapshot.cs @@ -25,6 +25,38 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "user_role", new[] { "registered", "confirmed", "administrator", "banned" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("LeaderboardBackend.Models.Entities.AccountRecovery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("UsedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("used_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_account_recoveries"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_account_recoveries_user_id"); + + b.ToTable("account_recoveries", (string)null); + }); + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Category", b => { b.Property("Id") @@ -178,6 +210,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("LeaderboardBackend.Models.Entities.AccountRecovery", b => + { + b.HasOne("LeaderboardBackend.Models.Entities.User", "User") + .WithMany("AccountRecoveries") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_account_recoveries_users_user_id"); + + b.Navigation("User"); + }); + modelBuilder.Entity("LeaderboardBackend.Models.Entities.Category", b => { b.HasOne("LeaderboardBackend.Models.Entities.Leaderboard", "Leaderboard") @@ -206,6 +250,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Categories"); }); + + modelBuilder.Entity("LeaderboardBackend.Models.Entities.User", b => + { + b.Navigation("AccountRecoveries"); + }); #pragma warning restore 612, 618 } } diff --git a/LeaderboardBackend/Models/Entities/AccountRecovery.cs b/LeaderboardBackend/Models/Entities/AccountRecovery.cs new file mode 100644 index 00000000..f4166ecd --- /dev/null +++ b/LeaderboardBackend/Models/Entities/AccountRecovery.cs @@ -0,0 +1,49 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using NodaTime; + +namespace LeaderboardBackend.Models.Entities; + +/// +/// Represents an account recovery attempt for a `User`. +/// +public class AccountRecovery +{ + /// + /// The unique identifier of the `AccountRecovery`.
+ /// Generated on creation. + ///
+ public Guid Id { get; set; } + + /// + /// The ID of the `User` tied to this `AccountRecovery`. + /// + [Required] + public Guid UserId { get; set; } + + /// + /// The `User` relationship model. + /// + [Required] + public User User { get; set; } = null!; + + /// + /// The time this `AccountRecovery` was created, i.e. the time the user + /// requested an account recovery. + /// + [Required] + public Instant CreatedAt { get; set; } + + /// + /// The time this `AccountRecovery` was used. + /// + public Instant? UsedAt { get; set; } + + /// + /// The time this `AccountRecovery` expires. Defaults to an hour from its + /// creation. + /// + /// john.doe@example.com + [Required] + public Instant ExpiresAt { get; set; } +} diff --git a/LeaderboardBackend/Models/Entities/ApplicationContext.cs b/LeaderboardBackend/Models/Entities/ApplicationContext.cs index 071384dc..00e12842 100644 --- a/LeaderboardBackend/Models/Entities/ApplicationContext.cs +++ b/LeaderboardBackend/Models/Entities/ApplicationContext.cs @@ -18,6 +18,7 @@ static ApplicationContext() public ApplicationContext(DbContextOptions options) : base(options) { } + public DbSet AccountRecoveries { get; set; } = null!; public DbSet Categories { get; set; } = null!; public DbSet Leaderboards { get; set; } = null!; public DbSet Runs { get; set; } = null!;