feature: PHI Column Encryption + Access Logging
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// PHI access log query for compliance (Admin / Compliance).
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/phi-access-logs")]
|
||||
[Produces("application/json")]
|
||||
[AuthorizePermission(ClinicalPermissions.AuditRead)]
|
||||
public class PhiAccessLogsController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public PhiAccessLogsController(AppDbContext db) => _db = db;
|
||||
|
||||
/// <summary>Query PHI access logs — who viewed which patient records.</summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] Guid? patientId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? accessType,
|
||||
[FromQuery] DateTimeOffset? from,
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
{
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
var query = _db.PhiAccessLogs.AsNoTracking().AsQueryable();
|
||||
|
||||
if (patientId.HasValue)
|
||||
query = query.Where(l => l.PatientId == patientId);
|
||||
if (userId.HasValue)
|
||||
query = query.Where(l => l.UserId == userId);
|
||||
if (!string.IsNullOrEmpty(accessType))
|
||||
query = query.Where(l => l.AccessType.ToDbString() == accessType);
|
||||
if (from.HasValue)
|
||||
query = query.Where(l => l.AccessedAt >= from);
|
||||
if (to.HasValue)
|
||||
query = query.Where(l => l.AccessedAt <= to);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(l => l.AccessedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items,
|
||||
page,
|
||||
pageSize,
|
||||
totalCount = total,
|
||||
totalPages = (int)Math.Ceiling(total / (double)pageSize)
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>Access history for a specific patient — common compliance query.</summary>
|
||||
/// <param name="patientId">Patient id.</param>
|
||||
/// <param name="page">Page number (1-based).</param>
|
||||
/// <param name="pageSize">Results per page.</param>
|
||||
/// <returns>A paginated list of PHI access events for the patient.</returns>
|
||||
[HttpGet("patients/{patientId:guid}")]
|
||||
[AuthorizePermission(ClinicalPermissions.PatientsRead)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> ForPatient(
|
||||
Guid patientId,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
{
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
var query = _db.PhiAccessLogs.AsNoTracking()
|
||||
.Where(l => l.PatientId == patientId);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(l => l.AccessedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items,
|
||||
page,
|
||||
pageSize,
|
||||
totalCount = total,
|
||||
totalPages = (int)Math.Ceiling(total / (double)pageSize)
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user