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

Implemented API for Activity Log #292

Merged
merged 14 commits into from
Jun 21, 2024
113 changes: 113 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/AuditorExtenstions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Diagnostics;
using FloppyBot.Base.Auditing.Abstraction.Entities;
using FloppyBot.Chat.Entities.Identifiers;

namespace FloppyBot.Base.Auditing.Abstraction;

[StackTraceHidden]
public static class AuditorExtensions
{
/// <summary>
/// Records a new event
/// </summary>
/// <seealso cref="IAuditor.Record"/>
/// <param name="auditor">The auditor to use</param>
/// <param name="userIdentifier">The identifier of the user taking the action</param>
/// <param name="channelIdentifier">The channel that this action was taken on</param>
/// <param name="objectType">The type of the object subjected to change</param>
/// <param name="objectIdentifier">An identifier for the object</param>
/// <param name="action">The action performed on the object</param>
/// <param name="additionalData">Additional context data</param>
/// <param name="timestamp">Optional timestamp</param>
public static void Record(
this IAuditor auditor,
string userIdentifier,
string channelIdentifier,
string objectType,
string objectIdentifier,
string action,
string? additionalData = null,
DateTimeOffset? timestamp = null
)
{
auditor.Record(
new AuditRecord(
// The ID is set to null here, but it will be set by the storage layer
Id: null!,
Timestamp: timestamp ?? DateTimeOffset.MinValue,
UserIdentifier: userIdentifier,
ChannelIdentifier: channelIdentifier,
ObjectType: objectType,
ObjectIdentifier: objectIdentifier,
Action: action,
AdditionalData: additionalData
)
);
}

/// <summary>
/// Records a new event
/// </summary>
/// <param name="auditor">The auditor to use</param>
/// <param name="user">The identifier of the user taking the action</param>
/// <param name="channel">The channel that this action was taken on</param>
/// <param name="objectType">The type of the object subjected to change</param>
/// <param name="objectIdentifier">An identifier for the object</param>
/// <param name="action">The action performed on the object</param>
/// <param name="additionalData">Additional context data</param>
/// <param name="timestamp">Optional timestamp</param>
public static void Record(
this IAuditor auditor,
ChannelIdentifier user,
ChannelIdentifier channel,
string objectType,
string objectIdentifier,
string action,
string? additionalData = null,
DateTimeOffset? timestamp = null
)
{
auditor.Record(
user.ToString(),
channel.ToString(),
objectType,
objectIdentifier,
action,
additionalData,
timestamp
);
}

/// <summary>
/// Records a new event
/// </summary>
/// <param name="auditor">The auditor to use</param>
/// <param name="user">The identifier of the user taking the action</param>
/// <param name="channel">The channel that this action was taken on</param>
/// <param name="object">The object affected</param>
/// <param name="objectIdentifier">A function that returns the identifier of the object</param>
/// <param name="action">The action performed on the object</param>
/// <param name="additionalData">Additional context data</param>
/// <param name="timestamp">Optional timestamp</param>
public static void Record<T>(
this IAuditor auditor,
ChannelIdentifier user,
ChannelIdentifier channel,
T @object,
Func<T, string> objectIdentifier,
string action,
string? additionalData = null,
DateTimeOffset? timestamp = null
)
{
auditor.Record(
user.ToString(),
channel.ToString(),
typeof(T).Name,
objectIdentifier(@object),
action,
additionalData,
timestamp
);
}
}
10 changes: 10 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/CommonActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FloppyBot.Base.Auditing.Abstraction;

public static class CommonActions
{
public const string Created = "Created";
public const string Updated = "Updated";
public const string Deleted = "Deleted";
public const string Disabled = "Disabled";
public const string Enabled = "Enabled";
}
12 changes: 12 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/Entities/AuditRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FloppyBot.Base.Auditing.Abstraction.Entities;

