feat: add AppDbContext (Identity + RefreshTokens) and DbSeeder (13 roles + dev admin)
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using ROLAC.API.Entities;
|
||||||
|
|
||||||
|
namespace ROLAC.API.Data;
|
||||||
|
|
||||||
|
public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
|
||||||
|
{
|
||||||
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||||
|
|
||||||
|
public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
|
builder.Entity<RefreshToken>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id);
|
||||||
|
|
||||||
|
// Unique index on hash — enables fast lookup and prevents duplicate tokens
|
||||||
|
entity.HasIndex(e => e.TokenHash).IsUnique();
|
||||||
|
|
||||||
|
entity.Property(e => e.TokenHash).HasMaxLength(64).IsRequired();
|
||||||
|
entity.Property(e => e.UserId).HasMaxLength(450).IsRequired();
|
||||||
|
entity.Property(e => e.DeviceInfo).HasMaxLength(200);
|
||||||
|
entity.Property(e => e.IpAddress).HasMaxLength(45);
|
||||||
|
entity.Property(e => e.ReplacedByHash).HasMaxLength(64);
|
||||||
|
|
||||||
|
entity.HasOne(e => e.User)
|
||||||
|
.WithMany(u => u.RefreshTokens)
|
||||||
|
.HasForeignKey(e => e.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
// Computed properties are not DB columns
|
||||||
|
entity.Ignore(e => e.IsExpired);
|
||||||
|
entity.Ignore(e => e.IsRevoked);
|
||||||
|
entity.Ignore(e => e.IsActive);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Entity<AppUser>(entity =>
|
||||||
|
{
|
||||||
|
entity.Property(e => e.LanguagePreference).HasMaxLength(10).HasDefaultValue("en");
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Entity<AppRole>(entity =>
|
||||||
|
{
|
||||||
|
entity.Property(e => e.Description).HasMaxLength(500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using ROLAC.API.Entities;
|
||||||
|
|
||||||
|
namespace ROLAC.API.Data;
|
||||||
|
|
||||||
|
public static class DbSeeder
|
||||||
|
{
|
||||||
|
private static readonly (string Name, string Description)[] Roles =
|
||||||
|
[
|
||||||
|
("super_admin", "System administrator — full access"),
|
||||||
|
("pastor", "Pastor — full member and financial overview"),
|
||||||
|
("board_member", "Board member — church governance"),
|
||||||
|
("coworker_chair", "Coworker chair — coordinates ministry leaders"),
|
||||||
|
("ministry_leader", "Ministry leader — scoped to own ministry"),
|
||||||
|
("district_leader", "District leader — manages multiple cell groups"),
|
||||||
|
("cell_leader", "Cell leader — scoped to own cell group"),
|
||||||
|
("coworker", "Coworker — general worker in assigned ministry"),
|
||||||
|
("finance", "Finance — manages giving and expense reports"),
|
||||||
|
("secretary", "Secretary — manages member data and scheduling"),
|
||||||
|
("worship_leader", "Worship leader — manages song library and setlists (Phase deferred)"),
|
||||||
|
("member", "Member — views own profile and service roster"),
|
||||||
|
("visitor", "Visitor — public pages only"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static async Task SeedRolesAsync(RoleManager<AppRole> roleManager)
|
||||||
|
{
|
||||||
|
foreach (var (name, description) in Roles)
|
||||||
|
{
|
||||||
|
if (!await roleManager.RoleExistsAsync(name))
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(new AppRole
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Description = description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a super_admin test account for local development.
|
||||||
|
/// DO NOT call this in production — remove or guard with IsDevelopment().
|
||||||
|
/// Credentials: admin@rolac.org / Admin1234!
|
||||||
|
/// </summary>
|
||||||
|
public static async Task SeedAdminUserAsync(UserManager<AppUser> userManager)
|
||||||
|
{
|
||||||
|
const string adminEmail = "admin@rolac.org";
|
||||||
|
const string adminPassword = "Admin1234!";
|
||||||
|
|
||||||
|
if (await userManager.FindByEmailAsync(adminEmail) is null)
|
||||||
|
{
|
||||||
|
var admin = new AppUser
|
||||||
|
{
|
||||||
|
UserName = adminEmail,
|
||||||
|
Email = adminEmail,
|
||||||
|
EmailConfirmed = true,
|
||||||
|
IsActive = true,
|
||||||
|
LanguagePreference = "en",
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await userManager.CreateAsync(admin, adminPassword);
|
||||||
|
if (result.Succeeded)
|
||||||
|
await userManager.AddToRoleAsync(admin, "super_admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user