using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] public sealed class WarningsController(IContinuityValidationService validation) : Controller { public async Task Index( int projectId, int? bookId, int? chapterId, int? sceneId, int? warningTypeId, int? warningSeverityId, string? entityType, string? category, bool includeDismissed = false, bool includeIntentional = false, bool showAcknowledgedWarnings = false) { var model = await validation.GetDashboardAsync(projectId, bookId, chapterId, sceneId, warningTypeId, warningSeverityId, entityType, category, includeDismissed, includeIntentional, showAcknowledgedWarnings); return model is null ? NotFound() : View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task ValidateProject(int projectId, int? bookId) { var summary = await validation.ValidateProjectAsync(projectId); var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary); return model is null ? NotFound() : View("Index", model); } [HttpPost] [ValidateAntiForgeryToken] public async Task ValidateBook(int projectId, int bookId) { var summary = await validation.ValidateBookAsync(bookId); var model = await validation.GetDashboardAsync(projectId, bookId, null, null, null, null, null, null, false, false, false, summary); return model is null ? NotFound() : View("Index", model); } [HttpPost] [ValidateAntiForgeryToken] public async Task ValidateScene(int projectId, int? bookId, int sceneId, bool returnToTimeline = false) { await validation.ValidateSceneAsync(sceneId); if (TryGetLocalReturnUrl(out var returnUrl)) { return LocalRedirect(returnUrl); } if (returnToTimeline) { return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId }); } return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task Dismiss(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false) { await validation.DismissAsync(id); return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline); } [HttpPost] [ValidateAntiForgeryToken] public async Task MarkIntentional(int id, int projectId, int? bookId, int? sceneId, bool returnToTimeline = false) { await validation.MarkIntentionalAsync(id); return RedirectAfterAction(projectId, bookId, sceneId, returnToTimeline); } [HttpPost] [ValidateAntiForgeryToken] public async Task Restore(int id, int projectId, int? bookId, int? sceneId) { await validation.RestoreAsync(id); return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId, includeDismissed = true, includeIntentional = true }); } [HttpPost] [ValidateAntiForgeryToken] public async Task AcknowledgeGeneratedWarning( string warningType, int? sceneId, int? characterId, int? assetId, string? notes, [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter) { if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) { return BadRequest("Warning identity is required."); } await validation.AcknowledgeGeneratedWarningAsync(new ContinuityWarningAcknowledgement { ProjectID = returnFilter.ProjectID, WarningType = warningType, SceneID = sceneId, CharacterID = characterId, AssetID = assetId, Notes = notes }); return RedirectToDashboard(returnFilter); } [HttpPost] [ValidateAntiForgeryToken] public async Task RestoreGeneratedWarning( string warningType, int? sceneId, int? characterId, int? assetId, [Bind(Prefix = "ReturnFilter")] WarningDashboardReturnFilter returnFilter) { if (returnFilter.ProjectID == 0 || string.IsNullOrWhiteSpace(warningType)) { return BadRequest("Warning identity is required."); } await validation.RestoreGeneratedWarningAsync(new ContinuityWarningAcknowledgement { ProjectID = returnFilter.ProjectID, WarningType = warningType, SceneID = sceneId, CharacterID = characterId, AssetID = assetId }); return RedirectToDashboard(returnFilter); } private IActionResult RedirectAfterAction(int projectId, int? bookId, int? sceneId, bool returnToTimeline) { if (TryGetLocalReturnUrl(out var returnUrl)) { return LocalRedirect(returnUrl); } if (returnToTimeline && sceneId.HasValue) { return RedirectToAction("Index", "Timeline", new { projectId, bookId, selectedSceneId = sceneId.Value }); } return RedirectToAction(nameof(Index), new { projectId, bookId, sceneId }); } private bool TryGetLocalReturnUrl(out string returnUrl) { if (Request.HasFormContentType && Request.Form.TryGetValue("ReturnUrl", out var postedReturnUrl) && Url.IsLocalUrl(postedReturnUrl.ToString())) { returnUrl = postedReturnUrl.ToString(); return true; } returnUrl = string.Empty; return false; } private IActionResult RedirectToDashboard(WarningDashboardReturnFilter filter) { return RedirectToAction(nameof(Index), new { projectId = filter.ProjectID, bookId = filter.BookID, chapterId = filter.ChapterID, sceneId = filter.SceneID, warningTypeId = filter.WarningTypeID, warningSeverityId = filter.WarningSeverityID, entityType = filter.EntityType, category = filter.Category, includeDismissed = filter.IncludeDismissed, includeIntentional = filter.IncludeIntentional, showAcknowledgedWarnings = filter.ShowAcknowledgedWarnings }); } }