Files
novelly/src/Novelly.Api/Chapters/ChapterEndpoints.cs
T
James Wampler aca26588f9
CI / build-and-push (push) Failing after 31s
CI / deploy (push) Has been skipped
Add soft delete + trash, keyboard-first web overhaul, move chapter tags to bottom
Adds SoftDelete/Trash across characters, chapters, locations, beats with a
purge schedule and Trash page. Reworks the web client for keyboard-driven
navigation (focus helpers, help overlay, keyboard.md doc). Moves the
ChapterPage tag editor to the bottom of the page to match CharacterDetailPage.
2026-08-20 16:39:09 -07:00

76 lines
3.1 KiB
C#

using Novelly.Api.Common;
using Novelly.Api.Common.Validation;
namespace Novelly.Api.Chapters;
public static class ChapterEndpoints
{
public static IEndpointRouteBuilder MapChapterEndpoints(this IEndpointRouteBuilder app)
{
var novelScoped = app.MapGroup("/api/novels/{novelId:guid}/chapters").WithTags("Chapters")
.AddEndpointFilter<RequestLoggingEndpointFilter>()
.AddEndpointFilter<ValidationEndpointFilter>();
novelScoped.MapGet("/", async (Guid novelId, ChapterService service, CancellationToken ct) =>
{
var chapters = await service.ListAsync(novelId, ct);
var displayNumbers = ChapterNumbering.DisplayNumbers(chapters);
return Results.Ok(chapters.Select(c =>
c.ToSummaryResponse(displayNumbers.TryGetValue(c.Id, out var n) ? n : null)));
})
.WithSummary("List a novel's chapters in manuscript order.");
novelScoped.MapPost("/", async (
Guid novelId, CreateChapterRequest request, ChapterService service, CancellationToken ct) =>
{
var chapter = await service.CreateAsync(novelId, request, ct);
if (chapter is null)
{
return Results.NotFound();
}
var displayNumber = await service.DisplayNumberAsync(chapter, ct);
var created = chapter.ToResponse(displayNumber);
return Results.Created($"/api/chapters/{created.Id}", created);
})
.WithSummary("Add a chapter.");
var chapters = app.MapGroup("/api/chapters").WithTags("Chapters")
.AddEndpointFilter<RequestLoggingEndpointFilter>()
.AddEndpointFilter<ValidationEndpointFilter>();
chapters.MapGet("/{id:guid}", async (Guid id, ChapterService service, CancellationToken ct) =>
{
var chapter = await service.GetAsync(id, ct);
if (chapter is null)
{
return Results.NotFound();
}
var displayNumber = await service.DisplayNumberAsync(chapter, ct);
return chapter.ToResponse(displayNumber).ToApiResult();
})
.WithSummary("Read a chapter with its beats and prose.");
chapters.MapPatch("/{id:guid}", async (
Guid id, UpdateChapterRequest request, ChapterService service, CancellationToken ct) =>
{
var chapter = await service.UpdateAsync(id, request, ct);
if (chapter is null)
{
return Results.NotFound();
}
var displayNumber = await service.DisplayNumberAsync(chapter, ct);
return chapter.ToResponse(displayNumber).ToApiResult();
})
.WithSummary("Update a chapter.");
chapters.MapDelete("/{id:guid}", async (Guid id, ChapterService service, CancellationToken ct) =>
await service.DeleteAsync(id, ct) ? Results.NoContent() : Results.NotFound())
.WithSummary("Move a chapter to the trash.");
return app;
}
}