Hardened API and catered for Partner role query filter bypass

This commit is contained in:
2026-10-04 08:18:43 +02:00
parent 8d069c2da0
commit 8100518549
62 changed files with 232 additions and 193 deletions
@@ -40,7 +40,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(projects.Select(p => p.Map()).ToArray());
return Result.Ok(projects?.Select(p => p.Map()).ToArray() ?? []);
}));
public async Task<Result<Project>> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
@@ -57,8 +57,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var project = await context.Projects.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(project is null, NotFoundError.New)
.ToResult(project!.Map());
return Result.FailIf(project is null, NotFoundError.Create("Project not found"))
.ToResult(project?.Map()!);
}));
public async Task<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
@@ -76,7 +76,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.OrderByDescending(a => a.CreatedAt)
.ToListAsync(cancellationToken);
return Result.Ok(activities.Select(a => a.Map()).ToArray());
return Result.Ok(activities?.Select(a => a.Map()).ToArray() ?? []);
}));
public async Task<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -157,7 +157,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.OrderBy(m => m.DueAt)
.ToListAsync(cancellationToken);
return Result.Ok(milestones.Select(i => i.Map()).ToArray());
return Result.Ok(milestones?.Select(i => i.Map()).ToArray() ?? []);
}));
public async Task<Result<Indicator[]>> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken) =>
@@ -176,7 +176,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.OrderByDescending(o => o.Id)
.ToListAsync(cancellationToken);
return Result.Ok(evidences.Select(e => e.Map()).ToArray());
return Result.Ok(evidences?.Select(e => e.Map()).ToArray() ?? []);
}));
public async Task<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
@@ -192,8 +192,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var milestone = await context.Milestones.AsNoTracking()
.FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(milestone is null, NotFoundError.New)
.ToResult(milestone!.Map());
return Result.FailIf(milestone is null, NotFoundError.Create("Milestone not found"))
.ToResult(milestone?.Map()!);
}));
public async Task<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
@@ -207,7 +207,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(milestones.Select(i => i.Map()).ToArray());
return Result.Ok(milestones?.Select(i => i.Map()).ToArray() ?? []);
}));
public async Task<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -216,7 +216,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
return Result.Fail(NotFoundError.New);
return Result.Fail(NotFoundError.Create("User not found"));
var changedTotal = await context.Milestones.Where(m => m.Id == request.MilestoneId)
.ExecuteUpdateAsync(f => f
@@ -263,8 +263,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var activity = await context.Activities.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(activity is null, NotFoundError.New)
.ToResult(activity!.Map());
return Result.FailIf(activity is null, NotFoundError.Create("Activity not found"))
.ToResult(activity?.Map()!);
}));
public async Task<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
@@ -283,7 +283,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.OrderByDescending(r => r.CreatedAt)
.ToListAsync(cancellationToken);
return Result.Ok(risks.Select(i => i.Map()).ToArray());
return Result.Ok(risks?.Select(i => i.Map()).ToArray() ?? []);
}));
public async Task<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -310,7 +310,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
TenantId = tenantContext.TenantId
});
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.Create("Failed to create programme."));
}));
public async Task<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -319,7 +319,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
return Result.Fail(NotFoundError.New);
return Result.Fail(NotFoundError.Create("User not found"));
var changedTotal = await context.Programmes.Where(p => p.Id == request.ProgrammeId)
.ExecuteUpdateAsync(f => f
@@ -344,8 +344,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var programme = await context.Programmes.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(programme is null, NotFoundError.New)
.ToResult(programme!.Map());
return Result.FailIf(programme is null, NotFoundError.Create("Programme not found"))
.ToResult(programme?.Map()!);
}));
public async Task<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
@@ -359,7 +359,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(programmes.Select(i => i.Map()).ToArray());
return Result.Ok(programmes?.Select(i => i.Map()).ToArray() ?? []);
}));
public async Task<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
@@ -373,14 +373,14 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Programmes.AnyAsync(p => p.Id == decodeResult.Value, cancellationToken))
return Result.Fail(NotFoundError.New);
return Result.Fail(NotFoundError.Create("Programme not found"));
var projects = await context.Projects.AsNoTracking()
.Where(p => p.ProgrammeId == decodeResult.Value)
.OrderByDescending(o => o.CreatedAt)
.ToListAsync(cancellationToken);
return Result.Ok(projects.Select(i => i.Map()).ToArray());
return Result.Ok(projects?.Select(i => i.Map()).ToArray() ?? []);
}));
public async Task<Result> CreateChangeRequestAsync(CreateChangeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -456,8 +456,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var changeRequest = await context.ChangeRequests.AsNoTracking().FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(changeRequest is null, NotFoundError.New)
.ToResult(changeRequest!.Map());
return Result.FailIf(changeRequest is null, NotFoundError.Create("Change request not found"))
.ToResult(changeRequest?.Map()!);
}));
public async Task<Result> CreateComplianceItemAsync(CreateComplianceItemRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -529,8 +529,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var compliancItem = await context.ComplianceItems.AsNoTracking().FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(compliancItem is null, NotFoundError.New)
.ToResult(compliancItem!.Map());
return Result.FailIf(compliancItem is null, NotFoundError.Create("Compliance item not found"))
.ToResult(compliancItem?.Map()!);
}));
public async Task<Result> CreateBeneficiaryAsync(CreateBeneficiaryRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -606,8 +606,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var beneficiary = await context.Beneficiaries.AsNoTracking()
.FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(beneficiary is null, NotFoundError.New)
.ToResult(beneficiary!.Map());
return Result.FailIf(beneficiary is null, NotFoundError.Create("Beneficiary not found"))
.ToResult(beneficiary?.Map()!);
}));
public async Task<Result> CreateIssueAsync(CreateIssueRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -681,8 +681,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var issue = await context.Issues.AsNoTracking().FirstOrDefaultAsync(i => i.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(issue is null, NotFoundError.New)
.ToResult(issue!.Map());
return Result.FailIf(issue is null, NotFoundError.Create("Issue not found"))
.ToResult(issue?.Map()!);
}));
public async Task<Result> CreateOutcomeAsync(CreateOutcomeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -750,8 +750,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var outcome = await context.Outcomes.AsNoTracking()
.FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(outcome is null, NotFoundError.New)
.ToResult(outcome!.Map());
return Result.FailIf(outcome is null, NotFoundError.Create("Outcome not found"))
.ToResult(outcome?.Map()!);
}));
public async Task<Result> CreateSiteVisitAsync(CreateSiteVisitRequest request, Guid userId, CancellationToken cancellationToken = default) =>
@@ -820,7 +820,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
var visit = await context.SiteVisits.AsNoTracking().FirstOrDefaultAsync(v => v.Id == decodeResult.Value, cancellationToken);
return Result.FailIf(visit is null, NotFoundError.New)
.ToResult(visit!.Map());
return Result.FailIf(visit is null, NotFoundError.Create("Site visit not found"))
.ToResult(visit?.Map()!);
}));
}