feat(templates): add localized push templates and actions

Share template keys across email and push with independent editors and revisions. Render localized actions and preserve version-one delivery.

Ref: IT-1627
This commit is contained in:
2026-10-31 09:38:05 +02:00
parent d4ee8fc0c8
commit aba4173827
79 changed files with 3906 additions and 104 deletions
@@ -78,6 +78,7 @@ internal sealed class EmailTemplateRepository(NotificationDbContext db, Template
if (entity is not null)
throw new EmailTemplateWriteException("A definition with this service and key already exists. Read its revision before updating.");
entity = new EmailTemplateDefinitionEntity { Service = service, ServiceId = service.Id, Key = patch.Key, Created = DateTimeOffset.UtcNow };
entity.Template = await GetOrCreateTemplateAsync(service, patch.Key, ct);
db.TemplateDefinitions.Add(entity);
return entity;
}
@@ -125,6 +126,7 @@ internal sealed class EmailTemplateRepository(NotificationDbContext db, Template
if (definition.Revision != revision)
throw new EmailTemplateWriteException("The definition revision changed. Read it again before deleting.");
db.TemplateDefinitions.Remove(definition);
await RemoveEmptyTemplateAsync(serviceId, key, ct);
removed = true;
}, ct);
return removed;
@@ -261,12 +263,31 @@ internal sealed class EmailTemplateRepository(NotificationDbContext db, Template
?? throw new EmailTemplateWriteException("Service does not exist. Create the service and its language codes first.", false);
var entity = new EmailTemplateDefinitionEntity
{
Template = await GetOrCreateTemplateAsync(service, key, ct),
Service = service, Key = key, Variables = MapVariables(variables), Created = DateTimeOffset.UtcNow
};
db.TemplateDefinitions.Add(entity);
return entity;
}
private async Task<TemplateEntity> GetOrCreateTemplateAsync(ServiceEntity service, string key, CancellationToken ct)
{
var template = await db.TemplateCatalog.SingleOrDefaultAsync(t => t.ServiceId == service.Id && t.Key == key, ct);
if (template is not null)
return template;
template = new TemplateEntity { Service = service, ServiceId = service.Id, Key = key, Created = DateTimeOffset.UtcNow };
db.TemplateCatalog.Add(template);
return template;
}
private async Task RemoveEmptyTemplateAsync(Guid serviceId, string key, CancellationToken ct)
{
if (await db.PushTemplateDefinitions.AnyAsync(t => t.ServiceId == serviceId && t.Key == key, ct))
return;
var template = await db.TemplateCatalog.SingleAsync(t => t.ServiceId == serviceId && t.Key == key, ct);
db.TemplateCatalog.Remove(template);
}
private static void ValidateLegacy(EmailTemplate template)
{
EmailTemplateValidation.ValidateIdentity(template.ServiceName, template.Key);