Skip to content

Commit aefe418

Browse files
committed
Use FluentValidation for Add alert journey
1 parent a9febe0 commit aefe418

File tree

6 files changed

+96
-44
lines changed

6 files changed

+96
-44
lines changed

TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Alert.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using EntityFrameworkCore.Projectables;
2+
using FluentValidation;
23
using TeachingRecordSystem.Core.Events.Legacy;
34

45
namespace TeachingRecordSystem.Core.DataStore.Postgres.Models;
@@ -10,6 +11,8 @@ public class Alert
1011
public const string PersonIdIndexName = "ix_alerts_person_id";
1112
public const string PersonForeignKeyName = "fk_alerts_person";
1213

14+
public const int DetailsMaxLength = 4000;
15+
1316
public required Guid AlertId { get; init; }
1417
public AlertType? AlertType { get; }
1518
public required Guid AlertTypeId { get; init; }
@@ -137,3 +140,41 @@ public void Update(
137140
};
138141
}
139142
}
143+
144+
public static class AlertValidationExtensions
145+
{
146+
public static IRuleBuilderOptions<T, string?> AlertDetails<T>(
147+
this IRuleBuilder<T, string?> ruleBuilder,
148+
Func<int, string> maxLengthMessage)
149+
{
150+
return ruleBuilder
151+
.MaximumLength(Alert.DetailsMaxLength).WithMessage(maxLengthMessage(Alert.DetailsMaxLength));
152+
}
153+
154+
public static IRuleBuilderOptions<T, string?> AlertLink<T>(
155+
this IRuleBuilder<T, string?> ruleBuilder,
156+
string invalidUrlMessage)
157+
{
158+
return ruleBuilder
159+
.Must(link => TrsUriHelper.TryCreateWebsiteUri(link, out _)).WithMessage(invalidUrlMessage);
160+
}
161+
162+
public static IRuleBuilderOptions<T, DateOnly?> AlertStartDate<T>(
163+
this IRuleBuilder<T, DateOnly?> ruleBuilder,
164+
DateOnly today,
165+
string requiredMessage,
166+
string dateInFutureMessage)
167+
{
168+
return ruleBuilder
169+
.NotNull().WithMessage(requiredMessage)
170+
.LessThanOrEqualTo(today).WithMessage(dateInFutureMessage);
171+
}
172+
173+
public static IRuleBuilderOptions<T, Guid?> AlertType<T>(
174+
this IRuleBuilder<T, Guid?> ruleBuilder,
175+
string requiredMessage)
176+
{
177+
return ruleBuilder
178+
.NotNull().WithMessage(requiredMessage);
179+
}
180+
}

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/AddAlert/Details.cshtml.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.AspNetCore.Mvc.Filters;
44
using Microsoft.AspNetCore.Mvc.RazorPages;
5+
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
56
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
67

78
namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;
89

910
[Journey(JourneyNames.AddAlert), RequireJourneyInstance]
1011
public class DetailsModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadManager evidenceUploadManager) : PageModel
1112
{
13+
private static readonly InlineValidator<DetailsModel> _validator = new()
14+
{
15+
v => v.RuleFor(m => m.Details).AlertDetails(
16+
maxLengthMessage: maxLength => $"Details must be {maxLength} characters or less")
17+
};
18+
1219
public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }
1320

1421
[FromQuery]
@@ -23,7 +30,7 @@ public class DetailsModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadMa
2330

2431
[BindProperty]
2532
[Display(Description = "For example, include any restrictions it places on a teacher.")]
26-
[MaxLength(UiDefaults.DetailMaxCharacterCount, ErrorMessage = $"Details {UiDefaults.DetailMaxCharacterCountErrorMessage}")]
33+
[MaxLength(FileUploadDefaults.DetailMaxCharacterCount, ErrorMessage = $"Details {FileUploadDefaults.DetailMaxCharacterCountErrorMessage}")]
2734
public string? Details { get; set; }
2835

2936
public void OnGet()
@@ -33,10 +40,7 @@ public void OnGet()
3340

