feat(push): add service-owned firebase channels and delivery

Add push contracts, provider delivery and channel diagnostics. Link email channels to services and document migration and validation.

Ref: IT-1074
This commit is contained in:
2026-10-24 12:31:35 +03:00
parent b30dba93e8
commit 1631786ce2
105 changed files with 3838 additions and 147 deletions
@@ -15,14 +15,14 @@ internal sealed class EmailChannelRepository : NotificationBaseRepository<EmailC
public async Task<IReadOnlyList<EmailChannel>> GetAllAsync(CancellationToken ct = default)
{
var entities = await EfRepository.Get().ToListAsync(ct);
var entities = await EfRepository.Get().AsNoTracking().Include(x => x.Service).ToListAsync(ct);
return entities.Select(MapToDomain).ToList();
}
public async Task<IReadOnlyList<EmailChannel>> GetByServiceAsync(string serviceName, CancellationToken ct = default)
public async Task<IReadOnlyList<EmailChannel>> GetByServiceAsync(Guid serviceId, CancellationToken ct = default)
{
var entities = await EfRepository.Get(x => x.ServiceName == serviceName)
var entities = await EfRepository.Get(x => x.ServiceId == serviceId).Include(x => x.Service)
.OrderBy(x => x.Priority)
.ToListAsync(ct);
@@ -32,8 +32,8 @@ internal sealed class EmailChannelRepository : NotificationBaseRepository<EmailC
public async Task<IReadOnlyList<ChannelWithUsage>> GetAllWithUsageSummaryAsync(
DateOnly today, CancellationToken ct = default)
{
var rows = await EfRepository.Get()
.OrderBy(c => c.ServiceName)
var rows = await EfRepository.Get().AsNoTracking().Include(c => c.Service)
.OrderBy(c => c.Service.Name)
.ThenBy(c => c.Priority)
.Select(c => new
{
@@ -54,7 +54,7 @@ internal sealed class EmailChannelRepository : NotificationBaseRepository<EmailC
public async Task<EmailChannel?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
EmailChannelEntity? entity = await EfRepository.Get().AsNoTracking().SingleOrDefaultAsync(c => c.Id == id, ct);
EmailChannelEntity? entity = await EfRepository.Get().AsNoTracking().Include(c => c.Service).SingleOrDefaultAsync(c => c.Id == id, ct);
return entity is null ? null : MapToDomain(entity);
}
@@ -79,7 +79,8 @@ internal sealed class EmailChannelRepository : NotificationBaseRepository<EmailC
return new EmailChannel
{
Id = e.Id,
ServiceName = e.ServiceName,
ServiceId = e.ServiceId,
ServiceName = e.Service.Name,
Priority = e.Priority,
EmailChannelType = e.EmailChannelType,
Settings = DeserializeSettings(e.EmailChannelType, e.SettingsJson),
@@ -97,7 +98,7 @@ internal sealed class EmailChannelRepository : NotificationBaseRepository<EmailC
return new EmailChannelEntity
{
Id = p.Id,
ServiceName = p.ServiceName,
ServiceId = p.ServiceId,
Priority = p.Priority,
EmailChannelType = p.EmailChannelType,
SettingsJson = JsonSerializer.Serialize(p.Settings, p.Settings.GetType()),