public record AuditRecord(
string Id,
DateTimeOffset Timestamp,
string UserIdentifier,
string ChannelIdentifier,
string ObjectType,
string ObjectIdentifier,
string Action,
string? AdditionalData
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\FloppyBot.Chat\FloppyBot.Chat.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/IAuditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FloppyBot.Base.Auditing.Abstraction.Entities;

namespace FloppyBot.Base.Auditing.Abstraction;

/// <summary>
/// A service that records events for auditing purposes
/// </summary>
public interface IAuditor
{
/// <summary>
/// Records a new event
/// </summary>
/// <param name="auditRecord"></param>
void Record(AuditRecord auditRecord);

/// <summary>
/// Get a list of audit records for the specified channels
/// </summary>
/// <param name="channels">Channels to query from</param>
/// <returns></returns>
IEnumerable<AuditRecord> GetAuditRecords(params string[] channels);
}
18 changes: 18 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/Impl/NoopAuditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FloppyBot.Base.Auditing.Abstraction.Entities;

namespace FloppyBot.Base.Auditing.Abstraction.Impl;

/// <summary>
/// A no-op auditor that does nothing, useful for testing
/// </summary>
public class NoopAuditor : IAuditor
{
/// <inheritdoc />
public void Record(AuditRecord auditRecord) { }

/// <inheritdoc />
public IEnumerable<AuditRecord> GetAuditRecords(params string[] channels)
{
return [];
}
}
12 changes: 12 additions & 0 deletions src/FloppyBot.Base.Auditing.Abstraction/Registration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Extensions.DependencyInjection;

namespace FloppyBot.Base.Auditing.Abstraction;

public static class Registration
{
public static IServiceCollection AddAuditor<T>(this IServiceCollection services)
where T : class, IAuditor
{
return services.AddScoped<IAuditor, T>();
}
}
47 changes: 47 additions & 0 deletions src/FloppyBot.Base.Auditing.Storage/AuditorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Diagnostics;
using FloppyBot.Base.Auditing.Abstraction;
using FloppyBot.Base.Storage;
using FloppyBot.Chat.Entities.Identifiers;

namespace FloppyBot.Base.Auditing.Storage;

public static class AuditorExtensions
{
/// <summary>
/// Records a new event
/// </summary>
/// <param name="auditor">The auditor to use</param>
/// <param name="user">The identifier of the user taking the action</param>
/// <param name="channel">The channel that this action was taken on</param>
/// <param name="object">The object affected</param>
/// <param name="action">The action performed on the object</param>
/// <param name="additionalData">Additional context data</param>
/// <param name="timestamp">Optional timestamp</param>
[StackTraceHidden]
public static void Record<T>(
this IAuditor auditor,
ChannelIdentifier user,
ChannelIdentifier channel,
T @object,
string action,
string? additionalData = null,
DateTimeOffset? timestamp = null
)
where T : IEntity<T>
{
auditor.Record(
user,
channel,
@object,
GetObjectIdentifier,
action,
additionalData ?? @object.ToString()
);
}

private static string GetObjectIdentifier<T>(T @object)
where T : IEntity<T>
{
return @object.Id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\FloppyBot.Base.Auditing.Abstraction\FloppyBot.Base.Auditing.Abstraction.csproj" />
<ProjectReference Include="..\FloppyBot.Base.Clock\FloppyBot.Base.Clock.csproj" />
<ProjectReference Include="..\FloppyBot.Base.Storage\FloppyBot.Base.Storage.csproj" />
</ItemGroup>

</Project>
49 changes: 49 additions & 0 deletions src/FloppyBot.Base.Auditing.Storage/InternalAuditRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using FloppyBot.Base.Auditing.Abstraction.Entities;
using FloppyBot.Base.Storage;

namespace FloppyBot.Base.Auditing.Storage;

internal record InternalAuditRecord(
string Id,
DateTimeOffset Timestamp,
string UserIdentifier,
string ChannelIdentifier,
string ObjectType,
string ObjectIdentifier,
string Action,
string? AdditionalData
) : IEntity<InternalAuditRecord>
{
public InternalAuditRecord WithId(string newId)
{
return this with { Id = newId };
}

public AuditRecord ToAuditRecord()
{
return new AuditRecord(
Id,
Timestamp,
UserIdentifier,
ChannelIdentifier,
ObjectType,
ObjectIdentifier,
Action,
AdditionalData
);
}

public static InternalAuditRecord FromAuditRecord(AuditRecord auditRecord)
{
return new InternalAuditRecord(
auditRecord.Id,
auditRecord.Timestamp,
auditRecord.UserIdentifier,
auditRecord.ChannelIdentifier,
auditRecord.ObjectType,
auditRecord.ObjectIdentifier,
auditRecord.Action,
auditRecord.AdditionalData
);
}
}
43 changes: 43 additions & 0 deletions src/FloppyBot.Base.Auditing.Storage/StorageAuditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FloppyBot.Base.Auditing.Abstraction;
using FloppyBot.Base.Auditing.Abstraction.Entities;
using FloppyBot.Base.Clock;
using FloppyBot.Base.Storage;

namespace FloppyBot.Base.Auditing.Storage;

/// <summary>
/// Implementation of <see cref="IAuditor"/> that stores audit records in a repository.
/// </summary>
public class StorageAuditor : IAuditor
{
private readonly IRepository<InternalAuditRecord> _repository;
private readonly ITimeProvider _timeProvider;

public StorageAuditor(IRepositoryFactory repositoryFactory, ITimeProvider timeProvider)
{
_timeProvider = timeProvider;
_repository = repositoryFactory.GetRepository<InternalAuditRecord>("AuditRecord");
}

/// <inheritdoc />
public void Record(AuditRecord auditRecord)
{
_repository.Insert(
InternalAuditRecord.FromAuditRecord(auditRecord) with
{
Timestamp = _timeProvider.GetCurrentUtcTime(),
}
);
}

/// <inheritdoc />
public IEnumerable<AuditRecord> GetAuditRecords(params string[] channels)
{
return _repository
.GetAll()
.Where(c => channels.Contains(c.ChannelIdentifier))
.ToList()
.Select(i => i.ToAuditRecord())
.OrderByDescending(i => i.Timestamp);
}
}
Loading
Loading