Rename Project concept to Novel across the stack

Renames the domain concept from Project to Novel throughout the backend
(entities, DTOs, services, endpoints, ProjectAccessService/Permission,
ProjectId foreign keys), MCP server (tool names and routes), and the
React/Vite frontend (types, hooks, routes, components). Adds a new EF
Core migration (RenameProjectToNovel) using RenameTable/RenameColumn to
preserve existing data instead of dropping/recreating tables. Updates
CLAUDE.md's structure section to reference Novels/ instead of Projects/.
This commit is contained in:
James Wampler
2026-08-17 23:03:09 -07:00
parent 0ab4f568b5
commit 4313c8f206
95 changed files with 3192 additions and 1660 deletions
+25 -25
View File
@@ -2,21 +2,21 @@ using Novelly.Api.Beats;
using Novelly.Api.Chapters;
using Novelly.Api.Characters;
using Novelly.Api.Common;
using Novelly.Api.Projects;
using Novelly.Api.Novels;
namespace Novelly.Api.Tests;
[TestFixture]
public class CharacterArcTests : ServiceTestFixture
{
private Guid _projectId;
private Guid _novelId;
private Guid _characterId;
protected override void OnSetUp()
{
_projectId = Projects.CreateAsync(new CreateProjectRequest("The Salt Road")).Result.Id;
_novelId = Novels.CreateAsync(new CreateNovelRequest("The Salt Road")).Result.Id;
_characterId = Characters.CreateAsync(
_projectId,
_novelId,
new CreateCharacterRequest("Ines", CharacterRole.Protagonist, CharacterImportance.Main))
.Result.Id;
}
@@ -24,7 +24,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task A_character_is_supporting_until_promoted()
{
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
var mara = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Mara"));
Assert.That(mara.Importance, Is.EqualTo(CharacterImportance.Supporting));
@@ -37,7 +37,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task Importance_is_separate_from_the_part_a_character_plays()
{
var mentor = await Characters.CreateAsync(_projectId, new CreateCharacterRequest(
var mentor = await Characters.CreateAsync(_novelId, new CreateCharacterRequest(
"Anders", CharacterRole.Mentor, CharacterImportance.Main));
Assert.Multiple(() =>
@@ -50,11 +50,11 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task Main_characters_are_listed_before_supporting_ones()
{
await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Zeno"));
await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Zeno"));
await Characters.CreateAsync(
_projectId, new CreateCharacterRequest("Mara", Importance: CharacterImportance.Main));
_novelId, new CreateCharacterRequest("Mara", Importance: CharacterImportance.Main));
var listed = await Characters.ListAsync(_projectId);
var listed = await Characters.ListAsync(_novelId);
Assert.That(
listed.Select(c => c.Name),
@@ -64,12 +64,12 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task Within_a_group_the_lead_comes_before_the_second_lead()
{
await Characters.CreateAsync(_projectId, new CreateCharacterRequest(
await Characters.CreateAsync(_novelId, new CreateCharacterRequest(
"Mara", CharacterRole.Deuteragonist, CharacterImportance.Main));
await Characters.CreateAsync(_projectId, new CreateCharacterRequest(
await Characters.CreateAsync(_novelId, new CreateCharacterRequest(
"Anders", CharacterRole.Antagonist, CharacterImportance.Main));
var listed = await Characters.ListAsync(_projectId);
var listed = await Characters.ListAsync(_novelId);
Assert.That(
listed.Select(c => c.Name),
@@ -127,7 +127,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task A_stage_pinned_to_a_chapter_resolves_that_chapter()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var stage = await Arcs.CreateAsync(
_characterId, new CreateArcStageRequest("The map is wrong", ChapterId: chapter.Id));
@@ -140,21 +140,21 @@ public class CharacterArcTests : ServiceTestFixture
}
[Test]
public async Task A_stage_cannot_be_pinned_to_a_chapter_from_another_project()
public async Task A_stage_cannot_be_pinned_to_a_chapter_from_another_novel()
{
var other = await Projects.CreateAsync(new CreateProjectRequest("Other Book"));
var other = await Novels.CreateAsync(new CreateNovelRequest("Other Book"));
var elsewhere = await Chapters.CreateAsync(other.Id, new CreateChapterRequest("Elsewhere"));
Assert.That(
async () => await Arcs.CreateAsync(
_characterId, new CreateArcStageRequest("A stage", ChapterId: elsewhere.Id)),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same project"));
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same novel"));
}
[Test]
public async Task Deleting_a_chapter_unpins_an_arc_stage_rather_than_deleting_it()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var stage = await Arcs.CreateAsync(
_characterId, new CreateArcStageRequest("The map is wrong", ChapterId: chapter.Id));
@@ -188,9 +188,9 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task The_character_page_sees_every_beat_they_appear_in_across_the_book()
{
var second = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Second", Number: 2));
var first = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("First", Number: 1));
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
var second = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Second", Number: 2));
var first = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("First", Number: 1));
var mara = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Mara"));
await Beats.CreateAsync(second.Id, new CreateBeatRequest("She boards anyway", CharacterIds: [_characterId]));
await Beats.CreateAsync(first.Id, new CreateBeatRequest("She finds the map", CharacterIds: [_characterId]));
@@ -215,7 +215,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task An_arc_stage_groups_the_beats_assigned_to_it()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var spoiled = await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Snaps at the crew", CharacterIds: [_characterId]));
var humbled = await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Learns to swab a deck", CharacterIds: [_characterId]));
var stage = await Arcs.CreateAsync(_characterId, new CreateArcStageRequest("Spoiled noble"));
@@ -229,7 +229,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task Assigning_a_beat_to_a_stage_moves_it_out_of_the_characters_other_stage()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var beat = await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Gets hurt", CharacterIds: [_characterId]));
var early = await Arcs.CreateAsync(_characterId, new CreateArcStageRequest("Spoiled noble"));
var later = await Arcs.CreateAsync(_characterId, new CreateArcStageRequest("Humbled"));
@@ -250,8 +250,8 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task A_beat_can_only_be_grouped_into_a_stage_for_a_character_who_appears_in_it()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var mara = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Mara"));
var beat = await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Mara alone", CharacterIds: [mara.Id]));
var stage = await Arcs.CreateAsync(_characterId, new CreateArcStageRequest("Spoiled noble"));
@@ -269,7 +269,7 @@ public class CharacterArcTests : ServiceTestFixture
[Test]
public async Task Clearing_a_stages_beats_with_an_empty_list_ungroups_them()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
var chapter = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall"));
var beat = await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Snaps at the crew", CharacterIds: [_characterId]));
var stage = await Arcs.CreateAsync(_characterId, new CreateArcStageRequest("Spoiled noble"));
await Arcs.SetBeatsAsync(stage.Id, new SetArcStageBeatsRequest([beat!.Id]));