feat(notification): add admin rest management and deletion

Add revision-safe service and template APIs, channel mapping, and validated admin controls.

Ref: IT-1601
This commit is contained in:
2026-10-17 22:51:01 +03:00
parent 81654c0d33
commit 8e81572386
80 changed files with 2253 additions and 54 deletions
@@ -1,3 +1,5 @@
using HrynCo.NotificationService.Services.TemplateDefinitions;
using MediatR;
using HrynCo.NotificationService.DAL.Abstract.Repositories;
using HrynCo.NotificationService.DAL.Abstract.Templates;
using HrynCo.NotificationService.Web.Controllers.Admin.ViewModels;
@@ -6,7 +8,8 @@ using Microsoft.AspNetCore.Mvc;
namespace HrynCo.NotificationService.Web.Controllers.Admin;
[Route("admin/services")]
public sealed class AdminServicesController(INotificationServiceRepository services, ILogger<AdminServicesController> logger) : Controller
public sealed class AdminServicesController(INotificationServiceRepository services, ILogger<AdminServicesController> logger,
IEmailTemplateDefinitionRepository definitions, IMediator mediator) : Controller
{
[HttpGet("")]
public async Task<IActionResult> Index(CancellationToken ct) => View(await services.GetAllAsync(ct));
@@ -77,6 +80,55 @@ public sealed class AdminServicesController(INotificationServiceRepository servi
}
}
[HttpGet("{serviceId:guid}/delete")]
public async Task<IActionResult> ConfirmServiceDelete([FromRoute] Guid serviceId, CancellationToken ct)
{
var model = await ServiceDeleteModelAsync(serviceId, ct);
return model is null ? NotFound() : View("DeleteService", model);
}
[HttpPost("{serviceId:guid}/delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteService([FromRoute] Guid serviceId, NotificationServiceDeleteViewModel model, CancellationToken ct)
{
if (!ModelState.IsValid || !model.ConfirmDelete)
return await ServiceDeleteErrorAsync(serviceId, "Confirm deletion of this service to continue.", ct);
try
{
var result = await mediator.Send(new DeleteNotificationServiceCommand(serviceId, model.Revision), ct);
if (result.IsSuccess)
return RedirectToAction(nameof(Index));
return await ServiceDeleteErrorAsync(serviceId, result.Error!.Message, ct);
}
catch (Exception error) when (error is not OperationCanceledException)
{
logger.LogError(error, "Failed to delete notification service.");
return await ServiceDeleteErrorAsync(serviceId, "Could not complete deletion. Review the current service before trying again.", ct);
}
}
private async Task<NotificationServiceDeleteViewModel?> ServiceDeleteModelAsync(Guid serviceId, CancellationToken ct)
{
var service = await services.GetAsync(serviceId, ct);
if (service is null)
return null;
return new NotificationServiceDeleteViewModel
{
ServiceId = service.Id, Name = service.Name, Revision = service.Revision,
DefinitionCount = (await definitions.GetDefinitionsAsync(serviceId, ct)).Count
};
}
private async Task<IActionResult> ServiceDeleteErrorAsync(Guid serviceId, string error, CancellationToken ct)
{
var model = await ServiceDeleteModelAsync(serviceId, ct);
if (model is null)
return NotFound();
ModelState.Clear();
ModelState.AddModelError("", error);
return View("DeleteService", model);
}
private async Task<IActionResult> ServiceEditorViewAsync(NotificationServiceEditViewModel model, CancellationToken ct)
{
if (model.Id is not Guid id)