81654c0d33
Add normalized service and template catalogs, scoped administration, migration, validation, and regression coverage. Ref: IT-1601
126 lines
5.7 KiB
C#
126 lines
5.7 KiB
C#
using HrynCo.NotificationService.DAL.Abstract.Repositories;
|
|
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
|
using HrynCo.NotificationService.Web.Controllers.Admin.ViewModels;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace HrynCo.NotificationService.Web.Controllers.Admin;
|
|
|
|
[Route("admin/services")]
|
|
public sealed class AdminServicesController(INotificationServiceRepository services, ILogger<AdminServicesController> logger) : Controller
|
|
{
|
|
[HttpGet("")]
|
|
public async Task<IActionResult> Index(CancellationToken ct) => View(await services.GetAllAsync(ct));
|
|
|
|
[HttpGet("create")]
|
|
public IActionResult Create() => View("Edit", new NotificationServiceEditViewModel());
|
|
|
|
[HttpGet("{id:guid}/edit")]
|
|
public async Task<IActionResult> Edit([FromRoute] Guid id, CancellationToken ct)
|
|
{
|
|
var service = await services.GetAsync(id, ct);
|
|
return service is null ? NotFound() : View(ToViewModel(service));
|
|
}
|
|
|
|
[HttpPost("save")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Save(NotificationServiceEditViewModel model, CancellationToken ct)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
return await ServiceEditorViewAsync(model, ct);
|
|
try
|
|
{
|
|
var service = new NotificationServiceInfo
|
|
{
|
|
Id = model.Id ?? Guid.Empty, Name = model.Name, Revision = model.Revision, Languages = model.Languages
|
|
};
|
|
await services.SaveAsync(service, model.IsNew, ct);
|
|
return RedirectToAction(nameof(AdminTemplatesController.Index), "AdminTemplates", new { serviceId = service.Id });
|
|
}
|
|
catch (EmailTemplateWriteException error)
|
|
{
|
|
ModelState.AddModelError("", error.Message);
|
|
}
|
|
catch (Exception error) when (error is not OperationCanceledException)
|
|
{
|
|
logger.LogError(error, "Failed to save notification service.");
|
|
ModelState.AddModelError("", "Could not save the service. Your entered values are retained; try again.");
|
|
}
|
|
return await ServiceEditorViewAsync(model, ct);
|
|
}
|
|
|
|
[HttpGet("{serviceId:guid}/languages/{languageId:guid}/delete")]
|
|
public async Task<IActionResult> ConfirmLanguageRemoval([FromRoute] Guid serviceId, [FromRoute] Guid languageId, CancellationToken ct)
|
|
{
|
|
var model = await RemovalModelAsync(serviceId, languageId, ct);
|
|
return model is null ? NotFound() : View("DeleteLanguage", model);
|
|
}
|
|
|
|
[HttpPost("{serviceId:guid}/languages/{languageId:guid}/delete")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RemoveLanguage([FromRoute] Guid serviceId, [FromRoute] Guid languageId, ServiceLanguageDeleteViewModel model, CancellationToken ct)
|
|
{
|
|
if (!ModelState.IsValid || !model.ConfirmDelete)
|
|
return await RemovalErrorAsync(serviceId, languageId, "Confirm deletion of the language and all listed versions to continue.", ct);
|
|
try
|
|
{
|
|
await services.RemoveLanguageAsync(serviceId, languageId, model.VersionCount, model.Revision, ct);
|
|
return RedirectToAction(nameof(Edit), new { id = serviceId });
|
|
}
|
|
catch (EmailTemplateWriteException error)
|
|
{
|
|
return await RemovalErrorAsync(serviceId, languageId, error.Message, ct);
|
|
}
|
|
catch (Exception error) when (error is not OperationCanceledException)
|
|
{
|
|
logger.LogError(error, "Failed to remove service language.");
|
|
return await RemovalErrorAsync(serviceId, languageId, "Could not delete the language. No changes were saved; review the count and try again.", ct);
|
|
}
|
|
}
|
|
|
|
private async Task<IActionResult> ServiceEditorViewAsync(NotificationServiceEditViewModel model, CancellationToken ct)
|
|
{
|
|
if (model.Id is not Guid id)
|
|
return View("Edit", model);
|
|
try
|
|
{
|
|
var persisted = await services.GetAsync(id, ct);
|
|
var counts = persisted?.Languages.ToDictionary(l => l.Id, l => l.VersionCount) ?? [];
|
|
foreach (var language in model.Languages)
|
|
language.VersionCount = counts.GetValueOrDefault(language.Id);
|
|
}
|
|
catch (Exception error) when (error is not OperationCanceledException)
|
|
{
|
|
logger.LogError(error, "Failed to refresh service language version counts.");
|
|
ViewData["LanguageCountsUnavailable"] = true;
|
|
ModelState.AddModelError("", "Could not refresh language usage counts. Removal is disabled until the service is available.");
|
|
}
|
|
return View("Edit", model);
|
|
}
|
|
|
|
private async Task<IActionResult> RemovalErrorAsync(Guid serviceId, Guid languageId, string error, CancellationToken ct)
|
|
{
|
|
var model = await RemovalModelAsync(serviceId, languageId, ct);
|
|
if (model is null)
|
|
return NotFound();
|
|
ModelState.Clear();
|
|
ModelState.AddModelError("", error);
|
|
return View("DeleteLanguage", model);
|
|
}
|
|
|
|
private async Task<ServiceLanguageDeleteViewModel?> RemovalModelAsync(Guid serviceId, Guid languageId, CancellationToken ct)
|
|
{
|
|
var service = await services.GetAsync(serviceId, ct);
|
|
var language = service?.Languages.SingleOrDefault(l => l.Id == languageId);
|
|
return language is null ? null : new ServiceLanguageDeleteViewModel
|
|
{
|
|
ServiceId = serviceId, LanguageId = languageId, ServiceName = service!.Name, Revision = service.Revision,
|
|
Code = language.Code, VersionCount = language.VersionCount
|
|
};
|
|
}
|
|
|
|
private static NotificationServiceEditViewModel ToViewModel(NotificationServiceInfo service) => new()
|
|
{
|
|
Id = service.Id, Name = service.Name, Revision = service.Revision, Languages = service.Languages.ToList()
|
|
};
|
|
}
|