Skip to content

Commit 53cf7f8

Browse files
authored
Use SupportTaskService to create change name/DOB tasks (#2878)
- Adds process types for change name/DOB requests. - Fixes `ICurrentUserProvider` to work with ID access token authentication scheme. - Updates `SendEmailJob` to publish an `EmailSentEvent` if a process ID is passed.
1 parent b3a8eb1 commit 53cf7f8

File tree

20 files changed

+219
-91
lines changed

20 files changed

+219
-91
lines changed

TeachingRecordSystem/src/TeachingRecordSystem.Api/Infrastructure/Security/ClaimsPrincipalCurrentUserProvider.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ public class ClaimsPrincipalCurrentUserProvider(IHttpContextAccessor httpContext
66
{
77
public static bool TryGetCurrentApplicationUserFromHttpContext(HttpContext httpContext, out (Guid UserId, string Name) user)
88
{
9-
var userIdStr = httpContext.User.FindFirstValue("sub");
10-
var name = httpContext.User.FindFirstValue(ClaimTypes.Name);
9+
var principal = httpContext.User;
10+
11+
// Look for ID access tokens and map those to the ID application user defined in configuration
12+
if (principal.HasClaim(c => c.Type == "trn") && principal.HasClaim(c => c.Type == "scope" && c.Value.Contains("dqt:read")))
13+
{
14+
var idApplicationUserId = httpContext.RequestServices.GetRequiredService<IConfiguration>().GetValue<Guid>("GetAnIdentityApplicationUserId");
15+
user = (idApplicationUserId, "Get an identity");
16+
return true;
17+
}
18+
19+
var userIdStr = principal.FindFirstValue("sub");
20+
var name = principal.FindFirstValue(ClaimTypes.Name);
1121

1222
if (userIdStr is null || !Guid.TryParse(userIdStr, out var userId) || name is null)
1323
{

TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/CreateDateOfBirthChangeRequest.cs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using Microsoft.AspNetCore.StaticFiles;
2+
using TeachingRecordSystem.Api.Infrastructure.Security;
23
using TeachingRecordSystem.Core.DataStore.Postgres;
34
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
45
using TeachingRecordSystem.Core.Jobs;
56
using TeachingRecordSystem.Core.Jobs.Scheduling;
67
using TeachingRecordSystem.Core.Models.SupportTasks;
78
using TeachingRecordSystem.Core.Services.Files;
9+
using TeachingRecordSystem.Core.Services.SupportTasks;
810

911
namespace TeachingRecordSystem.Api.V3.Implementation.Operations;
1012

@@ -20,12 +22,13 @@ public record CreateDateOfBirthChangeRequestCommand : ICommand<CreateDateOfBirth
2022
public record CreateDateOfBirthChangeRequestResult(string CaseNumber);
2123

2224
public class CreateDateOfBirthChangeRequestHandler(
23-
IConfiguration configuration,
2425
IBackgroundJobScheduler backgroundJobScheduler,
2526
IHttpClientFactory httpClientFactory,
2627
TrsDbContext dbContext,
28+
SupportTaskService supportTaskService,
2729
IFileService fileService,
28-
IClock clock) :
30+
IClock clock,
31+
ICurrentUserProvider currentUserProvider) :
2932
ICommandHandler<CreateDateOfBirthChangeRequestCommand, CreateDateOfBirthChangeRequestResult>
3033
{
3134
private readonly HttpClient _downloadEvidenceFileHttpClient = httpClientFactory.CreateClient("EvidenceFiles");
@@ -56,7 +59,7 @@ public async Task<ApiResult<CreateDateOfBirthChangeRequestResult>> ExecuteAsync(
5659
evidenceFileMimeType = "application/octet-stream";
5760
}
5861

59-
using var stream = await evidenceFileResponse.Content.ReadAsStreamAsync();
62+
await using var stream = await evidenceFileResponse.Content.ReadAsStreamAsync();
6063
var evidenceFileId = await fileService.UploadFileAsync(stream, evidenceFileMimeType);
6164

6265
var changeRequestData = new ChangeDateOfBirthRequestData()
@@ -68,23 +71,22 @@ public async Task<ApiResult<CreateDateOfBirthChangeRequestResult>> ExecuteAsync(
6871
ChangeRequestOutcome = null
6972
};
7073

71-
var getAnIdentityApplicationUserId = configuration.GetValue<Guid>("GetAnIdentityApplicationUserId");
74+
var (userId, _) = currentUserProvider.GetCurrentApplicationUser();
7275

73-
var supportTask = SupportTask.Create(
74-
SupportTaskType.ChangeDateOfBirthRequest,
75-
changeRequestData,
76-
person.PersonId,
77-
oneLoginUserSubject: null,
78-
trnRequestApplicationUserId: null,
79-
trnRequestId: null,
80-
createdBy: getAnIdentityApplicationUserId,
81-
clock.UtcNow,
82-
out var supportTaskCreatedEvent);
76+
var processContext = new ProcessContext(ProcessType.ChangeOfDateOfBirthRequestCreating, clock.UtcNow, userId);
8377

84-
dbContext.SupportTasks.Add(supportTask);
85-
await dbContext.AddEventAndBroadcastAsync(supportTaskCreatedEvent);
78+
var supportTask = await supportTaskService.CreateSupportTaskAsync(
79+
new CreateSupportTaskOptions
80+
{
81+
SupportTaskType = SupportTaskType.ChangeDateOfBirthRequest,
82+
Data = changeRequestData,
83+
PersonId = person.PersonId,
84+
OneLoginUserSubject = null,
85+
TrnRequest = null
86+
},
87+
processContext);
8688

87-
var emailAddress = string.IsNullOrEmpty(command.EmailAddress) ? person.EmailAddress : command.EmailAddress;
89+
var emailAddress = !string.IsNullOrEmpty(command.EmailAddress) ? command.EmailAddress : person.EmailAddress;
8890

8991
if (!string.IsNullOrEmpty(emailAddress))
9092
{
@@ -97,10 +99,11 @@ public async Task<ApiResult<CreateDateOfBirthChangeRequestResult>> ExecuteAsync(
9799
};
98100

99101
dbContext.Emails.Add(email);
100-
await backgroundJobScheduler.EnqueueAsync<SendEmailJob>(j => j.ExecuteAsync(email.EmailId));
101-
}
102102

103-
await dbContext.SaveChangesAsync();
103+
await dbContext.SaveChangesAsync();
104+
105+
await backgroundJobScheduler.EnqueueAsync<SendEmailJob>(j => j.ExecuteAsync(email.EmailId, processContext.ProcessId));
106+
}
104107

105108
return new CreateDateOfBirthChangeRequestResult(supportTask.SupportTaskReference);
106109
}

TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/CreateNameChangeRequest.cs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using Microsoft.AspNetCore.StaticFiles;
2+
using TeachingRecordSystem.Api.Infrastructure.Security;
23
using TeachingRecordSystem.Core.DataStore.Postgres;
34
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
45
using TeachingRecordSystem.Core.Jobs;
56
using TeachingRecordSystem.Core.Jobs.Scheduling;
67
using TeachingRecordSystem.Core.Models.SupportTasks;
78
using TeachingRecordSystem.Core.Services.Files;
9+
using TeachingRecordSystem.Core.Services.SupportTasks;
810

911
namespace TeachingRecordSystem.Api.V3.Implementation.Operations;
1012

@@ -22,12 +24,13 @@ public record CreateNameChangeRequestCommand : ICommand<CreateNameChangeRequestR
2224
public record CreateNameChangeRequestResult(string CaseNumber);
2325

2426
public class CreateNameChangeRequestHandler(
25-
IConfiguration configuration,
2627
IBackgroundJobScheduler backgroundJobScheduler,
2728
IHttpClientFactory httpClientFactory,
2829
TrsDbContext dbContext,
30+
SupportTaskService supportTaskService,
2931
IFileService fileService,
30-
IClock clock) :
32+
IClock clock,
33+
ICurrentUserProvider currentUserProvider) :
3134
ICommandHandler<CreateNameChangeRequestCommand, CreateNameChangeRequestResult>
3235
{
3336
private readonly HttpClient _downloadEvidenceFileHttpClient = httpClientFactory.CreateClient("EvidenceFiles");
@@ -58,7 +61,7 @@ public async Task<ApiResult<CreateNameChangeRequestResult>> ExecuteAsync(CreateN
5861
evidenceFileMimeType = "application/octet-stream";
5962
}
6063

61-
using var stream = await evidenceFileResponse.Content.ReadAsStreamAsync();
64+
await using var stream = await evidenceFileResponse.Content.ReadAsStreamAsync();
6265
var evidenceFileId = await fileService.UploadFileAsync(stream, evidenceFileMimeType);
6366

6467
var changeRequestData = new ChangeNameRequestData()
@@ -72,23 +75,22 @@ public async Task<ApiResult<CreateNameChangeRequestResult>> ExecuteAsync(CreateN
7275
ChangeRequestOutcome = null
7376
};
7477

75-
var getAnIdentityApplicationUserId = configuration.GetValue<Guid>("GetAnIdentityApplicationUserId");
78+
var (userId, _) = currentUserProvider.GetCurrentApplicationUser();
7679

77-
var supportTask = SupportTask.Create(
78-
SupportTaskType.ChangeNameRequest,
79-
changeRequestData,
80-
person.PersonId,
81-
oneLoginUserSubject: null,
82-
trnRequestApplicationUserId: null,
83-
trnRequestId: null,
84-
createdBy: getAnIdentityApplicationUserId,
85-
clock.UtcNow,
86-
out var supportTaskCreatedEvent);
80+
var processContext = new ProcessContext(ProcessType.ChangeOfNameRequestCreating, clock.UtcNow, userId);
8781

88-
dbContext.SupportTasks.Add(supportTask);
89-
await dbContext.AddEventAndBroadcastAsync(supportTaskCreatedEvent);
82+
var supportTask = await supportTaskService.CreateSupportTaskAsync(
83+
new CreateSupportTaskOptions
84+
{
85+
SupportTaskType = SupportTaskType.ChangeNameRequest,
86+
Data = changeRequestData,
87+
PersonId = person.PersonId,
88+
OneLoginUserSubject = null,
89+
TrnRequest = null
90+
},
91+
processContext);
9092

91-
var emailAddress = string.IsNullOrEmpty(command.EmailAddress) ? person.EmailAddress : command.EmailAddress;
93+
var emailAddress = !string.IsNullOrEmpty(command.EmailAddress) ? command.EmailAddress : person.EmailAddress;
9294

9395
if (!string.IsNullOrEmpty(emailAddress))
9496
{
@@ -101,7 +103,10 @@ public async Task<ApiResult<CreateNameChangeRequestResult>> ExecuteAsync(CreateN
101103
};
102104

103105
dbContext.Emails.Add(email);
104-
await backgroundJobScheduler.EnqueueAsync<SendEmailJob>(j => j.ExecuteAsync(email.EmailId));
106+
107+
await dbContext.SaveChangesAsync();
108+
109+
await backgroundJobScheduler.EnqueueAsync<SendEmailJob>(j => j.ExecuteAsync(email.EmailId, processContext.ProcessId));
105110
}
106111

107112
await dbContext.SaveChangesAsync();

TeachingRecordSystem/src/TeachingRecordSystem.Core/EventPublisher.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,19 @@ public ProcessContext(ProcessType processType, DateTime now, EventModels.RaisedB
8686
};
8787
}
8888

89+
public ProcessContext(Process process, DateTime now)
90+
{
91+
Now = now;
92+
Process = process;
93+
}
94+
8995
public DateTime Now { get; }
9096

97+
public IReadOnlyCollection<Guid> PersonIds => Process.PersonIds;
98+
9199
public Process Process { get; }
92100

101+
public Guid ProcessId => Process.ProcessId;
102+
93103
public ProcessType ProcessType => Process.ProcessType;
94104
}

TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendAytqInviteEmailJob.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ namespace TeachingRecordSystem.Core.Jobs;
99
public class SendAytqInviteEmailJob(
1010
INotificationSender notificationSender,
1111
TrsDbContext dbContext,
12+
IEventPublisher eventPublisher,
1213
IGetAnIdentityApiClient identityApiClient,
1314
IOptions<AccessYourTeachingQualificationsOptions> aytqOptions,
1415
IClock clock) :
15-
SendEmailJob(dbContext, notificationSender, clock)
16+
SendEmailJob(dbContext, eventPublisher, notificationSender, clock)
1617
{
1718
private const string MagicLinkPersonalizationKey = "link to access your teaching qualifications service";
1819

TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendEmailJob.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,48 @@
1+
using System.Transactions;
12
using TeachingRecordSystem.Core.DataStore.Postgres;
23
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
34
using TeachingRecordSystem.Core.Services.Notify;
45

56
namespace TeachingRecordSystem.Core.Jobs;
67

7-
public class SendEmailJob(TrsDbContext dbContext, INotificationSender notificationSender, IClock clock)
8+
public class SendEmailJob(TrsDbContext dbContext, IEventPublisher eventPublisher, INotificationSender notificationSender, IClock clock)
89
{
910
protected TrsDbContext DbContext => dbContext;
1011

12+
protected IEventPublisher EventPublisher => eventPublisher;
13+
1114
protected INotificationSender NotificationSender => notificationSender;
1215

1316
protected IClock Clock => clock;
1417

1518
public virtual Task ExecuteAsync(Guid emailId) => SendEmailAsync(emailId);
1619

20+
public virtual async Task ExecuteAsync(Guid emailId, Guid processId)
21+
{
22+
using var txn = new TransactionScope(
23+
TransactionScopeOption.Required,
24+
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
25+
TransactionScopeAsyncFlowOption.Enabled);
26+
27+
var process = await dbContext.Processes.SingleAsync(p => p.ProcessId == processId);
28+
var processContext = new ProcessContext(process, clock.UtcNow);
29+
30+
var personId = processContext.PersonIds.Single();
31+
32+
var email = await GetEmailByIdAsync(emailId);
33+
await SendEmailAsync(email);
34+
35+
await eventPublisher.PublishEventAsync(
36+
new EmailSentEvent
37+
{
38+
PersonId = personId,
39+
Email = EventModels.Email.FromModel(email)
40+
},
41+
processContext);
42+
43+
txn.Complete();
44+
}
45+
1746
protected Task<Email> GetEmailByIdAsync(Guid emailId) =>
1847
dbContext.Emails.SingleAsync(e => e.EmailId == emailId);
1948

TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendTrnRecipientEmailJob.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace TeachingRecordSystem.Core.Jobs;
77

88
public class SendTrnRecipientEmailJob(TrsDbContext dbContext, IEventPublisher eventPublisher, INotificationSender notificationSender, IClock clock) :
9-
SendEmailJob(dbContext, notificationSender, clock)
9+
SendEmailJob(dbContext, eventPublisher, notificationSender, clock)
1010
{
1111
public override async Task ExecuteAsync(Guid emailId)
1212
{
@@ -21,7 +21,7 @@ public override async Task ExecuteAsync(Guid emailId)
2121

2222
var processContext = new ProcessContext(ProcessType.NotifyingTrnRecipient, email.SentOn!.Value, SystemUser.SystemUserId);
2323

24-
await eventPublisher.PublishEventAsync(
24+
await EventPublisher.PublishEventAsync(
2525
new EmailSentEvent
2626
{
2727
PersonId = personId,

TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/ProcessType.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ public enum ProcessType
1212
SupportTaskDeleting = 8,
1313
TrnRequestResetting = 9,
1414
NotifyingTrnRecipient = 10,
15-
NoteCreating = 11
15+
NoteCreating = 11,
16+
ChangeOfDateOfBirthRequestCreating = 12,
17+
ChangeOfNameRequestCreating = 13,
1618
}

TeachingRecordSystem/tests/TeachingRecordSystem.Api.IntegrationTests/HostFixture.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public override async ValueTask InitializeAsync()
7272
{
7373
await InitializeDbAsync();
7474

75+
using var dbContext = DbHelper.Instance.DbContextFactory.CreateDbContext();
76+
AddApplicationUsers(dbContext);
77+
7578
_ = Services; // Start the server
7679
}
7780

TeachingRecordSystem/tests/TeachingRecordSystem.Api.IntegrationTests/TestBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected TestBase(HostFixture hostFixture)
3838
_testServices = TestScopedServices.Reset(hostFixture.Services);
3939
SetCurrentApiClient([]);
4040
}
41+
4142
protected HostFixture HostFixture { get; }
4243

4344
protected IDbContextFactory<TrsDbContext> DbContextFactory => HostFixture.Services.GetRequiredService<IDbContextFactory<TrsDbContext>>();

0 commit comments

Comments
 (0)