using EARSystem.Core;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace EARSystem.Infrastructure;
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; } = "";
public bool IsDemo { get; set; }
}
public class AppDbContext(DbContextOptions<AppDbContext> options) : IdentityDbContext<ApplicationUser>(options)
{
public DbSet<MoodEntry> Moods => Set<MoodEntry>();
public DbSet<UserPreference> Preferences => Set<UserPreference>();
public DbSet<Place> Places => Set<Place>();
public DbSet<PlaceAtmosphere> Atmospheres => Set<PlaceAtmosphere>();
public DbSet<Experience> Experiences => Set<Experience>();
public DbSet<Favorite> Favorites => Set<Favorite>();
public DbSet<RecommendationRecord> Recommendations => Set<RecommendationRecord>();
public DbSet<RuleConfiguration> Rules => Set<RuleConfiguration>();
protected override void OnModelCreating(ModelBuilder model)
{
base.OnModelCreating(model);
model.Entity<ApplicationUser>().Property(x => x.DisplayName).HasMaxLength(40);
model.Entity<MoodEntry>(b => {
b.HasIndex(x => new { x.UserId, x.RecordedAt });
b.Property(x => x.Thoughts).HasMaxLength(5000);
b.Property(x => x.TagsJson).HasMaxLength(1000);
b.Property(x => x.Version).IsConcurrencyToken();
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<UserPreference>(b => {
b.HasKey(x => x.UserId);
b.Property(x => x.Budget).HasPrecision(10, 2);
b.HasOne<ApplicationUser>().WithOne().HasForeignKey<UserPreference>(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<Place>(b => {
b.Property(x => x.Name).HasMaxLength(100);
b.Property(x => x.Address).HasMaxLength(300);
b.Property(x => x.Description).HasMaxLength(1000);
b.Property(x => x.AuthorizedExternalId).HasMaxLength(100);
b.Property(x => x.Price).HasPrecision(10, 2);
b.HasIndex(x => new { x.Source, x.Category });
b.HasOne(x => x.Atmosphere).WithOne().HasForeignKey<PlaceAtmosphere>(x => x.PlaceId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<PlaceAtmosphere>().HasKey(x => x.PlaceId);
model.Entity<PlaceAtmosphere>().Property(x => x.Evidence).HasMaxLength(500);
model.Entity<Experience>(b => {
b.HasIndex(x => new { x.UserId, x.PlaceId, x.VisitDay }).IsUnique();
b.HasIndex(x => new { x.UserId, x.VisitedAt });
b.HasIndex(x => new { x.PlaceId, x.Contribute });
b.Property(x => x.Thoughts).HasMaxLength(5000);
b.Property(x => x.Version).IsConcurrencyToken();
b.HasOne(x => x.Place).WithMany().HasForeignKey(x => x.PlaceId).OnDelete(DeleteBehavior.Restrict);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<Favorite>(b => {
b.HasKey(x => new { x.UserId, x.PlaceId });
b.HasOne(x => x.Place).WithMany().HasForeignKey(x => x.PlaceId).OnDelete(DeleteBehavior.Restrict);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<RecommendationRecord>(b => {
b.HasIndex(x => new { x.UserId, x.CreatedAt });
b.Property(x => x.AlgorithmVersion).HasMaxLength(50);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<RuleConfiguration>(b => {
b.Property(x => x.Version).HasMaxLength(50);
b.Property(x => x.ConcurrencyStamp).IsConcurrencyToken();
});
// UTC ticks preserve ordering on both SQL Server and the isolated SQLite test database.
foreach (var type in model.Model.GetEntityTypes())
foreach (var property in type.GetProperties().Where(p => p.ClrType == typeof(DateTimeOffset)))
property.SetValueConverter(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<DateTimeOffset, long>(
value => value.UtcTicks, ticks => new DateTimeOffset(ticks, TimeSpan.Zero)));
}
}
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
var connection = Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
?? "Server=(localdb)\\MSSQLLocalDB;Database=EARSystem;Trusted_Connection=True;TrustServerCertificate=True";
return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlServer(connection).Options);
}
}
using EARSystem.Core;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace EARSystem.Infrastructure;
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; } = "";
public bool IsDemo { get; set; }
}
public class AppDbContext(DbContextOptions<AppDbContext> options) : IdentityDbContext<ApplicationUser>(options)
{
public DbSet<MoodEntry> Moods => Set<MoodEntry>();
public DbSet<UserPreference> Preferences => Set<UserPreference>();
public DbSet<Place> Places => Set<Place>();
public DbSet<PlaceAtmosphere> Atmospheres => Set<PlaceAtmosphere>();
public DbSet<Experience> Experiences => Set<Experience>();
public DbSet<Favorite> Favorites => Set<Favorite>();
public DbSet<RecommendationRecord> Recommendations => Set<RecommendationRecord>();
public DbSet<RuleConfiguration> Rules => Set<RuleConfiguration>();
protected override void OnModelCreating(ModelBuilder model)
{
base.OnModelCreating(model);
model.Entity<ApplicationUser>().Property(x => x.DisplayName).HasMaxLength(40);
model.Entity<MoodEntry>(b => {
b.HasIndex(x => new { x.UserId, x.RecordedAt });
b.Property(x => x.Thoughts).HasMaxLength(5000);
b.Property(x => x.TagsJson).HasMaxLength(1000);
b.Property(x => x.Version).IsConcurrencyToken();
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<UserPreference>(b => {
b.HasKey(x => x.UserId);
b.Property(x => x.Budget).HasPrecision(10, 2);
b.HasOne<ApplicationUser>().WithOne().HasForeignKey<UserPreference>(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<Place>(b => {
b.Property(x => x.Name).HasMaxLength(100);
b.Property(x => x.Address).HasMaxLength(300);
b.Property(x => x.Description).HasMaxLength(1000);
b.Property(x => x.AuthorizedExternalId).HasMaxLength(100);
b.Property(x => x.Price).HasPrecision(10, 2);
b.HasIndex(x => new { x.Source, x.Category });
b.HasOne(x => x.Atmosphere).WithOne().HasForeignKey<PlaceAtmosphere>(x => x.PlaceId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<PlaceAtmosphere>().HasKey(x => x.PlaceId);
model.Entity<PlaceAtmosphere>().Property(x => x.Evidence).HasMaxLength(500);
model.Entity<Experience>(b => {
b.HasIndex(x => new { x.UserId, x.PlaceId, x.VisitDay }).IsUnique();
b.HasIndex(x => new { x.UserId, x.VisitedAt });
b.HasIndex(x => new { x.PlaceId, x.Contribute });
b.Property(x => x.Thoughts).HasMaxLength(5000);
b.Property(x => x.Version).IsConcurrencyToken();
b.HasOne(x => x.Place).WithMany().HasForeignKey(x => x.PlaceId).OnDelete(DeleteBehavior.Restrict);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<Favorite>(b => {
b.HasKey(x => new { x.UserId, x.PlaceId });
b.HasOne(x => x.Place).WithMany().HasForeignKey(x => x.PlaceId).OnDelete(DeleteBehavior.Restrict);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<RecommendationRecord>(b => {
b.HasIndex(x => new { x.UserId, x.CreatedAt });
b.Property(x => x.AlgorithmVersion).HasMaxLength(50);
b.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
});
model.Entity<RuleConfiguration>(b => {
b.Property(x => x.Version).HasMaxLength(50);
b.Property(x => x.ConcurrencyStamp).IsConcurrencyToken();
});
// UTC ticks preserve ordering on both SQL Server and the isolated SQLite test database.
foreach (var type in model.Model.GetEntityTypes())
foreach (var property in type.GetProperties().Where(p => p.ClrType == typeof(DateTimeOffset)))
property.SetValueConverter(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<DateTimeOffset, long>(
value => value.UtcTicks, ticks => new DateTimeOffset(ticks, TimeSpan.Zero)));
}
}
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
var connection = Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
?? "Server=(localdb)\\MSSQLLocalDB;Database=EARSystem;Trusted_Connection=True;TrustServerCertificate=True";
return new(new DbContextOptionsBuilder<AppDbContext>().UseSqlServer(connection).Options);
}
}