Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support storing a DQT user in audit events #993

Merged
merged 1 commit into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace TeachingRecordSystem.Core.DataStore.Postgres.Migrations
{
/// <inheritdoc />
public partial class RenameEventSourceUserId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
update events set
payload = jsonb_set(payload, array['RaisedBy'], to_jsonb(payload->>'SourceUserId'::text), true) - 'SourceUserId'
where payload->>'RaisedBy' is null;
""");
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
update events set
payload = jsonb_set(payload, array['SourceUserId'], to_jsonb(payload->>'RaisedBy'::text), true) - 'RaisedBy'
where payload->>'SourceUserId' is null;
""");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using TeachingRecordSystem.Core.Events.Models;

namespace TeachingRecordSystem.Core.Events;

Expand All @@ -8,5 +9,5 @@ public abstract record EventBase

public required Guid EventId { get; init; }
public required DateTime CreatedUtc { get; init; }
public required Guid SourceUserId { get; init; }
public required RaisedByUserInfo RaisedBy { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace TeachingRecordSystem.Core.Events.Models;

/// <summary>
/// Represents the user who raised the event.
/// Contains either a TRS user ID or the DQT user ID and name.
/// </summary>
[JsonConverter(typeof(RaisedByUserInfoJsonConverter))]
public sealed class RaisedByUserInfo
{
private RaisedByUserInfo() { }

public Guid? UserId { get; private set; }

public Guid? DqtUserId { get; private set; }
public string? DqtUserName { get; private set; }

[MemberNotNullWhen(true, nameof(DqtUserId), nameof(DqtUserName))]
[MemberNotNullWhen(false, nameof(UserId))]
public bool IsDqtUser => DqtUserId.HasValue;

public static implicit operator RaisedByUserInfo(Guid userId) => FromUserId(userId);

public static RaisedByUserInfo FromUserId(Guid userId) => new()
{
UserId = userId
};

public static RaisedByUserInfo FromDqtUser(Guid dqtUserId, string dqtUserName) => new()
{
DqtUserId = dqtUserId,
DqtUserName = dqtUserName
};
}

public class RaisedByUserInfoJsonConverter : JsonConverter<RaisedByUserInfo>
{
public override RaisedByUserInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartObject)
{
var dqtUserInfo = JsonSerializer.Deserialize<DqtUserInfo>(ref reader, options)!;
return RaisedByUserInfo.FromDqtUser(dqtUserInfo.DqtUserId, dqtUserInfo.DqtUserName);
}
else
{
var userId = reader.GetGuid();
return RaisedByUserInfo.FromUserId(userId);
}
}

public override void Write(Utf8JsonWriter writer, RaisedByUserInfo value, JsonSerializerOptions options)
{
if (value.IsDqtUser)
{
var dqtUserInfo = new DqtUserInfo()
{
DqtUserId = value.DqtUserId.Value,
DqtUserName = value.DqtUserName
};
JsonSerializer.Serialize(writer, dqtUserInfo, options);
}
else
{
writer.WriteStringValue(value.UserId.ToString());
}
}

private sealed class DqtUserInfo
{
public required Guid DqtUserId { get; init; }
public required string DqtUserName { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TeachingRecordSystem.Core.Events;
namespace TeachingRecordSystem.Core.Events.Models;

public record User
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TeachingRecordSystem.Core.Events.Models;

namespace TeachingRecordSystem.Core.Events;

public record UserActivatedEvent : EventBase
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TeachingRecordSystem.Core.Events.Models;

namespace TeachingRecordSystem.Core.Events;

public record UserAddedEvent : EventBase
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TeachingRecordSystem.Core.Events.Models;

namespace TeachingRecordSystem.Core.Events;

public record UserDeactivatedEvent : EventBase
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using TeachingRecordSystem.Core.Events.Models;

namespace TeachingRecordSystem.Core.Events;

public record UserUpdatedEvent : EventBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task Execute(Guid eytsAwardedEmailsJobId, Guid personId)
PersonId = personId,
EmailAddress = item.EmailAddress,
CreatedUtc = _clock.UtcNow,
SourceUserId = DataStore.Postgres.Models.User.SystemUserId
RaisedBy = DataStore.Postgres.Models.User.SystemUserId
});

