Skip to content
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
18 changes: 4 additions & 14 deletions src/Cluckwork.Api/Endpoints/Audit/AuditEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Cluckwork.Application.Features.Audit;
using Cluckwork.Domain.Auditing;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Infrastructure.Persistence;

namespace Cluckwork.Api.Endpoints.Audit;
Expand All @@ -21,7 +20,7 @@ public static RouteGroupBuilder MapAuditEndpoints(this RouteGroupBuilder group)
}

private static async Task<IResult> ListAuditEvents(
IAuditEventRepository events,
IInsightsModule events,
TenantContext tenant,
CancellationToken ct,
string? action = null,
Expand All @@ -37,17 +36,8 @@ private static async Task<IResult> ListAuditEvents(
var take = Math.Clamp(limit ?? DefaultPageSize, 1, MaxPageSize);
var skip = Math.Max(offset ?? 0, 0);

var list = await events.ListAsync(action, entityType,
var list = await events.ListAuditEventsAsync(action, entityType,
entityId, from, to, take, skip, ct);
return Results.Ok(list.Select(ToResponse));
return Results.Ok(list);
}

private static AuditEventResponse ToResponse(AuditEvent e) =>
new(e.Id, e.OccurredAtUtc, e.ActorEmail, e.Action, e.EntityType, e.EntityId,
e.Reason, e.DetailsJson);
}

public sealed record AuditEventResponse(
Guid Id, DateTimeOffset OccurredAtUtc, string ActorEmail,
string Action, string EntityType, Guid EntityId,
string? Reason, string? DetailsJson);
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static RouteGroupBuilder MapDailyEntryEndpoints(this RouteGroupBuilder gr
private static async Task<IResult> GetDailyEntry(
Guid id,
Cluckwork.Application.Features.DailyEntries.IDailyEntryRepository entries,
Cluckwork.Application.Features.Audit.IAuditEventRepository audit,
Cluckwork.Application.Features.Insights.IInsightsModule audit,
Cluckwork.Application.Features.Flocks.IFlockRepository flocks,
TenantContext tenant,
CancellationToken ct)
Expand All @@ -75,7 +75,7 @@ private static async Task<IResult> GetDailyEntry(

private static async Task<IResult> ListDailyEntries(
Cluckwork.Application.Features.DailyEntries.IDailyEntryRepository entries,
Cluckwork.Application.Features.Audit.IAuditEventRepository audit,
Cluckwork.Application.Features.Insights.IInsightsModule audit,
Cluckwork.Application.Features.Flocks.IFlockRepository flocks,
TenantContext tenant,
CancellationToken ct,
Expand Down
5 changes: 3 additions & 2 deletions src/Cluckwork.Api/Endpoints/EggGrades/EggGradeEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Cluckwork.Application.Features.EggGrades.CreateEggGrade;
using Cluckwork.Application.Features.EggGrades.SetEggGradeActive;
using Cluckwork.Application.Features.EggGrades.UpdateEggGrade;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Domain.Eggs;
using Cluckwork.Infrastructure.Persistence;
using FluentValidation;
Expand Down Expand Up @@ -48,7 +49,7 @@ public static RouteGroupBuilder MapEggGradeEndpoints(this RouteGroupBuilder grou
}

private static async Task<IResult> ListEggGrades(
IEggGradeRepository grades, IAuditEventRepository audit,
IEggGradeRepository grades, IInsightsModule audit,
TenantContext tenant, CancellationToken ct,
bool includeInactive = false)
{
Expand All @@ -64,7 +65,7 @@ private static async Task<IResult> ListEggGrades(
}

private static async Task<IResult> GetEggGrade(
Guid id, IEggGradeRepository grades, IAuditEventRepository audit,
Guid id, IEggGradeRepository grades, IInsightsModule audit,
TenantContext tenant, CancellationToken ct)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand Down
7 changes: 4 additions & 3 deletions src/Cluckwork.Api/Endpoints/Expenses/ExpenseEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Cluckwork.Application.Features.Expenses.CreateExpense;
using Cluckwork.Application.Features.Expenses.CreateExpenseCategory;
using Cluckwork.Application.Features.Expenses.UpdateExpenseCategory;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Infrastructure.Persistence;
using FluentValidation;

Expand Down Expand Up @@ -113,7 +114,7 @@ private static async Task<IResult> ListExpenses(
IFinanceModule finance,
Cluckwork.Application.Features.Flocks.IFlockRepository flocks,
IAccountRepository accounts,
IAuditEventRepository audit,
IInsightsModule audit,
TenantContext tenant,
CancellationToken ct,
DateOnly? from = null,
Expand Down Expand Up @@ -153,7 +154,7 @@ private static async Task<IResult> ListExpenses(
private static async Task<IResult> GetExpense(
Guid id, IFinanceModule finance,
Cluckwork.Application.Features.Flocks.IFlockRepository flocks,
IAuditEventRepository audit, TenantContext tenant, CancellationToken ct)
IInsightsModule audit, TenantContext tenant, CancellationToken ct)
{
if (!tenant.IsResolved) return Results.Unauthorized();
var expense = await finance.GetExpenseAsync(id, ct);
Expand Down Expand Up @@ -192,7 +193,7 @@ private static async Task<IResult> AdjustExpense(
IFinanceModule finance,
IValidator<AdjustExpenseCommand> validator,
Cluckwork.Application.Features.Flocks.IFlockRepository flocks,
IAuditEventRepository audit,
IInsightsModule audit,
TenantContext tenant,
CancellationToken ct)
{
Expand Down
8 changes: 4 additions & 4 deletions src/Cluckwork.Api/Endpoints/Export/ExportEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.IO.Pipelines;
using System.Text.Json;
using Cluckwork.Application.Common;
using Cluckwork.Application.Features.Export;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Infrastructure.Persistence;

namespace Cluckwork.Api.Endpoints.Export;
Expand Down Expand Up @@ -30,7 +30,7 @@ public static RouteGroupBuilder MapExportEndpoints(this RouteGroupBuilder group)
}

private static async Task<IResult> ExportDataset(
string dataset, IExportQueries exports, IAuditWriter audit,
string dataset, IInsightsModule exports, IAuditWriter audit,
IUnitOfWork unitOfWork, IClock clock, TenantContext tenant, CancellationToken ct)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand All @@ -52,7 +52,7 @@ await audit.WriteAsync(AuditActions.AccountExport, "Account", tenant.AccountId,
}

private static async Task<IResult> ExportAll(
IExportQueries exports, IAuditWriter audit,
IInsightsModule exports, IAuditWriter audit,
IUnitOfWork unitOfWork, IClock clock, TenantContext tenant, CancellationToken ct)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand Down Expand Up @@ -90,7 +90,7 @@ await audit.WriteAsync(AuditActions.AccountExport, "Account", tenant.AccountId,
}

private static async Task WriteZipAsync(
PipeWriter writer, IExportQueries exports, IClock clock, CancellationToken ct)
PipeWriter writer, IInsightsModule exports, IClock clock, CancellationToken ct)
{
try
{
Expand Down
5 changes: 3 additions & 2 deletions src/Cluckwork.Api/Endpoints/Flocks/FlockEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Cluckwork.Application.Features.Flocks.ReactivateFlock;
using Cluckwork.Application.Features.Flocks.RecordBirdMovement;
using Cluckwork.Application.Features.Flocks.UpdateFlock;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Domain.Flocks;
using Cluckwork.Infrastructure.Persistence;
using FluentValidation;
Expand Down Expand Up @@ -105,7 +106,7 @@ private static async Task<IResult> CreateFlock(
}

private static async Task<IResult> ListFlocks(
IFlockRepository flocks, IBirdMovementRepository movements, IAuditEventRepository audit,
IFlockRepository flocks, IBirdMovementRepository movements, IInsightsModule audit,
TenantContext tenant,
CancellationToken ct, string? search = null, string? eligibility = null,
bool? includeArchived = null, int? limit = null, int? offset = null)
Expand Down Expand Up @@ -186,7 +187,7 @@ private static async Task<IResult> ListFlocks(

private static async Task<IResult> GetFlock(
Guid id, IFlockRepository flocks, IBirdMovementRepository movements,
IAuditEventRepository audit, TenantContext tenant, CancellationToken ct)
IInsightsModule audit, TenantContext tenant, CancellationToken ct)
{
if (!tenant.IsResolved) return Results.Unauthorized();
var flock = await flocks.GetByIdAsync(id, ct);
Expand Down
10 changes: 5 additions & 5 deletions src/Cluckwork.Api/Endpoints/Reports/ReportEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Cluckwork.Application.Common;
using Cluckwork.Application.Features.Flocks;
using Cluckwork.Application.Features.Reports;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Infrastructure.Persistence;

namespace Cluckwork.Api.Endpoints.Reports;
Expand Down Expand Up @@ -67,7 +67,7 @@ public static RouteGroupBuilder MapReportEndpoints(this RouteGroupBuilder group)
}

private static async Task<IResult> Production(
IReportQueries reports, IFlockRepository flocks, TenantContext tenant,
IInsightsModule reports, IFlockRepository flocks, TenantContext tenant,
IFarmClock farmClock, CancellationToken ct,
DateOnly? from = null, DateOnly? to = null, Guid? flockId = null)
{
Expand All @@ -87,7 +87,7 @@ private static async Task<IResult> Production(
}

private static async Task<IResult> Sales(
IReportQueries reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
IInsightsModule reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
DateOnly? from = null, DateOnly? to = null)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand All @@ -97,7 +97,7 @@ private static async Task<IResult> Sales(
}

private static async Task<IResult> Expenses(
IReportQueries reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
IInsightsModule reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
DateOnly? from = null, DateOnly? to = null)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand All @@ -107,7 +107,7 @@ private static async Task<IResult> Expenses(
}

private static async Task<IResult> Profit(
IReportQueries reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
IInsightsModule reports, TenantContext tenant, IFarmClock farmClock, CancellationToken ct,
DateOnly? from = null, DateOnly? to = null)
{
if (!tenant.IsResolved) return Results.Unauthorized();
Expand Down
5 changes: 3 additions & 2 deletions src/Cluckwork.Api/Endpoints/Sales/SaleEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Cluckwork.Application.Features.Sales.UpdateOrderItem;
using Cluckwork.Application.Features.Sales.VoidSale;
using FluentValidation;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Domain.Sales;
using Cluckwork.Infrastructure.Persistence;
using Cluckwork.Application.Features.Customers;
Expand Down Expand Up @@ -197,7 +198,7 @@ private static async Task<IResult> CancelSalesOrder(

private static async Task<IResult> GetSalesOrder(
Guid id, ISalesOrderRepository orders, ICustomerRepository customers, IEggGradeRepository grades,
IAuditEventRepository audit, IPaymentRepository payments,
IInsightsModule audit, IPaymentRepository payments,
IAuthorizationService authorization, ClaimsPrincipal caller,
TenantContext tenant, CancellationToken ct)
{
Expand Down Expand Up @@ -245,7 +246,7 @@ private static async Task<IResult> ListSalesOrders(
ISalesOrderRepository orders,
Cluckwork.Application.Features.Customers.ICustomerRepository customers,
IEggGradeRepository grades,
IAuditEventRepository audit,
IInsightsModule audit,
IAuthorizationService authorization, ClaimsPrincipal caller,
TenantContext tenant, CancellationToken ct,
string? status = null, Guid? customerId = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
using Cluckwork.Application.Features.Users.SetStepperUnit;
using Cluckwork.Infrastructure.Persistence;
using Cluckwork.Infrastructure.Providers;
using Cluckwork.Infrastructure.Insights;
using Cluckwork.Infrastructure.Repositories;
using Cluckwork.Infrastructure.Time;
using FluentValidation;
Expand Down
3 changes: 3 additions & 0 deletions src/Cluckwork.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
using Cluckwork.Api.Middleware;
using Cluckwork.Api.Security;
using Cluckwork.Api.Validation;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Infrastructure.Insights;
using Cluckwork.Infrastructure.Persistence;
using Cluckwork.Infrastructure.RateLimiting;
using Microsoft.AspNetCore.Builder;
Expand Down Expand Up @@ -73,6 +75,7 @@
rateLimiting.Options.ReportsConcurrency.PermitLimit, sharedState.Redis.KeyNamespace);

builder.Services.AddCluckworkFeatures(builder.Configuration);
builder.Services.AddScoped<IInsightsModule, InsightsModule>();

// #307 — lease duration / max-wait bounds for the idempotency claim protocol.
builder.Services.Configure<IdempotencyOptions>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Cluckwork.Domain.Auditing;
using Cluckwork.Application.Features.Insights;

namespace Cluckwork.Application.Features.Audit;

Expand All @@ -11,7 +11,7 @@ public interface IAuditEventRepository
// fixed cap eventually; a larger input is chunked, never refused.
public const int MaxBatchIds = 500;

Task<IReadOnlyList<AuditEvent>> ListAsync(
Task<IReadOnlyList<AuditEventRead>> ListAsync(
string? action, string? entityType, Guid? entityId, DateOnly? from, DateOnly? to,
int limit, int offset, CancellationToken ct = default);

Expand Down
6 changes: 6 additions & 0 deletions src/Cluckwork.Application/Features/Insights/AuditEventRead.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Cluckwork.Application.Features.Insights;

public sealed record AuditEventRead(
Guid Id, DateTimeOffset OccurredAtUtc, string ActorEmail,
string Action, string EntityType, Guid EntityId,
string? Reason, string? DetailsJson);
15 changes: 15 additions & 0 deletions src/Cluckwork.Application/Features/Insights/IInsightsModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Cluckwork.Application.Features.Audit;
using Cluckwork.Application.Features.Export;
using Cluckwork.Application.Features.Reports;

namespace Cluckwork.Application.Features.Insights;

public interface IInsightsModule : IReportQueries, IExportQueries
{
Task<IReadOnlyList<AuditEventRead>> ListAuditEventsAsync(
string? action, string? entityType, Guid? entityId, DateOnly? from, DateOnly? to,
int limit, int offset, CancellationToken ct = default);

Task<IReadOnlyDictionary<Guid, EntityProvenance>> GetProvenanceAsync(
string entityType, IReadOnlyCollection<Guid> entityIds, CancellationToken ct = default);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Cluckwork.Application.Common;
using Cluckwork.Application.Features.Audit;
using Cluckwork.Application.Features.Insights;
using Cluckwork.Domain.Auditing;
using Cluckwork.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace Cluckwork.Infrastructure.Repositories;
namespace Cluckwork.Infrastructure.Insights;

public sealed class AuditEventRepository(AppDbContext db, TenantContext tenant) : IAuditEventRepository
{
public async Task<IReadOnlyList<AuditEvent>> ListAsync(
public async Task<IReadOnlyList<AuditEventRead>> ListAsync(
string? action, string? entityType, Guid? entityId, DateOnly? from, DateOnly? to,
int limit, int offset, CancellationToken ct = default)
{
Expand All @@ -23,7 +24,7 @@ public async Task<IReadOnlyList<AuditEvent>> ListAsync(
: new DateTimeOffset(t.AddDays(1).ToDateTime(TimeOnly.MinValue), TimeSpan.Zero)
: (DateTimeOffset?)null;

return await db.AuditEvents
var events = await db.AuditEvents
.AsNoTracking()
.Where(e => (action == null || e.Action == action)
&& (entityType == null || e.EntityType == entityType)
Expand All @@ -37,6 +38,10 @@ public async Task<IReadOnlyList<AuditEvent>> ListAsync(
.Skip(offset)
.Take(limit)
.ToListAsync(ct);

return events.Select(e => new AuditEventRead(
e.Id, e.OccurredAtUtc, e.ActorEmail, e.Action, e.EntityType, e.EntityId,
e.Reason, e.DetailsJson)).ToList();
}

// #494 — who created a record and who last changed it, read out of the
Expand Down
Loading
Loading