Skip to content

Commit

Permalink
feat: add additional audit configuration methods
Browse files Browse the repository at this point in the history
  • Loading branch information
ascott18 committed Oct 15, 2024
1 parent 1948a7c commit a65102d
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- feat: All Coalesce-generated endpoints that accept a formdata body now also accept a JSON body. Existing formdata endpoints remain unchanged.
- feat: Automatically produce user-friendly response messages in behaviors for Save and Delete operations that fail due to a violation of a SQL Server foreign key or unique constraint. This behavior can be controlled with the `DetailedEfConstraintExceptionMessages` setting in `.AddCoalesce(c => c.Configure(o => { ... }))`, or by overriding `StandardBehaviors.GetExceptionResult`. This is not a substitute for adding proper validation or other handling of related entities - it only exists to provide a better user experience in cases where the developer has forgotten to handle these situations. This behavior does respect Coalesce's security model and won't produce descriptions of types or values that the user is not allowed to see. (#468)
- feat: Error responses now include inner exception messages when `DetailedExceptionMessages` is enabled. (#468)
- feat: Add additional audit configuration methods to allow for allow-listing certain properties.
- feat(c-admin-table): Clicking a row takes you to the details page (#465)
- feat(c-admin-table): Always show button for details page (#465)

Expand Down
19 changes: 19 additions & 0 deletions src/IntelliTect.Coalesce.AuditLogging.Tests/AuditTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,25 @@ public async Task PropertyDesc_OnlyLoadsPrincipalWhenChanged()
Assert.Null(user.Parent1);
}

[Fact]
public async Task PropertyDesc_DoesntBreakForOneToOneWhenPkIsFk()
{
// Arrange
using var db = BuildDbContext(b => b
.UseCoalesceAuditLogging<TestAuditLog>(x => x
.WithAugmentation<TestOperationContext>()
));

// Act
var entity = new OneToOneParent { Name = "bob" };
db.Add(entity);
await db.SaveChangesAsync();


// Assert
var log = Assert.Single(db.AuditLogs.Include(l => l.Properties));
}

[Fact]
public void FormatsPrimitiveCollections()
{
Expand Down
28 changes: 28 additions & 0 deletions src/IntelliTect.Coalesce.AuditLogging.Tests/TestDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using IntelliTect.Coalesce.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace IntelliTect.Coalesce.AuditLogging.Tests;

Expand All @@ -16,6 +18,9 @@ public TestDbContext(DbContextOptions options) : base(options)
public DbSet<TestAuditLog> AuditLogs => Set<TestAuditLog>();
public DbSet<AuditLogProperty> AuditLogProperties => Set<AuditLogProperty>();

public DbSet<OneToOneParent> OneToOneParent => Set<OneToOneParent>();
public DbSet<OneToOneChild> OneToOneChild => Set<OneToOneChild>();

public bool SuppressAudit { get; set; }
}

Expand Down Expand Up @@ -56,6 +61,29 @@ class ParentWithUnMappedListText
public string CustomListTextField => "Name:" + Name;
}

class OneToOneParent
{
[Key]
public int ParentId { get; set; }

public string? Name { get; set; }

[InverseProperty("Parent")]
public OneToOneChild? Child { get; set; }
}

class OneToOneChild
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ParentId { get; set; }

public string? Name { get; set; }

[ForeignKey(nameof(ParentId))]
[InverseProperty("Child")]
public OneToOneParent? Parent { get; set; }
}

internal class TestAuditLog : DefaultAuditLog
{
public string? UserId { get; set; }
Expand Down
55 changes: 51 additions & 4 deletions src/IntelliTect.Coalesce.AuditLogging/Models/AuditConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,59 @@ public AuditConfiguration IncludeAddedDefaultValues(bool include = true)
return this;
}

public AuditConfiguration Include<T>()
/// <summary>
/// Exclude all entity types when auditing.
/// Types can subsequently be re-included by calling `Include`.
/// </summary>
public AuditConfiguration Exclude()
{
EntityPredicates.Add((x) => (x.Entity is T) ? true : null);
EntityPredicates.Add((x) => false);
return this;
}

/// <summary>
/// Exclude the specified entity type when auditing.
/// </summary>
public AuditConfiguration Exclude<T>()
{
EntityPredicates.Add((x) => (x.Entity is T) ? false : null);
return this;
}

/// <summary>
/// Exclude entities matching the predicate when auditing.
/// </summary>
public AuditConfiguration Exclude(Func<EntityEntry, bool> predicate)
{
EntityPredicates.Add((x) => predicate(x) ? false : null);
return this;
}

/// <summary>
/// Include the specified entity type when auditing.
/// </summary>
public AuditConfiguration Include<T>()
{
EntityPredicates.Add((x) => (x.Entity is T) ? true : null);
return this;
}

/// <summary>
/// Include entities matching the predicate when auditing.
/// </summary>
public AuditConfiguration Include(Func<EntityEntry, bool> predicate)
{
EntityPredicates.Add((x) => predicate(x) ? true : null);
return this;
}

public AuditConfiguration Exclude(Func<EntityEntry, bool> predicate)
/// <summary>
/// Exclude all properties when auditing.
/// Properties can subsequently be re-included by calling `IncludeProperty`.
/// </summary>
public AuditConfiguration ExcludeProperty()
{
EntityPredicates.Add((x) => predicate(x) ? false : null);
PropertyPredicates.Add((e) => false);
return this;
}

Expand All @@ -80,6 +112,21 @@ public AuditConfiguration ExcludeProperty<T>(params string[] propNames)
return this;
}

public AuditConfiguration IncludeProperty<T>(Expression<Func<T, object?>> propertySelector)
{
var props = propertySelector.GetExpressedProperties<T>().Select(p => p.Name).ToArray();
return IncludeProperty<T>(props);
}

public AuditConfiguration IncludeProperty<T>(params string[] propNames)
{
var props = propNames.ToHashSet();

PropertyPredicates.Add((e) => e.EntityEntry.Entity is T && props.Contains(e.Metadata.Name) ? true : null);

return this;
}

public AuditConfiguration Format<T>(Expression<Func<T, object?>> propertySelector, Func<object, string?> formatter) where T : class
{
var props = propertySelector.GetExpressedProperties<T>().Select(p => p.Name).ToArray();
Expand Down
6 changes: 3 additions & 3 deletions src/coalesce-vue/test/api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1153,12 +1153,12 @@ describe("$makeCaller with args object", () => {

const caller = new StudentApiClient().$makeCaller(
"item",
(c) => c.getFile(42, "bob"),
(c) => c.getFile(42, null),
() => ({}),
(c, args) => c.getFile(42, "bob")
(c, args) => c.getFile(42, "bob+/")
);

expect(caller.url).toBe("/api/Students/getFile?id=42&etag=bob");
expect(caller.url).toBe("/api/Students/getFile?id=42&etag=bob%2B%2F");
expect(adapter).toBeCalledTimes(0);
});

Expand Down

0 comments on commit a65102d

Please sign in to comment.