Skip to content

Commit

Permalink
Merge pull request #292 from rGunti/262-feature-activity-log
Browse files Browse the repository at this point in the history
Implemented API for Activity Log
  • Loading branch information
rGunti authored Jun 21, 2024
2 parents bc32b68 + b88839e commit aad520d
Show file tree
Hide file tree
Showing 44 changed files with 1,514 additions and 71 deletions.
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

0 comments on commit aad520d

Please sign in to comment.