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 logger) : Controller { [HttpGet("")] public async Task Index(CancellationToken ct) => View(await services.GetAllAsync(ct)); [HttpGet("create")] public IActionResult Create() => View("Edit", new NotificationServiceEditViewModel()); [HttpGet("{id:guid}/edit")] public async Task 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 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 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 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 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 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 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() }; }