Create default user profile on registration

Implement a `UserWithProfileManager` to automatically create a default user profile upon user registration.
This ensures that each user has an associated profile with default values (e.g., `UserName`, `IsAdmin`, `IsDeleted`).
Updates the database schema to reflect the change from UserId to UserProfileId in the UserProfileCompanyTableStat and UserProfileProductTableStat entities.
This commit is contained in:
martinshoob 2025-08-09 18:54:27 +02:00
parent e7ce2c7718
commit e06700458f
5 changed files with 69 additions and 23 deletions

View file

@ -1,8 +1,52 @@
using DrinkRateAPI.Contexts;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace DrinkRateAPI.DbEntities; namespace DrinkRateAPI.DbEntities;
public class DbApplicationUser : IdentityUser<Guid> public class DbApplicationUser : IdentityUser<Guid>
{ {
public virtual DbUserProfile UserProfile { get; set; } public virtual DbUserProfile UserProfile { get; set; }
}
public class UserWithProfileManager : UserManager<DbApplicationUser>
{
private readonly ApplicationDbContext _context;
public UserWithProfileManager(
IUserStore<DbApplicationUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<DbApplicationUser> passwordHasher,
IEnumerable<IUserValidator<DbApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<DbApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<DbApplicationUser>> logger,
ApplicationDbContext context)
: base(store, optionsAccessor, passwordHasher, userValidators,
passwordValidators, keyNormalizer, errors, services, logger)
{
_context = context;
}
public override async Task<IdentityResult> CreateAsync(DbApplicationUser user, string password)
{
var result = await base.CreateAsync(user, password);
if (!result.Succeeded)
return result;
var newProfile = new DbUserProfile
{
ApplicationUser = user,
UserName = $"User_{user.Id}",
IsAdmin = false,
IsDeleted = false
};
await _context.UserProfiles.AddAsync(newProfile);
await _context.SaveChangesAsync();
return result;
}
} }

View file

