Persist backend state with EF Core SQLite
This commit is contained in:
77
RpgRoller/Data/RpgRollerDbContext.cs
Normal file
77
RpgRoller/Data/RpgRollerDbContext.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RpgRoller.Domain;
|
||||
|
||||
namespace RpgRoller.Data;
|
||||
|
||||
public sealed class RpgRollerDbContext : DbContext
|
||||
{
|
||||
public RpgRollerDbContext(DbContextOptions<RpgRollerDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<UserAccount> Users => Set<UserAccount>();
|
||||
public DbSet<UserSession> Sessions => Set<UserSession>();
|
||||
public DbSet<Campaign> Campaigns => Set<Campaign>();
|
||||
public DbSet<Character> Characters => Set<Character>();
|
||||
public DbSet<Skill> Skills => Set<Skill>();
|
||||
public DbSet<RollLogEntry> RollLogEntries => Set<RollLogEntry>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<UserAccount>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Username).IsRequired().HasMaxLength(64);
|
||||
entity.Property(x => x.UsernameNormalized).IsRequired().HasMaxLength(64);
|
||||
entity.Property(x => x.PasswordHash).IsRequired();
|
||||
entity.Property(x => x.DisplayName).IsRequired().HasMaxLength(128);
|
||||
entity.HasIndex(x => x.UsernameNormalized).IsUnique();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<UserSession>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Token);
|
||||
entity.Property(x => x.Token).HasMaxLength(64);
|
||||
entity.Property(x => x.CreatedAtUtc).IsRequired();
|
||||
entity.HasIndex(x => x.UserId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Campaign>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||
entity.Property(x => x.Ruleset).HasConversion<string>().IsRequired();
|
||||
entity.Property(x => x.Version).IsRequired();
|
||||
entity.HasIndex(x => x.GmUserId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Character>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||
entity.HasIndex(x => x.OwnerUserId);
|
||||
entity.HasIndex(x => x.CampaignId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Skill>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||
entity.Property(x => x.DiceRollDefinition).IsRequired().HasMaxLength(128);
|
||||
entity.HasIndex(x => x.CharacterId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<RollLogEntry>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Visibility).HasConversion<string>().IsRequired();
|
||||
entity.Property(x => x.Breakdown).IsRequired().HasMaxLength(256);
|
||||
entity.Property(x => x.TimestampUtc).IsRequired();
|
||||
entity.HasIndex(x => x.CampaignId);
|
||||
entity.HasIndex(x => x.RollerUserId);
|
||||
entity.HasIndex(x => x.SkillId);
|
||||
entity.HasIndex(x => x.CharacterId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,10 @@ public sealed class UserAccount
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required string Username { get; init; }
|
||||
public required string UsernameNormalized { get; init; }
|
||||
public required string PasswordHash { get; set; }
|
||||
public required string DisplayName { get; set; }
|
||||
public Guid? ActiveCharacterId { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserSession
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RpgRoller.Contracts;
|
||||
using RpgRoller.Data;
|
||||
using RpgRoller.Domain;
|
||||
using RpgRoller.Services;
|
||||
|
||||
const string SessionCookieName = "rpgroller_session";
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var sqliteConnectionString = builder.Configuration.GetConnectionString("RpgRoller") ?? "Data Source=App_Data/rpgroller.db";
|
||||
EnsureSqliteDataDirectory(sqliteConnectionString, builder.Environment.ContentRootPath);
|
||||
|
||||
builder.Services.AddSingleton<IPasswordHasher<UserAccount>, PasswordHasher<UserAccount>>();
|
||||
builder.Services.AddDbContext<RpgRollerDbContext>(options => options.UseSqlite(sqliteConnectionString));
|
||||
builder.Services.AddSingleton<IDiceRoller, RandomDiceRoller>();
|
||||
builder.Services.AddSingleton<IGameService, GameService>();
|
||||
builder.Services.AddScoped<IGameService, GameService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<RpgRollerDbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
@@ -287,4 +300,23 @@ static BadRequest<ApiError> ToBadRequest(ServiceError error)
|
||||
return TypedResults.BadRequest(new ApiError(error.Message));
|
||||
}
|
||||
|
||||
static void EnsureSqliteDataDirectory(string connectionString, string contentRootPath)
|
||||
{
|
||||
var builder = new SqliteConnectionStringBuilder(connectionString);
|
||||
if (string.IsNullOrWhiteSpace(builder.DataSource) || builder.DataSource == ":memory:")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var fullPath = Path.IsPathRooted(builder.DataSource)
|
||||
? builder.DataSource
|
||||
: Path.Combine(contentRootPath, builder.DataSource);
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Program;
|
||||
|
||||
@@ -6,4 +6,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"RpgRoller": "Data Source=App_Data/rpgroller.db"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
Reference in New Issue
Block a user