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

View file

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

View file

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

View file

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