Add dev hook for basic location review rebuild

This commit is contained in:
Nick Beckley 2026-08-31 16:13:16 +00:00
parent 1cf0174466
commit 66e28b7f0e
6 changed files with 160 additions and 2 deletions

View File

@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using PlotLine.Data;
using PlotLine.Models;
using PlotLine.Services;
using PlotLine.ViewModels;
@ -23,8 +25,17 @@ public sealed class AdminController(
IStoryIntelligenceImportCommitService storyIntelligenceImportCommit,
INameIntelligenceService nameIntelligence,
IIllustrationLibraryService illustrationLibrary,
IProjectRepository projects,
IBookRepository books,
IOnboardingBuildRepository onboardingBuilds,
ICoreImportBasicLocationService coreImportBasicLocations,
IWebHostEnvironment environment,
ILogger<AdminController> logger) : Controller
{
private static readonly Guid DiscoveryBasicLocationReviewPreviewId = Guid.Parse("50760000-3068-4000-8000-000000005076");
private const int DiscoveryProjectId = 3068;
private const int DiscoveryBookId = 5076;
[HttpGet("")]
[HttpGet("index")]
public async Task<IActionResult> Index()
@ -41,6 +52,110 @@ public sealed class AdminController(
return View();
}
[HttpGet("development/core-basic-location-review/discovery")]
public async Task<IActionResult> DevelopmentDiscoveryBasicLocationReview(bool reset = true)
{
if (!environment.IsDevelopment())
{
return NotFound();
}
if (currentUser.UserId is not int userId)
{
return Forbid();
}
var project = await projects.GetAsync(DiscoveryProjectId);
var book = await books.GetAsync(DiscoveryBookId);
if (project is null || book is null || book.ProjectID != DiscoveryProjectId)
{
return NotFound();
}
var sceneSources = await onboardingBuilds.ListSceneManuscriptSourcesByBookAsync(DiscoveryBookId);
if (sceneSources.Count == 0)
{
TempData["AdminError"] = "Book 5076 has no canonical scene source text to prepare.";
return RedirectToAction(nameof(Index));
}
if (reset)
{
await coreImportBasicLocations.ResetAndPrepareAsync(userId, DiscoveryBasicLocationReviewPreviewId, DiscoveryProjectId, DiscoveryBookId, null, sceneSources);
}
else
{
await coreImportBasicLocations.EnsurePreparedAsync(userId, DiscoveryBasicLocationReviewPreviewId, DiscoveryProjectId, DiscoveryBookId, null, sceneSources);
}
var model = await coreImportBasicLocations.GetReviewAsync(
userId,
DiscoveryBasicLocationReviewPreviewId,
"Discovery retained Book 5076",
project.ProjectName,
book.BookDisplayTitle);
if (model is null)
{
return NotFound();
}
return View("~/Views/Onboarding/ScanLocations.cshtml", new CoreImportBasicLocationReviewViewModel
{
PreviewID = model.PreviewID,
ReviewID = model.ReviewID,
Status = model.Status,
DocumentTitle = model.DocumentTitle,
SelectedProjectName = model.SelectedProjectName,
SelectedBookTitle = model.SelectedBookTitle,
FormController = "Admin",
FormAction = nameof(SaveDevelopmentDiscoveryBasicLocationReview),
ContinueButtonText = "Save selected locations",
RawObservationCount = model.RawObservationCount,
CandidateCount = model.CandidateCount,
Candidates = model.Candidates,
ExistingLocationOptions = model.ExistingLocationOptions
});
}
[HttpPost("development/core-basic-location-review/discovery")]
[RequestFormLimits(ValueCountLimit = 20000, KeyLengthLimit = 4096, ValueLengthLimit = 1024 * 1024)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveDevelopmentDiscoveryBasicLocationReview(CoreImportBasicLocationReviewForm form, string intent = "save")
{
if (!environment.IsDevelopment())
{
return NotFound();
}
if (currentUser.UserId is not int userId)
{
return Forbid();
}
if (form.PreviewID != DiscoveryBasicLocationReviewPreviewId)
{
return BadRequest();
}
if (string.Equals(intent, "skip", StringComparison.OrdinalIgnoreCase))
{
await coreImportBasicLocations.SkipAsync(userId, form.PreviewID);
TempData["AdminMessage"] = "Development basic location review skipped for Book 5076.";
return RedirectToAction(nameof(Index));
}
await coreImportBasicLocations.SaveDecisionsAsync(userId, form);
if (string.Equals(intent, "continue", StringComparison.OrdinalIgnoreCase))
{
await coreImportBasicLocations.CompleteAsync(userId, form.PreviewID);
TempData["AdminMessage"] = "Development basic location review materialised selected locations for Book 5076.";
return RedirectToAction(nameof(Index));
}
TempData["OnboardingReviewMessage"] = "Development location review choices saved.";
return RedirectToAction(nameof(DevelopmentDiscoveryBasicLocationReview), new { reset = false });
}
[HttpPost("name-intelligence/validate")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ValidateNameIntelligence(CancellationToken cancellationToken)

View File

@ -7,6 +7,7 @@ namespace PlotLine.Data;
public interface ICoreImportBasicLocationReviewRepository
{
Task<CoreImportBasicLocationReview?> GetByPreviewAsync(int userId, Guid previewId);
Task ResetByPreviewAsync(int userId, Guid previewId);
Task<CoreImportBasicLocationReview> CreateAsync(int userId, Guid previewId, int projectId, int bookId, int? buildId);
Task<CoreImportBasicLocationCandidate> SaveCandidateAsync(int reviewId, CoreImportBasicLocationCandidate candidate);
Task DeleteEvidenceAsync(int candidateId);
@ -30,6 +31,15 @@ public sealed class CoreImportBasicLocationReviewRepository(ISqlConnectionFactor
commandType: CommandType.StoredProcedure);
}
public async Task ResetByPreviewAsync(int userId, Guid previewId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.CoreImportBasicLocationReview_ResetByPreview",
new { UserID = userId, PreviewID = previewId },
commandType: CommandType.StoredProcedure);
}
public async Task<CoreImportBasicLocationReview> CreateAsync(int userId, Guid previewId, int projectId, int bookId, int? buildId)
{
using var connection = connectionFactory.CreateConnection();

View File

@ -8,6 +8,7 @@ namespace PlotLine.Services;
public interface ICoreImportBasicLocationService
{
Task<CoreImportBasicLocationReview?> EnsurePreparedAsync(int userId, Guid previewId, int projectId, int bookId, int? buildId, IReadOnlyList<SceneManuscriptSourceRecord> sceneSources);
Task<CoreImportBasicLocationReview?> ResetAndPrepareAsync(int userId, Guid previewId, int projectId, int bookId, int? buildId, IReadOnlyList<SceneManuscriptSourceRecord> sceneSources);
Task<CoreImportBasicLocationReviewViewModel?> GetReviewAsync(int userId, Guid previewId, string documentTitle, string projectName, string bookTitle);
Task<bool> IsCompleteAsync(int userId, Guid previewId);
Task SaveDecisionsAsync(int userId, CoreImportBasicLocationReviewForm form);
@ -57,6 +58,18 @@ public sealed class CoreImportBasicLocationService(
return await reviews.GetByPreviewAsync(userId, previewId);
}
public async Task<CoreImportBasicLocationReview?> ResetAndPrepareAsync(
int userId,
Guid previewId,
int projectId,
int bookId,
int? buildId,
IReadOnlyList<SceneManuscriptSourceRecord> sceneSources)
{
await reviews.ResetByPreviewAsync(userId, previewId);
return await EnsurePreparedAsync(userId, previewId, projectId, bookId, buildId, sceneSources);
}
public async Task<CoreImportBasicLocationReviewViewModel?> GetReviewAsync(int userId, Guid previewId, string documentTitle, string projectName, string bookTitle)
{
var review = await reviews.GetByPreviewAsync(userId, previewId);

View File

@ -0,0 +1,17 @@
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE OR ALTER PROCEDURE dbo.CoreImportBasicLocationReview_ResetByPreview
@UserID int,
@PreviewID uniqueidentifier
AS
BEGIN
SET NOCOUNT ON;
DELETE dbo.CoreImportBasicLocationReviews
WHERE UserID = @UserID
AND PreviewID = @PreviewID;
END;
GO

View File

@ -956,6 +956,9 @@ public sealed class CoreImportBasicLocationReviewViewModel
public string DocumentTitle { get; init; } = "Word manuscript";
public string SelectedProjectName { get; init; } = string.Empty;
public string SelectedBookTitle { get; init; } = string.Empty;
public string FormController { get; init; } = "Onboarding";
public string FormAction { get; init; } = "SaveBasicLocations";
public string ContinueButtonText { get; init; } = "Save locations and review characters";
public int RawObservationCount { get; init; }
public int CandidateCount { get; init; }
public IReadOnlyList<CoreImportBasicLocationCandidateViewModel> Candidates { get; init; } = [];

View File

@ -31,7 +31,7 @@
</div>
</div>
<form asp-controller="Onboarding" asp-action="SaveBasicLocations" method="post">
<form asp-controller="@Model.FormController" asp-action="@Model.FormAction" method="post">
@Html.AntiForgeryToken()
<input type="hidden" name="PreviewID" value="@Model.PreviewID" />
@ -122,7 +122,7 @@
<div class="form-actions">
<button class="btn btn-secondary" type="submit" name="intent" value="save">Save choices</button>
<button class="btn btn-outline-secondary" type="submit" name="intent" value="skip">Skip locations</button>
<button class="btn btn-primary" type="submit" name="intent" value="continue">Save locations and review characters</button>
<button class="btn btn-primary" type="submit" name="intent" value="continue">@Model.ContinueButtonText</button>
</div>
</form>
</div>