Add outline import feature; drop Dto naming, map entities at the API boundary

Services now return entities; endpoints (and the agent toolsets) map to
*Response records instead of services building wire DTOs themselves.
Also brings in the outline-import agent, MCP tool, ledger and web dialog
that were already in progress on disk.
This commit is contained in:
James Wampler
2026-08-06 18:36:40 -07:00
parent 40f93e40a8
commit 189ebf3237
66 changed files with 3310 additions and 364 deletions
+8 -10
View File
@@ -14,13 +14,13 @@ public class ChapterService(
IModelValidator<CreateChapterRequest> createValidator,
IModelValidator<UpdateChapterRequest> updateValidator)
{
public async Task<IReadOnlyList<ChapterSummaryDto>> ListAsync(Guid projectId, CancellationToken ct = default)
public async Task<IReadOnlyList<Chapter>> ListAsync(Guid projectId, CancellationToken ct = default)
{
Guard.Default(projectId, nameof(projectId));
logger.LogInformation("Listing chapters for project {ProjectId}", projectId);
var chapters = await db.Chapters
return await db.Chapters
.Include(c => c.PovCharacter)
.Include(c => c.Beats)
.Include(c => c.Scenes)
@@ -28,20 +28,18 @@ public class ChapterService(
.Where(c => c.ProjectId == projectId)
.OrderBy(c => c.Number)
.ToListAsync(ct);
return [.. chapters.Select(c => c.ToSummaryDto())];
}
/// <summary>Null when no chapter has this id — a lookup miss is expected, not exceptional.</summary>
public async Task<ChapterDto?> GetAsync(Guid id, CancellationToken ct = default)
public async Task<Chapter?> GetAsync(Guid id, CancellationToken ct = default)
{
Guard.Default(id, nameof(id));
logger.LogInformation("Getting chapter {ChapterId}", id);
return (await FindAsync(id, ct))?.ToDto();
return await FindAsync(id, ct);
}
public async Task<ChapterDto> CreateAsync(Guid projectId, CreateChapterRequest request, CancellationToken ct = default)
public async Task<Chapter> CreateAsync(Guid projectId, CreateChapterRequest request, CancellationToken ct = default)
{
Guard.Default(projectId, nameof(projectId));
Guard.Null(request, nameof(request));
@@ -77,10 +75,10 @@ public class ChapterService(
await db.SaveChangesAsync(ct);
// Just created it — the reload is only to pick up includes, not to check existence.
return (await FindAsync(chapter.Id, ct))!.ToDto();
return (await FindAsync(chapter.Id, ct))!;
}
public async Task<ChapterDto?> UpdateAsync(Guid id, UpdateChapterRequest request, CancellationToken ct = default)
public async Task<Chapter?> UpdateAsync(Guid id, UpdateChapterRequest request, CancellationToken ct = default)
{
Guard.Default(id, nameof(id));
Guard.Null(request, nameof(request));
@@ -110,7 +108,7 @@ public class ChapterService(
}
await db.SaveChangesAsync(ct);
return (await FindAsync(id, ct))!.ToDto();
return (await FindAsync(id, ct))!;
}
/// <summary>True if a chapter was deleted; false if no chapter had this id.</summary>