feature: Site & Gateway Registry + Clinical Sync Contracts
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class GatewayRegistryService : IGatewayRegistryService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public GatewayRegistryService(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<WardGateway> RegisterAsync(Guid siteId, RegisterGatewayRequest req)
|
||||
{
|
||||
var siteExists = await _db.ClinicalSites.AnyAsync(s => s.Id == siteId);
|
||||
if (!siteExists)
|
||||
throw new NotFoundException("Clinical site not found.", "SITE_NOT_FOUND");
|
||||
|
||||
var duplicate = await _db.WardGateways.AnyAsync(g =>
|
||||
g.SiteId == siteId && g.GatewayCode == req.GatewayCode);
|
||||
if (duplicate)
|
||||
throw new ConflictException(
|
||||
$"Gateway code '{req.GatewayCode}' already exists for this site.",
|
||||
"GATEWAY_CODE_CONFLICT");
|
||||
|
||||
var gateway = new WardGateway(siteId, req.GatewayCode, req.Department);
|
||||
_db.WardGateways.Add(gateway);
|
||||
await _db.SaveChangesAsync();
|
||||
return gateway;
|
||||
}
|
||||
|
||||
public async Task<WardGateway> GetByIdAsync(Guid id)
|
||||
{
|
||||
var gateway = await _db.WardGateways
|
||||
.AsNoTracking()
|
||||
.Include(g => g.Site)
|
||||
.FirstOrDefaultAsync(g => g.Id == id);
|
||||
if (gateway is null)
|
||||
throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND");
|
||||
return gateway;
|
||||
}
|
||||
|
||||
public async Task<List<WardGateway>> ListBySiteAsync(Guid siteId, string? department)
|
||||
{
|
||||
var siteExists = await _db.ClinicalSites.AnyAsync(s => s.Id == siteId);
|
||||
if (!siteExists)
|
||||
throw new NotFoundException("Clinical site not found.", "SITE_NOT_FOUND");
|
||||
|
||||
var query = _db.WardGateways.AsNoTracking().Where(g => g.SiteId == siteId);
|
||||
if (!string.IsNullOrWhiteSpace(department))
|
||||
query = query.Where(g => g.Department == department);
|
||||
|
||||
return await query.OrderBy(g => g.GatewayCode).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<WardGateway> RecordHeartbeatAsync(
|
||||
Guid gatewayId, GatewayHeartbeatRequest req, string? authenticatedGatewayId)
|
||||
{
|
||||
if (authenticatedGatewayId is not null
|
||||
&& Guid.TryParse(authenticatedGatewayId, out var authId)
|
||||
&& authId != gatewayId)
|
||||
throw new ValidationException(
|
||||
"Gateway id in URL does not match X-Gateway-Id header.",
|
||||
"GATEWAY_ID_MISMATCH");
|
||||
|
||||
var gateway = await _db.WardGateways.FindAsync(gatewayId);
|
||||
if (gateway is null)
|
||||
throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND");
|
||||
|
||||
var status = GatewayStatusExtensions.FromDbString(req.Status.ToUpperInvariant());
|
||||
gateway.RecordHeartbeat(status, req.BufferDepth, req.ReportedAtUtc);
|
||||
await _db.SaveChangesAsync();
|
||||
return gateway;
|
||||
}
|
||||
|
||||
public async Task MarkOfflineAsync(Guid gatewayId)
|
||||
{
|
||||
var gateway = await _db.WardGateways.FindAsync(gatewayId);
|
||||
if (gateway is null)
|
||||
throw new NotFoundException("Ward gateway not found.", "GATEWAY_NOT_FOUND");
|
||||
gateway.MarkOffline();
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public interface IGatewayRegistryService
|
||||
{
|
||||
Task<WardGateway> RegisterAsync(Guid siteId, RegisterGatewayRequest req);
|
||||
Task<WardGateway> GetByIdAsync(Guid id);
|
||||
Task<List<WardGateway>> ListBySiteAsync(Guid siteId, string? department);
|
||||
Task<WardGateway> RecordHeartbeatAsync(Guid gatewayId, GatewayHeartbeatRequest req, string? authenticatedGatewayId);
|
||||
Task MarkOfflineAsync(Guid gatewayId);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
public interface ISiteService
|
||||
{
|
||||
Task<ClinicalSite> CreateAsync(CreateSiteRequest req);
|
||||
Task<ClinicalSite> GetByIdAsync(Guid id);
|
||||
Task<List<ClinicalSite>> ListAsync();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class SiteService : ISiteService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public SiteService(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<ClinicalSite> CreateAsync(CreateSiteRequest req)
|
||||
{
|
||||
var exists = await _db.ClinicalSites.AnyAsync(s => s.SiteCode == req.SiteCode);
|
||||
if (exists)
|
||||
throw new ConflictException(
|
||||
$"Site code '{req.SiteCode}' is already registered.",
|
||||
"SITE_CODE_CONFLICT");
|
||||
|
||||
var site = new ClinicalSite(req.SiteCode, req.Name, req.Address);
|
||||
_db.ClinicalSites.Add(site);
|
||||
await _db.SaveChangesAsync();
|
||||
return site;
|
||||
}
|
||||
|
||||
public async Task<ClinicalSite> GetByIdAsync(Guid id)
|
||||
{
|
||||
var site = await _db.ClinicalSites
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(s => s.Id == id);
|
||||
if (site is null)
|
||||
throw new NotFoundException("Clinical site not found.", "SITE_NOT_FOUND");
|
||||
return site;
|
||||
}
|
||||
|
||||
public async Task<List<ClinicalSite>> ListAsync() =>
|
||||
await _db.ClinicalSites.AsNoTracking()
|
||||
.OrderBy(s => s.SiteCode)
|
||||
.ToListAsync();
|
||||
}
|
||||
Reference in New Issue
Block a user