await _dbContext.SaveChangesAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task Execute(Guid inductionCompletedEmailsJobId, Guid personId)
PersonId = personId,
EmailAddress = item.EmailAddress,
CreatedUtc = _clock.UtcNow,
SourceUserId = DataStore.Postgres.Models.User.SystemUserId
RaisedBy = DataStore.Postgres.Models.User.SystemUserId
});

await _dbContext.SaveChangesAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task Execute(Guid internationalQtsAwardedEmailsJobId, Guid personId
PersonId = personId,
EmailAddress = item.EmailAddress,
CreatedUtc = _clock.UtcNow,
SourceUserId = DataStore.Postgres.Models.User.SystemUserId
RaisedBy = DataStore.Postgres.Models.User.SystemUserId
});

await _dbContext.SaveChangesAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task Execute(Guid qtsAwardedEmailsJobId, Guid personId)
PersonId = personId,
EmailAddress = item.EmailAddress,
CreatedUtc = _clock.UtcNow,
SourceUserId = DataStore.Postgres.Models.User.SystemUserId
RaisedBy = DataStore.Postgres.Models.User.SystemUserId
});

await _dbContext.SaveChangesAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public async Task<IActionResult> OnPost()
_dbContext.AddEvent(new UserAddedEvent()
{
EventId = Guid.NewGuid(),
User = Core.Events.User.FromModel(newUser),
SourceUserId = User.GetUserId(),
User = Core.Events.Models.User.FromModel(newUser),
RaisedBy = User.GetUserId(),
CreatedUtc = _clock.UtcNow
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public async Task<IActionResult> OnPost()
_dbContext.AddEvent(new UserUpdatedEvent
{
EventId = Guid.NewGuid(),
User = Core.Events.User.FromModel(user),
SourceUserId = User.GetUserId(),
User = Core.Events.Models.User.FromModel(user),
RaisedBy = User.GetUserId(),
CreatedUtc = _clock.UtcNow,
Changes = changes
});
Expand All @@ -130,8 +130,8 @@ public async Task<IActionResult> OnPostDeactivate()
_dbContext.AddEvent(new UserDeactivatedEvent
{
EventId = Guid.NewGuid(),
User = Core.Events.User.FromModel(user),
SourceUserId = User.GetUserId(),
User = Core.Events.Models.User.FromModel(user),
RaisedBy = User.GetUserId(),
CreatedUtc = _clock.UtcNow
});

Expand All @@ -155,8 +155,8 @@ public async Task<IActionResult> OnPostActivate()
_dbContext.AddEvent(new UserActivatedEvent
{
EventId = Guid.NewGuid(),
User = Core.Events.User.FromModel(user),
SourceUserId = User.GetUserId(),
User = Core.Events.Models.User.FromModel(user),
RaisedBy = User.GetUserId(),
CreatedUtc = _clock.UtcNow
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using TeachingRecordSystem.Core.Events;
using TeachingRecordSystem.Core.Events.Models;
using TeachingRecordSystem.Core.Models;

namespace TeachingRecordSystem.Core.Tests;
Expand All @@ -13,7 +14,7 @@ public void EventSerializesCorrectly()
{
EventId = Guid.NewGuid(),
CreatedUtc = DateTime.UtcNow,
SourceUserId = DataStore.Postgres.Models.User.SystemUserId,
RaisedBy = DataStore.Postgres.Models.User.SystemUserId,
User = new()
{
AzureAdUserId = "ad-user-id",
Expand All @@ -34,12 +35,44 @@ public void EventSerializesCorrectly()
// Assert
var roundTripped = Assert.IsType<EventInfo<UserActivatedEvent>>(deserialized);
Assert.Equal(e.CreatedUtc, roundTripped.Event.CreatedUtc);
Assert.Equal(e.SourceUserId, roundTripped.Event.SourceUserId);
Assert.Equal(e.RaisedBy.UserId, roundTripped.Event.RaisedBy.UserId);
Assert.Equal(e.User.AzureAdUserId, roundTripped.Event.User.AzureAdUserId);
Assert.Equal(e.User.Email, roundTripped.Event.User.Email);
Assert.Equal(e.User.Name, roundTripped.Event.User.Name);
Assert.Equal(e.User.Roles, roundTripped.Event.User.Roles);
Assert.Equal(e.User.UserId, roundTripped.Event.User.UserId);
Assert.Equal(e.User.UserType, roundTripped.Event.User.UserType);
}

[Fact]
public void EventWithDqtUserIdSerializesRaisedByCorrectly()
{
// Arrange
var @e = new UserActivatedEvent()
{
EventId = Guid.NewGuid(),
CreatedUtc = DateTime.UtcNow,
RaisedBy = RaisedByUserInfo.FromDqtUser(Guid.NewGuid(), "A DQT User"),
User = new()
{
AzureAdUserId = "ad-user-id",
Email = "[email protected]",
Name = "Test User",
Roles = ["Administrator"],
UserId = Guid.NewGuid(),
UserType = UserType.Person
}
};

var eventInfo = EventInfo.Create(@e);

// Act
var serialized = eventInfo.Serialize();
var deserialized = EventInfo.Deserialize(serialized);

// Assert
var roundTripped = Assert.IsType<EventInfo<UserActivatedEvent>>(deserialized);
Assert.Equal(e.RaisedBy.DqtUserId, roundTripped.Event.RaisedBy.DqtUserId);
Assert.Equal(e.RaisedBy.DqtUserName, roundTripped.Event.RaisedBy.DqtUserName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public async Task PublishEvents_EventObserverThrows_DoesNotThrow()
EventId = Guid.NewGuid(),
DummyProperty = Faker.Name.FullName(),
CreatedUtc = TestableClock.Initial.ToUniversalTime(),
SourceUserId = DataStore.Postgres.Models.User.SystemUserId
RaisedBy = User.SystemUserId
};

private class TestableEventObserver : IEventObserver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public async Task Post_ValidRequest_CreatesUserEmitsEventAndRedirectsWithFlashMe
{
var userCreatedEvent = Assert.IsType<UserAddedEvent>(e);
Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc);
Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId());
Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId());
Assert.Equal(UserType.Person, userCreatedEvent.User.UserType);
Assert.Equal(newName, userCreatedEvent.User.Name);
Assert.Equal(email, userCreatedEvent.User.Email);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public async Task Post_ValidRequest_CreatesUserEmitsEventAndRedirectsWithFlashMe
{
var userCreatedEvent = Assert.IsType<UserUpdatedEvent>(e);
Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc);
Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId());
Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId());
Assert.Equal(UserType.Person, userCreatedEvent.User.UserType);
Assert.Equal(newName, userCreatedEvent.User.Name);
Assert.Equal(updatedUser.Email, userCreatedEvent.User.Email);
Expand Down Expand Up @@ -247,7 +247,7 @@ public async Task Post_ValidRequest_DeactivatesUsersEmitsEventAndRedirectsWithFl
{
var userCreatedEvent = Assert.IsType<UserDeactivatedEvent>(e);
Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc);
Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId());
Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId());
Assert.Equal(UserType.Person, userCreatedEvent.User.UserType);
});

Expand Down Expand Up @@ -294,7 +294,7 @@ public async Task Post_ValidRequest_ActivatesUsersEmitsEventAndRedirectsWithFlas
{
var userCreatedEvent = Assert.IsType<UserActivatedEvent>(e);
Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc);
Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId());
Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId());
Assert.Equal(UserType.Person, userCreatedEvent.User.UserType);
});

Expand Down
Loading