Add comprehensive validation system with runtime configuration
Implements a flexible validation framework for student rankings, team assignments, and team composition with administrator-configurable rules and thresholds. Validation System: - Reflection-based rule discovery eliminates manual registration - Base classes (RequiredEventTypeRuleBase, EventCountThresholdRuleBase) reduce code duplication by ~250 lines - 12 validation rules covering event requirements, counts, and team constraints - Configurable severity levels (Warning/Error) per rule type - ValidationService with caching for optimal performance - Rules apply contextually (StudentRanking, StudentAssignment, TeamComposition) Configuration & Admin UI: - ValidationSettings admin page for editing thresholds and severity levels - ChapterSettings admin page for editing chapter information - Settings stored in Data/appsettings.json for runtime configuration - JSON config works in both development and production environments - Auto-creates Data directory and config template on first run User Experience: - ValidationWarnings component displays inline warnings/errors - Integrated in Event Ranking, Registration, and Team Assignment pages - Color-coded severity indicators (warning yellow, error red) - Includes "Too Many Regional Events" rule (max 3 recommended) Technical Improvements: - Template Method pattern for rule base classes - Singleton rule instances with lazy initialization - Configuration loaded via IConfiguration with fallback to defaults - Safe JSON updates preserve other appsettings sections
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Validation.Rules.TeamRules;
|
||||
|
||||
/// <summary>
|
||||
/// Validation rule that checks if a team-based event has an assigned captain
|
||||
/// </summary>
|
||||
public class MissingCaptainRule : IValidationRule<Team>
|
||||
{
|
||||
public ValidationWarning? Validate(Team entity, ValidationConfiguration config)
|
||||
{
|
||||
if (!config.RequireTeamCaptain)
|
||||
return null;
|
||||
|
||||
// Individual events don't need captains
|
||||
if (entity.Event.EventFormat == EventFormat.Individual)
|
||||
return null;
|
||||
|
||||
if (entity.Captain != null)
|
||||
return null;
|
||||
|
||||
return new ValidationWarning
|
||||
{
|
||||
Code = "MISSING_CAPTAIN",
|
||||
Message = "Team has no captain assigned",
|
||||
Severity = config.MissingCaptainSeverity,
|
||||
Context = ValidationContext.Team,
|
||||
IconIdentifier = "Captain",
|
||||
Metadata = new Dictionary<string, object>
|
||||
{
|
||||
{ "TeamId", entity.Id },
|
||||
{ "EventName", entity.Event.Name },
|
||||
{ "TeamSize", entity.Students.Count }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AppliesTo(ValidationContext context) =>
|
||||
context == ValidationContext.Team;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Validation.Rules.TeamRules;
|
||||
|
||||
/// <summary>
|
||||
/// Validation rule that checks if a team has more members than the maximum allowed
|
||||
/// </summary>
|
||||
public class TeamSizeTooLargeRule : IValidationRule<Team>
|
||||
{
|
||||
public ValidationWarning? Validate(Team entity, ValidationConfiguration config)
|
||||
{
|
||||
// Individual events don't have team size requirements
|
||||
if (entity.Event.EventFormat == EventFormat.Individual)
|
||||
return null;
|
||||
|
||||
var actualSize = entity.Students.Count;
|
||||
var maxSize = entity.Event.MaxTeamSize;
|
||||
|
||||
if (actualSize <= maxSize)
|
||||
return null;
|
||||
|
||||
return new ValidationWarning
|
||||
{
|
||||
Code = "TEAM_SIZE_TOO_LARGE",
|
||||
Message = $"Team has {actualSize} members (max: {maxSize})",
|
||||
Severity = ValidationSeverity.Error, // Always error - hard constraint
|
||||
Context = ValidationContext.Team,
|
||||
IconIdentifier = null,
|
||||
Metadata = new Dictionary<string, object>
|
||||
{
|
||||
{ "TeamId", entity.Id },
|
||||
{ "EventName", entity.Event.Name },
|
||||
{ "ActualSize", actualSize },
|
||||
{ "MinSize", entity.Event.MinTeamSize },
|
||||
{ "MaxSize", maxSize }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AppliesTo(ValidationContext context) =>
|
||||
context == ValidationContext.Team;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Validation.Rules.TeamRules;
|
||||
|
||||
/// <summary>
|
||||
/// Validation rule that checks if a team has fewer members than the minimum required
|
||||
/// </summary>
|
||||
public class TeamSizeTooSmallRule : IValidationRule<Team>
|
||||
{
|
||||
public ValidationWarning? Validate(Team entity, ValidationConfiguration config)
|
||||
{
|
||||
// Individual events don't have team size requirements
|
||||
if (entity.Event.EventFormat == EventFormat.Individual)
|
||||
return null;
|
||||
|
||||
var actualSize = entity.Students.Count;
|
||||
var minSize = entity.Event.MinTeamSize;
|
||||
|
||||
if (actualSize >= minSize)
|
||||
return null;
|
||||
|
||||
return new ValidationWarning
|
||||
{
|
||||
Code = "TEAM_SIZE_TOO_SMALL",
|
||||
Message = $"Team has {actualSize} members (min: {minSize})",
|
||||
Severity = config.TeamSizeSeverity,
|
||||
Context = ValidationContext.Team,
|
||||
IconIdentifier = null,
|
||||
Metadata = new Dictionary<string, object>
|
||||
{
|
||||
{ "TeamId", entity.Id },
|
||||
{ "EventName", entity.Event.Name },
|
||||
{ "ActualSize", actualSize },
|
||||
{ "MinSize", minSize },
|
||||
{ "MaxSize", entity.Event.MaxTeamSize }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AppliesTo(ValidationContext context) =>
|
||||
context == ValidationContext.Team;
|
||||
}
|
||||
Reference in New Issue
Block a user