using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] public sealed class BooksController(IBookService books, IOnboardingService onboarding) : Controller { public async Task Details(int id) { var model = await books.GetDetailAsync(id); return model is null ? NotFound() : View(model); } public async Task Create(int projectId) { var model = await books.GetCreateAsync(projectId); return model is null ? NotFound() : View("Edit", model); } public async Task Edit(int id) { var model = await books.GetEditAsync(id); return model is null ? NotFound() : View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task Save(BookEditViewModel model) { if (model.BeginMode is not (BookBeginModes.Manual or BookBeginModes.Import)) { model.BeginMode = BookBeginModes.Manual; } if (!BookStatuses.IsValid(model.Status)) { ModelState.AddModelError(nameof(model.Status), "Select a valid book status."); } if (!ModelState.IsValid) { await books.PopulateEditContextAsync(model); return View("Edit", model); } BookSaveResult result; try { result = await books.SaveAsync(model); } catch (SubscriptionLimitException ex) when (model.BookID == 0) { ModelState.AddModelError(string.Empty, ex.Message); var createModel = await books.GetCreateAsync(model.ProjectID); if (createModel is null) { return NotFound(); } createModel.BookTitle = model.BookTitle; createModel.Subtitle = model.Subtitle; createModel.Tagline = model.Tagline; createModel.ShortDescription = model.ShortDescription; createModel.Status = model.Status; createModel.BookNumber = model.BookNumber; createModel.Description = model.Description; createModel.TargetWordCount = model.TargetWordCount; createModel.WritingStartDate = model.WritingStartDate; createModel.TargetCompletionDate = model.TargetCompletionDate; createModel.PublicationDate = model.PublicationDate; createModel.UsesExplicitScenes = model.UsesExplicitScenes; createModel.BeginMode = model.BeginMode; await books.PopulateEditContextAsync(createModel); return View("Edit", createModel); } if (!result.Succeeded) { model.BookID = result.BookID; ModelState.AddModelError(nameof(model.CoverUpload), result.ErrorMessage ?? "Cover image could not be uploaded."); await books.PopulateEditContextAsync(model); return View("Edit", model); } if (model.BookID == 0 && string.Equals(model.BeginMode, BookBeginModes.Import, StringComparison.Ordinal)) { await onboarding.BeginManuscriptImportForBookAsync(model.ProjectID, result.BookID); return RedirectToAction("Index", "Onboarding"); } return RedirectToAction(nameof(Details), new { id = result.BookID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task Archive(int id, int projectId) { await books.ArchiveAsync(id); TempData["ArchiveMessage"] = "Book archived. You can restore it from Archived Items."; return RedirectToAction("Details", "Projects", new { id = projectId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task UnlinkManuscript(int id) { var result = await books.UnlinkManuscriptAsync(id); TempData["ManuscriptMessage"] = result is null ? "Unable to unlink manuscript. It may already be unlinked, or you may not have access." : result.Message; return RedirectToAction(nameof(Details), new { id }); } }