From 611980d66081ac26cea79fb6a3759ec12967d8e4 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Tue, 16 Jan 2024 22:27:02 +0100 Subject: [PATCH] Added new Domain data for User profile customization --- Wave/Data/ApplicationDbContext.cs | 4 + Wave/Data/ApplicationUser.cs | 12 +- .../20240116201504_UserProfiles.Designer.cs | 403 ++++++++++++++++++ .../postgres/20240116201504_UserProfiles.cs | 60 +++ .../ApplicationDbContextModelSnapshot.cs | 18 + Wave/Data/UserClaimsFactory.cs | 15 + 6 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 Wave/Data/Migrations/postgres/20240116201504_UserProfiles.Designer.cs create mode 100644 Wave/Data/Migrations/postgres/20240116201504_UserProfiles.cs create mode 100644 Wave/Data/UserClaimsFactory.cs diff --git a/Wave/Data/ApplicationDbContext.cs b/Wave/Data/ApplicationDbContext.cs index d671b8a..958de41 100644 --- a/Wave/Data/ApplicationDbContext.cs +++ b/Wave/Data/ApplicationDbContext.cs @@ -10,6 +10,10 @@ public class ApplicationDbContext(DbContextOptions options base.OnModelCreating(builder); builder.Entity(user => { + user.Property(u => u.FullName).HasMaxLength(64); + user.Property(u => u.AboutTheAuthor).HasMaxLength(512); + user.Property(u => u.Biography).HasMaxLength(4096); + user.HasOne(u => u.ProfilePicture).WithOne().HasForeignKey(typeof(ProfilePicture)) .OnDelete(DeleteBehavior.SetNull); diff --git a/Wave/Data/ApplicationUser.cs b/Wave/Data/ApplicationUser.cs index 3c950fd..62e1b27 100644 --- a/Wave/Data/ApplicationUser.cs +++ b/Wave/Data/ApplicationUser.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Identity; namespace Wave.Data; @@ -5,5 +6,14 @@ namespace Wave.Data; public class ApplicationUser : IdentityUser { public ProfilePicture? ProfilePicture { get; set; } - public string Name => UserName ?? Email ?? Id; + [MaxLength(64), PersonalData] + public string? FullName { get; set; } + + [MaxLength(512), PersonalData] + public string AboutTheAuthor { get; set; } = string.Empty; + [MaxLength(4096), PersonalData] + public string Biography { get; set; } = string.Empty; + public string BiographyHtml { get; set; } = string.Empty; + + public string Name => FullName ?? UserName ?? "Anon"; } \ No newline at end of file diff --git a/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.Designer.cs b/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.Designer.cs new file mode 100644 index 0000000..a77db53 --- /dev/null +++ b/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.Designer.cs @@ -0,0 +1,403 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Wave.Data; + +#nullable disable + +namespace Wave.Data.Migrations.postgres +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240116201504_UserProfiles")] + partial class UserProfiles + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Wave.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AboutTheAuthor") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Biography") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("BiographyHtml") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Wave.Data.Article", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyHtml") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModified") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("PublishDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewerId") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ReviewerId"); + + b.ToTable("Article"); + }); + + modelBuilder.Entity("Wave.Data.ProfilePicture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApplicationUserId") + .HasColumnType("text"); + + b.Property("ImageId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ProfilePictures", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Wave.Data.Article", b => + { + b.HasOne("Wave.Data.ApplicationUser", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Wave.Data.ApplicationUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Author"); + + b.Navigation("Reviewer"); + }); + + modelBuilder.Entity("Wave.Data.ProfilePicture", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithOne("ProfilePicture") + .HasForeignKey("Wave.Data.ProfilePicture", "ApplicationUserId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Wave.Data.ApplicationUser", b => + { + b.Navigation("ProfilePicture"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.cs b/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.cs new file mode 100644 index 0000000..33626ca --- /dev/null +++ b/Wave/Data/Migrations/postgres/20240116201504_UserProfiles.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Wave.Data.Migrations.postgres; + +/// +public partial class UserProfiles : Migration { + /// + protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.AddColumn( + name: "AboutTheAuthor", + table: "AspNetUsers", + type: "character varying(512)", + maxLength: 512, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Biography", + table: "AspNetUsers", + type: "character varying(4096)", + maxLength: 4096, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "BiographyHtml", + table: "AspNetUsers", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "FullName", + table: "AspNetUsers", + type: "character varying(64)", + maxLength: 64, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropColumn( + name: "AboutTheAuthor", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "Biography", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "BiographyHtml", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "FullName", + table: "AspNetUsers"); + } +} \ No newline at end of file diff --git a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs index d6bdbb5..e137dc3 100644 --- a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs +++ b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs @@ -159,9 +159,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("text"); + b.Property("AboutTheAuthor") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + b.Property("AccessFailedCount") .HasColumnType("integer"); + b.Property("Biography") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("BiographyHtml") + .IsRequired() + .HasColumnType("text"); + b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnType("text"); @@ -173,6 +187,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EmailConfirmed") .HasColumnType("boolean"); + b.Property("FullName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + b.Property("LockoutEnabled") .HasColumnType("boolean"); diff --git a/Wave/Data/UserClaimsFactory.cs b/Wave/Data/UserClaimsFactory.cs new file mode 100644 index 0000000..66d5931 --- /dev/null +++ b/Wave/Data/UserClaimsFactory.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; + +namespace Wave.Data; + +public class UserClaimsFactory(UserManager userManager, IOptions optionsAccessor) + : UserClaimsPrincipalFactory(userManager, optionsAccessor) { + protected override async Task GenerateClaimsAsync(ApplicationUser user) { + var principal = await base.GenerateClaimsAsync(user); + principal.AddClaim(new Claim("Id", user.Id)); + principal.AddClaim(new Claim("FullName", user.Name)); + return principal; + } +} \ No newline at end of file