@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DrinkRateAPI.Migrations namespace DrinkRateAPI.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("20250809154816_ChangeApplicationUserIdType")] [Migration("20250809165046_UserCreationDemo")]
partial class ChangeApplicationUserIdType partial class UserCreationDemo
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -347,7 +347,7 @@ namespace DrinkRateAPI.Migrations
modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileCompanyTableStat", b => modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileCompanyTableStat", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserProfileId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("CompanyTableId") b.Property<Guid>("CompanyTableId")
@ -362,7 +362,7 @@ namespace DrinkRateAPI.Migrations
b.Property<int>("RatingCount") b.Property<int>("RatingCount")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("UserId", "CompanyTableId"); b.HasKey("UserProfileId", "CompanyTableId");
b.HasIndex("CompanyTableId"); b.HasIndex("CompanyTableId");
@ -371,7 +371,7 @@ namespace DrinkRateAPI.Migrations
modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileProductTableStat", b => modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileProductTableStat", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserProfileId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("ProductTableId") b.Property<Guid>("ProductTableId")
@ -386,7 +386,7 @@ namespace DrinkRateAPI.Migrations
b.Property<int>("RatingCount") b.Property<int>("RatingCount")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("UserId", "ProductTableId"); b.HasKey("UserProfileId", "ProductTableId");
b.HasIndex("ProductTableId"); b.HasIndex("ProductTableId");
@ -672,7 +672,7 @@ namespace DrinkRateAPI.Migrations
b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile") b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile")
.WithMany("UserProfileCompanyTableStats") .WithMany("UserProfileCompanyTableStats")
.HasForeignKey("UserId") .HasForeignKey("UserProfileId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
@ -691,7 +691,7 @@ namespace DrinkRateAPI.Migrations
b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile") b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile")
.WithMany("UserProfileProductTableStats") .WithMany("UserProfileProductTableStats")
.HasForeignKey("UserId") .HasForeignKey("UserProfileId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();

View file

@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DrinkRateAPI.Migrations namespace DrinkRateAPI.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class ChangeApplicationUserIdType : Migration public partial class UserCreationDemo : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@ -345,7 +345,7 @@ namespace DrinkRateAPI.Migrations
name: "UserProfileCompanyTableStats", name: "UserProfileCompanyTableStats",
columns: table => new columns: table => new
{ {
UserId = table.Column<Guid>(type: "uuid", nullable: false), UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
CompanyTableId = table.Column<Guid>(type: "uuid", nullable: false), CompanyTableId = table.Column<Guid>(type: "uuid", nullable: false),
RatingCount = table.Column<int>(type: "integer", nullable: false), RatingCount = table.Column<int>(type: "integer", nullable: false),
HighestRatingCount = table.Column<int>(type: "integer", nullable: false), HighestRatingCount = table.Column<int>(type: "integer", nullable: false),
@ -353,7 +353,7 @@ namespace DrinkRateAPI.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_UserProfileCompanyTableStats", x => new { x.UserId, x.CompanyTableId }); table.PrimaryKey("PK_UserProfileCompanyTableStats", x => new { x.UserProfileId, x.CompanyTableId });
table.ForeignKey( table.ForeignKey(
name: "FK_UserProfileCompanyTableStats_CompanyTables_CompanyTableId", name: "FK_UserProfileCompanyTableStats_CompanyTables_CompanyTableId",
column: x => x.CompanyTableId, column: x => x.CompanyTableId,
@ -361,8 +361,8 @@ namespace DrinkRateAPI.Migrations
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_UserProfileCompanyTableStats_UserProfiles_UserId", name: "FK_UserProfileCompanyTableStats_UserProfiles_UserProfileId",
column: x => x.UserId, column: x => x.UserProfileId,
principalTable: "UserProfiles", principalTable: "UserProfiles",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
@ -372,7 +372,7 @@ namespace DrinkRateAPI.Migrations
name: "UserProfileProductTableStats", name: "UserProfileProductTableStats",
columns: table => new columns: table => new
{ {
UserId = table.Column<Guid>(type: "uuid", nullable: false), UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
ProductTableId = table.Column<Guid>(type: "uuid", nullable: false), ProductTableId = table.Column<Guid>(type: "uuid", nullable: false),
RatingCount = table.Column<int>(type: "integer", nullable: false), RatingCount = table.Column<int>(type: "integer", nullable: false),
HighestRatingCount = table.Column<int>(type: "integer", nullable: false), HighestRatingCount = table.Column<int>(type: "integer", nullable: false),
@ -380,7 +380,7 @@ namespace DrinkRateAPI.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_UserProfileProductTableStats", x => new { x.UserId, x.ProductTableId }); table.PrimaryKey("PK_UserProfileProductTableStats", x => new { x.UserProfileId, x.ProductTableId });
table.ForeignKey( table.ForeignKey(
name: "FK_UserProfileProductTableStats_ProductTable_ProductTableId", name: "FK_UserProfileProductTableStats_ProductTable_ProductTableId",
column: x => x.ProductTableId, column: x => x.ProductTableId,
@ -388,8 +388,8 @@ namespace DrinkRateAPI.Migrations
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_UserProfileProductTableStats_UserProfiles_UserId", name: "FK_UserProfileProductTableStats_UserProfiles_UserProfileId",
column: x => x.UserId, column: x => x.UserProfileId,
principalTable: "UserProfiles", principalTable: "UserProfiles",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);

View file

@ -344,7 +344,7 @@ namespace DrinkRateAPI.Migrations
modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileCompanyTableStat", b => modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileCompanyTableStat", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserProfileId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("CompanyTableId") b.Property<Guid>("CompanyTableId")
@ -359,7 +359,7 @@ namespace DrinkRateAPI.Migrations
b.Property<int>("RatingCount") b.Property<int>("RatingCount")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("UserId", "CompanyTableId"); b.HasKey("UserProfileId", "CompanyTableId");
b.HasIndex("CompanyTableId"); b.HasIndex("CompanyTableId");
@ -368,7 +368,7 @@ namespace DrinkRateAPI.Migrations
modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileProductTableStat", b => modelBuilder.Entity("DrinkRateAPI.DbEntities.DbUserProfileProductTableStat", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserProfileId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("ProductTableId") b.Property<Guid>("ProductTableId")
@ -383,7 +383,7 @@ namespace DrinkRateAPI.Migrations
b.Property<int>("RatingCount") b.Property<int>("RatingCount")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("UserId", "ProductTableId"); b.HasKey("UserProfileId", "ProductTableId");
b.HasIndex("ProductTableId"); b.HasIndex("ProductTableId");
@ -669,7 +669,7 @@ namespace DrinkRateAPI.Migrations
b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile") b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile")
.WithMany("UserProfileCompanyTableStats") .WithMany("UserProfileCompanyTableStats")
.HasForeignKey("UserId") .HasForeignKey("UserProfileId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
@ -688,7 +688,7 @@ namespace DrinkRateAPI.Migrations
b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile") b.HasOne("DrinkRateAPI.DbEntities.DbUserProfile", "UserProfile")
.WithMany("UserProfileProductTableStats") .WithMany("UserProfileProductTableStats")
.HasForeignKey("UserId") .HasForeignKey("UserProfileId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();

View file

@ -1,5 +1,6 @@
using DrinkRateAPI.Contexts; using DrinkRateAPI.Contexts;
using DrinkRateAPI.DbEntities; using DrinkRateAPI.DbEntities;
using Microsoft.AspNetCore.Identity;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -12,6 +13,7 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthorization(); builder.Services.AddAuthorization();
builder.Services.AddIdentityApiEndpoints<DbApplicationUser>() builder.Services.AddIdentityApiEndpoints<DbApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>(); .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddScoped<UserManager<DbApplicationUser>, UserWithProfileManager>();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerGen(c =>
{ {