Sube capa Infrastructure, Solution y archivos de configuracion del Backend
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core mapping for the sensitive-action audit trail.
|
||||
/// </summary>
|
||||
public class AuditLogEntityConfiguration : BaseEntityConfiguration<AuditLog>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<AuditLog> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.AuditLog, EntityConstants.Schema);
|
||||
|
||||
// Action is persisted as the enum name string, mirroring other enum columns in this DB, including Match.Status and PlayerTeamRegistration.MedicalRecordStatus.
|
||||
builder.Property(a => a.Action).IsRequired().HasConversion<string>();
|
||||
builder.Property(a => a.Actor).IsRequired().HasMaxLength(256);
|
||||
builder.Property(a => a.TargetType).HasMaxLength(128);
|
||||
builder.Property(a => a.TargetId).HasMaxLength(128);
|
||||
builder.Property(a => a.TargetName).HasMaxLength(256);
|
||||
builder.Property(a => a.Detail).HasMaxLength(1024);
|
||||
|
||||
builder.HasIndex(a => a.Action);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class BackupRecordEntityConfiguration : BaseEntityConfiguration<BackupRecord>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<BackupRecord> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.BackupRecord, EntityConstants.Schema);
|
||||
|
||||
builder.Property(b => b.StoragePath).IsRequired().HasMaxLength(500);
|
||||
builder.Property(b => b.SizeBytes).IsRequired();
|
||||
builder.Property(b => b.Origin).IsRequired().HasConversion<string>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Base EF Core configuration shared by every entity, mapping the primary key, audit timestamps, and a DateCreated index.
|
||||
/// </summary>
|
||||
public abstract class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
|
||||
where TEntity : EntityBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<TEntity> builder)
|
||||
{
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id)
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
builder.Property(e => e.DateCreated)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(e => e.DateUpdated);
|
||||
|
||||
builder.HasIndex(e => e.DateCreated)
|
||||
.HasDatabaseName($"IX_{typeof(TEntity).Name}_CreatedAt");
|
||||
|
||||
ConfigureEntity(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to configure entity-specific table, columns, indexes, and relationships.
|
||||
/// </summary>
|
||||
protected abstract void ConfigureEntity(EntityTypeBuilder<TEntity> builder);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class BlogPostEntityConfiguration : BaseEntityConfiguration<BlogPost>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<BlogPost> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.BlogPost, EntityConstants.Schema);
|
||||
|
||||
builder.Property(b => b.Author).IsRequired().HasMaxLength(50);
|
||||
builder.Property(b => b.Title).IsRequired();
|
||||
builder.Property(b => b.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(b => b.PhotoUrl).HasMaxLength(2048);
|
||||
builder.Property(b => b.MarkdownText).IsRequired();
|
||||
|
||||
// Defaults to true so every pre-existing post back-filled by the migration stays publicly visible, and any future insert that omits the flag is treated as published.
|
||||
builder.Property(b => b.IsPublished).IsRequired().HasDefaultValue(true);
|
||||
|
||||
builder.HasIndex(b => b.Slug).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Maps the Club to Team relationship as optional and non-cascading, so deleting a Club only nulls its teams' ClubId instead of removing them.
|
||||
/// </summary>
|
||||
public class ClubEntityConfiguration : BaseEntityConfiguration<Club>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Club> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Club, EntityConstants.Schema);
|
||||
|
||||
builder.Property(c => c.Name).IsRequired();
|
||||
builder.Property(c => c.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(c => c.LogoUrl);
|
||||
|
||||
builder.HasIndex(c => c.Slug).IsUnique();
|
||||
|
||||
builder.HasMany(c => c.Teams)
|
||||
.WithOne(t => t.Club)
|
||||
.HasForeignKey(t => t.ClubId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
// Self-referencing, one level deep: deleting the parent club unlinks its
|
||||
// squads instead of cascading, and Restrict avoids the multiple-cascade-path
|
||||
// error SQL Server/SQLite raise for a self-join with SetNull here too.
|
||||
builder.HasMany(c => c.ChildClubs)
|
||||
.WithOne(c => c.ParentClub)
|
||||
.HasForeignKey(c => c.ParentClubId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using Application.Utils.Constants.Validation;
|
||||
|
||||
using Domain.Entities.Models;
|
||||
using Domain.Enums;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class DivisionEntityConfiguration : BaseEntityConfiguration<Division>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Division> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Division, EntityConstants.Schema);
|
||||
|
||||
builder.Property(d => d.Name).IsRequired().HasMaxLength(DivisionFieldLengths.NameMaxLength);
|
||||
builder.Property(d => d.Slug).IsRequired().HasMaxLength(220);
|
||||
|
||||
builder.HasIndex(d => d.Slug).IsUnique();
|
||||
|
||||
builder.HasOne(d => d.Tournament)
|
||||
.WithMany(t => t.Divisions)
|
||||
.HasForeignKey(d => d.TournamentId)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(d => d.PointsForWin).IsRequired().HasDefaultValue(2);
|
||||
builder.Property(d => d.PointsForLoss).IsRequired().HasDefaultValue(1);
|
||||
builder.Property(d => d.QualifiersPerGroup).IsRequired().HasDefaultValue(1);
|
||||
builder.Property(d => d.Category).IsRequired().HasDefaultValue(TournamentCategory.Masculine);
|
||||
|
||||
builder.HasMany(d => d.Stages)
|
||||
.WithOne(s => s.Division)
|
||||
.HasForeignKey(s => s.DivisionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(d => d.PlayoffMappings)
|
||||
.WithOne(m => m.Division)
|
||||
.HasForeignKey(m => m.DivisionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class DivisionPlayoffMappingEntityConfiguration : BaseEntityConfiguration<DivisionPlayoffMapping>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<DivisionPlayoffMapping> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.DivisionPlayoffMapping, EntityConstants.Schema);
|
||||
|
||||
builder.Property(m => m.FromPosition).IsRequired();
|
||||
builder.Property(m => m.ToPosition).IsRequired();
|
||||
builder.Property(m => m.Destination).IsRequired().HasMaxLength(100);
|
||||
|
||||
builder.HasIndex(m => m.DivisionId);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// A team is enrolled in the same division at most once, enforced by a unique index on TeamId and DivisionId.
|
||||
/// </summary>
|
||||
public class DivisionTeamRegistrationEntityConfiguration : BaseEntityConfiguration<DivisionTeamRegistration>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<DivisionTeamRegistration> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.DivisionTeamRegistration, EntityConstants.Schema);
|
||||
|
||||
builder.Property(r => r.TeamId).IsRequired();
|
||||
builder.Property(r => r.DivisionId).IsRequired();
|
||||
|
||||
builder.HasIndex(r => new { r.TeamId, r.DivisionId }).IsUnique();
|
||||
|
||||
builder.HasIndex(r => r.DivisionId);
|
||||
|
||||
builder.HasOne(r => r.Team)
|
||||
.WithMany(t => t.DivisionTeamRegistrations)
|
||||
.HasForeignKey(r => r.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(r => r.Division)
|
||||
.WithMany(d => d.DivisionTeamRegistrations)
|
||||
.HasForeignKey(r => r.DivisionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Match has multiple optional foreign keys to Team, each configured explicitly to avoid EF Core relationship ambiguity.
|
||||
/// </summary>
|
||||
public class MatchEntityConfiguration : BaseEntityConfiguration<Match>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Match> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Match, EntityConstants.Schema);
|
||||
|
||||
builder.Property(m => m.Type).IsRequired().HasConversion<string>();
|
||||
builder.Property(m => m.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(m => m.IsFinished).IsRequired();
|
||||
builder.Property(m => m.Status).IsRequired().HasConversion<string>();
|
||||
builder.Property(m => m.StageId).IsRequired();
|
||||
|
||||
builder.HasIndex(m => m.Slug).IsUnique();
|
||||
|
||||
builder.HasOne(m => m.Stage)
|
||||
.WithMany(s => s.Matches)
|
||||
.HasForeignKey(m => m.StageId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasOne(m => m.HomeTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.HomeTeamId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(m => m.VisitorTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.VisitorTeamId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(m => m.WinningTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.WinningTeamId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(m => m.Venue)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.VenueId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(m => m.Series)
|
||||
.WithMany(ms => ms.Matches)
|
||||
.HasForeignKey(m => m.SeriesId)
|
||||
.IsRequired(false);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// MatchSeries has multiple foreign keys to Team, each configured explicitly to avoid EF Core relationship ambiguity.
|
||||
/// </summary>
|
||||
public class MatchSeriesEntityConfiguration : BaseEntityConfiguration<MatchSeries>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<MatchSeries> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.MatchSeries, EntityConstants.Schema);
|
||||
|
||||
builder.Property(ms => ms.StageId).IsRequired();
|
||||
builder.Property(ms => ms.HomeTeamId).IsRequired();
|
||||
builder.Property(ms => ms.VisitorTeamId).IsRequired();
|
||||
|
||||
builder.HasOne(ms => ms.Stage)
|
||||
.WithMany(s => s.MatchSeries)
|
||||
.HasForeignKey(ms => ms.StageId)
|
||||
.IsRequired();
|
||||
|
||||
// Explicit Restrict overrides EF's default Cascade for required FKs so a team with an active series is never silently deleted along with its series history if TeamService.DeleteTeamAsync's guard is ever bypassed.
|
||||
builder.HasOne(ms => ms.HomeTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(ms => ms.HomeTeamId)
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(ms => ms.VisitorTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(ms => ms.VisitorTeamId)
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(ms => ms.WinningTeam)
|
||||
.WithMany()
|
||||
.HasForeignKey(ms => ms.WinningTeamId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasMany(ms => ms.Matches)
|
||||
.WithOne(m => m.Series)
|
||||
.HasForeignKey(m => m.SeriesId)
|
||||
.IsRequired(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// FullName is ignored because it is a computed C# property, not a DB column.
|
||||
/// </summary>
|
||||
public class PlayerEntityConfiguration : BaseEntityConfiguration<Player>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Player> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Player, EntityConstants.Schema);
|
||||
|
||||
builder.Property(p => p.FirstName).IsRequired().HasMaxLength(70);
|
||||
builder.Property(p => p.SecondName).HasMaxLength(70);
|
||||
builder.Property(p => p.LastName).IsRequired().HasMaxLength(70);
|
||||
builder.Property(p => p.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(p => p.DocumentNumber).IsRequired().HasMaxLength(15);
|
||||
builder.Property(p => p.IsSanctioned).IsRequired();
|
||||
builder.Property(p => p.BirthDate).IsRequired();
|
||||
builder.Property(p => p.SocialSecurity).IsRequired().HasMaxLength(100);
|
||||
|
||||
builder.Ignore(p => p.FullName);
|
||||
|
||||
builder.HasIndex(p => p.DocumentNumber).IsUnique();
|
||||
|
||||
builder.HasIndex(p => p.Slug).IsUnique();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class PlayerSanctionEntityConfiguration : BaseEntityConfiguration<PlayerSanction>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<PlayerSanction> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.PlayerSanction, EntityConstants.Schema);
|
||||
|
||||
builder.Property(ps => ps.Duration).IsRequired();
|
||||
builder.Property(ps => ps.IssuedDate).IsRequired();
|
||||
builder.Property(ps => ps.Description).IsRequired().HasMaxLength(255);
|
||||
builder.Property(ps => ps.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(ps => ps.SubjectType).IsRequired().HasConversion<string>();
|
||||
builder.Property(ps => ps.StaffName).HasMaxLength(255);
|
||||
builder.Property(ps => ps.AppealStatus).IsRequired().HasConversion<string>();
|
||||
builder.Property(ps => ps.AppealReason).HasMaxLength(1000);
|
||||
builder.Property(ps => ps.AppealResolution).HasMaxLength(1000);
|
||||
|
||||
builder.HasIndex(ps => ps.Slug).IsUnique();
|
||||
|
||||
// OnDelete is stated explicitly because the existing migrations already applied Cascade for Player and Restrict for Team, and without it the optional FKs would default to NoAction and drift from the persisted schema.
|
||||
builder.HasOne(ps => ps.Player)
|
||||
.WithMany()
|
||||
.HasForeignKey(ps => ps.PlayerId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(ps => ps.Team)
|
||||
.WithMany()
|
||||
.HasForeignKey(ps => ps.TeamId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(ps => ps.Match)
|
||||
.WithMany()
|
||||
.HasForeignKey(ps => ps.MatchId)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class PlayerStatisticEntityConfiguration : BaseEntityConfiguration<PlayerStatistic>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<PlayerStatistic> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.PlayerStatistic, EntityConstants.Schema);
|
||||
|
||||
builder.Property(ps => ps.Value).IsRequired();
|
||||
|
||||
builder.Property(ps => ps.Type).IsRequired().HasConversion<string>();
|
||||
|
||||
builder.HasOne(ps => ps.Match)
|
||||
.WithMany(m => m.PlayerStatistics)
|
||||
.HasForeignKey(ps => ps.MatchId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasOne(ps => ps.Player)
|
||||
.WithMany()
|
||||
.HasForeignKey(ps => ps.PlayerId)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// A player may have at most one registration per tournament season, enforced by a unique index on PlayerId and TournamentId, so a player can never be on two teams in one season.
|
||||
/// </summary>
|
||||
public class PlayerTeamRegistrationEntityConfiguration : BaseEntityConfiguration<PlayerTeamRegistration>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<PlayerTeamRegistration> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.PlayerTeamRegistration, EntityConstants.Schema);
|
||||
|
||||
builder.Property(r => r.PlayerId).IsRequired();
|
||||
builder.Property(r => r.TeamId).IsRequired();
|
||||
builder.Property(r => r.TournamentId).IsRequired();
|
||||
|
||||
// Status is persisted as the enum name string, mirroring other enum columns in this DB, including Match.Status, PlayerStatistic.Type, and PlayerSanction.AppealStatus.
|
||||
builder.Property(r => r.MedicalRecordStatus).IsRequired().HasConversion<string>();
|
||||
builder.Property(r => r.MedicalRecordFileUrl);
|
||||
builder.Property(r => r.MedicalRecordFileName);
|
||||
builder.Property(r => r.MedicalRecordReviewReason);
|
||||
builder.Property(r => r.MedicalRecordReviewedAt);
|
||||
|
||||
builder.HasIndex(r => new { r.PlayerId, r.TournamentId }).IsUnique();
|
||||
|
||||
// Both Npgsql and SQLite treat NULLs as distinct in a unique index, so this allows any number of players with no assigned dorsal while rejecting duplicate non-null dorsals, with no provider-specific filtered index required.
|
||||
builder.Property(r => r.JerseyNumber);
|
||||
builder.HasIndex(r => new { r.TeamId, r.TournamentId, r.JerseyNumber }).IsUnique();
|
||||
|
||||
builder.HasOne(r => r.Player)
|
||||
.WithMany(p => p.PlayerTeamRegistrations)
|
||||
.HasForeignKey(r => r.PlayerId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(r => r.Team)
|
||||
.WithMany(t => t.PlayerTeamRegistrations)
|
||||
.HasForeignKey(r => r.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(r => r.Tournament)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.TournamentId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Scorer is a service-layer read-model with no DataAnnotations, so there is nothing to migrate beyond the mappings configured here.
|
||||
/// </summary>
|
||||
public class ScorerEntityConfiguration : BaseEntityConfiguration<Scorer>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Scorer> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Scorer, EntityConstants.Schema);
|
||||
|
||||
builder.Property(s => s.PlayerId).IsRequired();
|
||||
builder.Property(s => s.Points).IsRequired();
|
||||
builder.Property(s => s.MatchId).IsRequired();
|
||||
|
||||
builder.HasOne(s => s.Player)
|
||||
.WithMany(p => p.Scorers)
|
||||
.HasForeignKey(s => s.PlayerId);
|
||||
|
||||
builder.HasOne(s => s.Match)
|
||||
.WithMany(m => m.Scorers)
|
||||
.HasForeignKey(s => s.MatchId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Application.Utils.Constants.Validation;
|
||||
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class SeasonEntityConfiguration : BaseEntityConfiguration<Season>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Season> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Season, EntityConstants.Schema);
|
||||
|
||||
builder.Property(s => s.Name).IsRequired().HasMaxLength(SeasonFieldLengths.NameMaxLength);
|
||||
builder.Property(s => s.Slug).IsRequired().HasMaxLength(220);
|
||||
|
||||
builder.HasIndex(s => s.Slug).IsUnique();
|
||||
|
||||
// Deleting a season detaches its tournaments via SetNull instead of cascading, since a season is a purely additive grouping and must never delete a tournament.
|
||||
builder.HasMany(s => s.Tournaments)
|
||||
.WithOne(t => t.Season)
|
||||
.HasForeignKey(t => t.SeasonId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class StageEntityConfiguration : BaseEntityConfiguration<Stage>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Stage> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Stage, EntityConstants.Schema);
|
||||
|
||||
builder.Property(s => s.Name).IsRequired().HasMaxLength(100);
|
||||
builder.Property(s => s.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(s => s.Description).HasMaxLength(1000);
|
||||
builder.Property(s => s.StageType).IsRequired().HasConversion<string>();
|
||||
builder.Property(s => s.IsActive).IsRequired();
|
||||
builder.Property(s => s.StartDate).IsRequired();
|
||||
builder.Property(s => s.EndDate).IsRequired();
|
||||
builder.Property(s => s.DivisionId).IsRequired();
|
||||
builder.Property(s => s.BracketName).HasMaxLength(100);
|
||||
builder.Property(s => s.BestOf).IsRequired().HasDefaultValue(1);
|
||||
builder.Property(s => s.RoundRobinLegs).IsRequired().HasDefaultValue(1);
|
||||
|
||||
builder.HasIndex(s => new { s.Name, s.DivisionId })
|
||||
.IsUnique()
|
||||
.HasDatabaseName(EntityConstants.Indexes.UniqueStageNameDivision);
|
||||
|
||||
builder.HasIndex(s => s.Slug).IsUnique();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class StageTeamMatchEntityConfiguration : BaseEntityConfiguration<StageTeamMatch>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<StageTeamMatch> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.StageTeamMatch, EntityConstants.Schema);
|
||||
|
||||
builder.Property(stm => stm.StageId).IsRequired();
|
||||
builder.Property(stm => stm.TeamId).IsRequired();
|
||||
|
||||
builder.HasOne(stm => stm.Stage)
|
||||
.WithMany(s => s.StageTeamMatches)
|
||||
.HasForeignKey(stm => stm.StageId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(stm => stm.Team)
|
||||
.WithMany(t => t.StageTeamMatches)
|
||||
.HasForeignKey(stm => stm.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// TournamentId is an optional foreign key, since a Team may exist without a tournament.
|
||||
/// </summary>
|
||||
public class TeamEntityConfiguration : BaseEntityConfiguration<Team>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Team> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Team, EntityConstants.Schema);
|
||||
|
||||
builder.Property(t => t.Name).IsRequired();
|
||||
builder.Property(t => t.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(t => t.ThreeLetterCode).IsRequired();
|
||||
builder.Property(t => t.LogoUrl).IsRequired();
|
||||
builder.Property(t => t.ShirtColor).IsRequired();
|
||||
builder.Property(t => t.JerseyStyle).IsRequired().HasMaxLength(20).HasDefaultValue("solid");
|
||||
builder.Property(t => t.ShirtSecondaryColor).HasMaxLength(9);
|
||||
builder.Property(t => t.ShirtTertiaryColor).HasMaxLength(9);
|
||||
|
||||
builder.HasIndex(t => t.Slug).IsUnique();
|
||||
|
||||
builder.HasOne(t => t.Tournament)
|
||||
.WithMany(tourn => tourn.Teams)
|
||||
.HasForeignKey(t => t.TournamentId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasMany(t => t.Players)
|
||||
.WithOne(p => p.Team)
|
||||
.HasForeignKey(p => p.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Deleting a division cascades its point deductions away, but the team foreign key is Restrict, so a team that still carries deductions is not silently removed.
|
||||
/// </summary>
|
||||
public class TeamPointDeductionEntityConfiguration : BaseEntityConfiguration<TeamPointDeduction>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<TeamPointDeduction> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.TeamPointDeduction, EntityConstants.Schema);
|
||||
|
||||
builder.Property(d => d.Points).IsRequired();
|
||||
builder.Property(d => d.Reason).IsRequired().HasMaxLength(300);
|
||||
|
||||
builder.HasIndex(d => d.DivisionId);
|
||||
|
||||
builder.HasOne(d => d.Division)
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.DivisionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(d => d.Team)
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.TeamId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// A team staff row carries no independent value once its team or tournament is gone, so both foreign keys cascade.
|
||||
/// </summary>
|
||||
public class TeamStaffEntityConfiguration : BaseEntityConfiguration<TeamStaff>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<TeamStaff> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.TeamStaff, EntityConstants.Schema);
|
||||
|
||||
builder.Property(s => s.FullName).IsRequired().HasMaxLength(150);
|
||||
builder.Property(s => s.Role).IsRequired();
|
||||
|
||||
builder.HasIndex(s => new { s.TeamId, s.TournamentId });
|
||||
|
||||
builder.HasOne(s => s.Team)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(s => s.Tournament)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.TournamentId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// A team may be registered to the same tournament only once, enforced by a unique index on TeamId and TournamentId, but may hold independent registrations across multiple tournaments at once.
|
||||
/// </summary>
|
||||
public class TeamTournamentRegistrationEntityConfiguration : BaseEntityConfiguration<TeamTournamentRegistration>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<TeamTournamentRegistration> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.TeamTournamentRegistration, EntityConstants.Schema);
|
||||
|
||||
builder.Property(r => r.TeamId).IsRequired();
|
||||
builder.Property(r => r.TournamentId).IsRequired();
|
||||
|
||||
builder.HasIndex(r => new { r.TeamId, r.TournamentId }).IsUnique();
|
||||
|
||||
builder.HasOne(r => r.Team)
|
||||
.WithMany(t => t.TeamTournamentRegistrations)
|
||||
.HasForeignKey(r => r.TeamId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasOne(r => r.Tournament)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.TournamentId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using Domain.Entities.Models;
|
||||
using Domain.Enums;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
public class TournamentEntityConfiguration : BaseEntityConfiguration<Tournament>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Tournament> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Tournament, EntityConstants.Schema, t =>
|
||||
t.HasCheckConstraint(
|
||||
EntityConstants.CheckConstraints.TournamentDeadlineBeforeStart,
|
||||
"\"TeamRegistrationDeadline\" < \"StartDate\""
|
||||
)
|
||||
);
|
||||
|
||||
builder.Property(t => t.Name).IsRequired();
|
||||
builder.Property(t => t.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(t => t.Description).IsRequired();
|
||||
builder.Property(t => t.TeamRegistrationDeadline).IsRequired();
|
||||
builder.Property(t => t.StartDate).IsRequired();
|
||||
builder.Property(t => t.Status).HasDefaultValue(TournamentStatus.Scheduled).IsRequired();
|
||||
builder.Property(t => t.Category).HasDefaultValue(TournamentCategory.Masculine).IsRequired();
|
||||
|
||||
builder.HasIndex(t => t.Slug).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Domain.Entities.Models;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Infrastructure.Persistance.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// PhotoUrl is intentionally not configured here since its format is enforced by the ImageReference attribute on the request DTO, a validation-only concern with no corresponding database constraint.
|
||||
/// </summary>
|
||||
public class VenueEntityConfiguration : BaseEntityConfiguration<Venue>
|
||||
{
|
||||
protected override void ConfigureEntity(EntityTypeBuilder<Venue> builder)
|
||||
{
|
||||
builder.ToTable(EntityConstants.Tables.Venue, EntityConstants.Schema);
|
||||
|
||||
builder.Property(v => v.Name).IsRequired().HasMaxLength(50);
|
||||
builder.Property(v => v.Slug).IsRequired().HasMaxLength(220);
|
||||
builder.Property(v => v.Address).IsRequired().HasMaxLength(200);
|
||||
|
||||
builder.HasIndex(v => v.Slug).IsUnique();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user