3441
public async Task<IActionResult> OnPostAsync()
3542
{
36-
if (!ModelState.IsValid)
37-
{
38-
return this.PageWithErrors();
39-
}
43+
await _validator.ValidateAndThrowAsync(this);
4044

4145
await JourneyInstance!.UpdateStateAsync(state =>
4246
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/AddAlert/Link.cshtml.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.AspNetCore.Mvc.Filters;
44
using Microsoft.AspNetCore.Mvc.RazorPages;
5+
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
56
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
67

78
namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;
89

910
[Journey(JourneyNames.AddAlert), RequireJourneyInstance]
1011
public class LinkModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadManager evidenceUploadManager) : PageModel
1112
{
13+
private static readonly InlineValidator<LinkModel> _validator = new()
14+
{
15+
v => v.RuleFor(m => m.AddLink).NotNull().WithMessage("Select yes if you want to add a link to a panel outcome"),
16+
v => v.RuleFor(m => m.Link).AlertLink("Enter a valid URL").When(m => m.AddLink == true)
17+
};
18+
1219
public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }
1320

1421
[FromQuery]
@@ -21,7 +28,6 @@ public class LinkModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadManag
2128

2229
[BindProperty]
2330
[Display(Name = "Do you want to add a link to a panel outcome?")]
24-
[Required(ErrorMessage = "Select yes if you want to add a link to a panel outcome")]
2531
public bool? AddLink { get; set; }
2632

2733
[BindProperty]
@@ -36,15 +42,7 @@ public void OnGet()
3642

3743
public async Task<IActionResult> OnPostAsync()
3844
{
39-
if (AddLink == true && !TrsUriHelper.TryCreateWebsiteUri(Link, out _))
40-
{
41-
ModelState.AddModelError(nameof(Link), "Enter a valid URL");
42-
}
43-
44-
if (!ModelState.IsValid)
45-
{
46-
return this.PageWithErrors();
47-
}
45+
await _validator.ValidateAndThrowAsync(this);
4846

4947
await JourneyInstance!.UpdateStateAsync(state =>
5048
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/AddAlert/Reason.cshtml.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;
99
[Journey(JourneyNames.AddAlert), RequireJourneyInstance]
1010
public class ReasonModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadManager evidenceUploadManager) : PageModel
1111
{
12+
private static readonly InlineValidator<ReasonModel> _validator = new()
13+
{
14+
v => v.RuleFor(m => m.AddReason)
15+
.NotNull().WithMessage("Select a reason"),
16+
v => v.RuleFor(m => m.HasAdditionalReasonDetail)
17+
.NotNull().WithMessage("Select yes if you want to add more information about why you’re adding this alert"),
18+
v => v.RuleFor(m => m.AddReasonDetail)
19+
.NotNull().WithMessage("Enter additional detail")
20+
.MaximumLength(4000).WithMessage("Additional detail must be 4000 characters or less")
21+
.When(m => m.HasAdditionalReasonDetail == true),
22+
v => v.RuleFor(m => m.UploadEvidence)
23+
.NotNull().WithMessage("Select yes if you want to upload evidence"),
24+
v => v.RuleFor(m => m.EvidenceFile)
25+
.NotNull().WithMessage("Select a file")
26+
.EvidenceFile()
27+
.When(m => m.UploadEvidence == true && m.EvidenceFileId is null)
28+
};
29+
1230
public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }
1331

1432
[FromQuery]
@@ -21,17 +39,15 @@ public class ReasonModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadMan
2139

2240
[BindProperty]
2341
[Display(Name = "Select a reason")]
24-
[Required(ErrorMessage = "Select a reason")]
2542
public AddAlertReasonOption? AddReason { get; set; }
2643

2744
[BindProperty]
2845
[Display(Name = "Do you want to add more information about why you’re adding this alert?")]
29-
[Required(ErrorMessage = "Select yes if you want to add more information about why you’re adding this alert")]
3046
public bool? HasAdditionalReasonDetail { get; set; }
3147

3248
[BindProperty]
3349
[Display(Name = "Add additional detail")]
34-
[MaxLength(UiDefaults.DetailMaxCharacterCount, ErrorMessage = $"Additional detail {UiDefaults.DetailMaxCharacterCountErrorMessage}")]
50+
[MaxLength(FileUploadDefaults.DetailMaxCharacterCount, ErrorMessage = $"Additional detail {FileUploadDefaults.DetailMaxCharacterCountErrorMessage}")]
3551
public string? AddReasonDetail { get; set; }
3652

3753
[BindProperty]
@@ -60,17 +76,8 @@ public void OnGet()
6076

6177
public async Task<IActionResult> OnPostAsync()
6278
{
63-
if (HasAdditionalReasonDetail == true && AddReasonDetail is null)
64-
{
65-
ModelState.AddModelError(nameof(AddReasonDetail), "Enter additional detail");
66-
}
67-
68-
await evidenceUploadManager.ValidateAndUploadAsync(Evidence, ModelState);
69-
70-
if (!ModelState.IsValid)
71-
{
72-
return this.PageWithErrors();
73-
}
79+
await _validator.ValidateAndThrowAsync(this);
80+
ModelState.AddModelError(nameof(EvidenceFile), "Select a file");
7481

7582
await JourneyInstance!.UpdateStateAsync(state =>
7683
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/AddAlert/StartDate.cshtml.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.AspNetCore.Mvc.Filters;
44
using Microsoft.AspNetCore.Mvc.RazorPages;
5+
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
56
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
67

78
namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;
89

910
[Journey(JourneyNames.AddAlert), RequireJourneyInstance]
1011
public class StartDateModel(SupportUiLinkGenerator linkGenerator, EvidenceUploadManager evidenceUploadManager, IClock clock) : PageModel
1112
{
13+
private readonly InlineValidator<StartDateModel> _validator = new()
14+
{
15+
v => v.RuleFor(m => m.StartDate).AlertStartDate(
16+
clock.Today,
17+
requiredMessage: "Enter a start date",
18+
dateInFutureMessage: "Start date cannot be in the future")
19+
};
20+
1221
public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }
1322

1423
[FromQuery]
@@ -21,7 +30,6 @@ public class StartDateModel(SupportUiLinkGenerator linkGenerator, EvidenceUpload
2130

2231
[BindProperty]
2332
[DateInput(ErrorMessagePrefix = "Start date")]
24-
[Required(ErrorMessage = "Enter a start date")]
2533
[Display(Name = "Enter start date")]
2634
public DateOnly? StartDate { get; set; }
2735

@@ -32,15 +40,7 @@ public void OnGet()
3240

3341
public async Task<IActionResult> OnPostAsync()
3442
{
35-
if (StartDate > clock.Today)
36-
{
37-
ModelState.AddModelError(nameof(StartDate), "Start date cannot be in the future");
38-
}
39-
40-
if (!ModelState.IsValid)
41-
{
42-
return this.PageWithErrors();
43-
}
43+
await _validator.ValidateAndThrowAsync(this);
4444

4545
await JourneyInstance!.UpdateStateAsync(state =>
4646
{

TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/AddAlert/Type.cshtml.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.AspNetCore.Mvc.Filters;
55
using Microsoft.AspNetCore.Mvc.RazorPages;
6+
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
67
using TeachingRecordSystem.SupportUi.Infrastructure.Security;
78
using TeachingRecordSystem.SupportUi.Infrastructure.Security.Requirements;
89
using TeachingRecordSystem.SupportUi.Pages.Shared.Evidence;
@@ -16,6 +17,11 @@ public class TypeModel(
1617
EvidenceUploadManager evidenceUploadManager,
1718
IAuthorizationService authorizationService) : PageModel
1819
{
20+
private static readonly InlineValidator<TypeModel> _validator = new()
21+
{
22+
v => v.RuleFor(m => m.AlertTypeId).AlertType(requiredMessage: "Select an alert type")
23+
};
24+
1925
public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }
2026

2127
[FromQuery]
@@ -28,7 +34,6 @@ public class TypeModel(
2834

2935
[BindProperty]
3036
[Display(Name = "Select an alert type")]
31-
[Required(ErrorMessage = "Select an alert type")]
3237
public Guid? AlertTypeId { get; set; }
3338

3439
public AlertCategoryInfo[]? Categories { get; set; }
@@ -42,10 +47,7 @@ public void OnGet()
4247

4348
public async Task<IActionResult> OnPostAsync()
4449
{
45-
if (!ModelState.IsValid)
46-
{
47-
return this.PageWithErrors();
48-
}
50+
await _validator.ValidateAndThrowAsync(this);
4951

5052
var selectedType = AlertTypes!.Single(t => t.AlertTypeId == AlertTypeId);
5153

0 commit comments

Comments
 (0)