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
+20 -20
View File
@@ -1,20 +1,20 @@
using Novelly.Api.Beats;
using Novelly.Api.Chapters;
using Novelly.Api.Characters;
using Novelly.Api.Projects;
using Novelly.Api.Novels;
namespace Novelly.Api.Tests;
[TestFixture]
public class BeatServiceTests : ServiceTestFixture
{
private Guid _projectId;
private Guid _novelId;
private Guid _chapterId;
protected override void OnSetUp()
{
_projectId = Projects.CreateAsync(new CreateProjectRequest("The Salt Road")).Result.Id;
_chapterId = Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall")).Result.Id;
_novelId = Novels.CreateAsync(new CreateNovelRequest("The Salt Road")).Result.Id;
_chapterId = Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall")).Result.Id;
}
[Test]
@@ -100,8 +100,8 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task A_beat_can_carry_several_characters()
{
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
var ines = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines"));
var mara = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Mara"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest(
"She burns the atlas",
@@ -117,22 +117,22 @@ public class BeatServiceTests : ServiceTestFixture
}
[Test]
public async Task A_beat_cannot_borrow_a_character_from_another_project()
public async Task A_beat_cannot_borrow_a_character_from_another_novel()
{
var other = await Projects.CreateAsync(new CreateProjectRequest("Other Book"));
var other = await Novels.CreateAsync(new CreateNovelRequest("Other Book"));
var stranger = await Characters.CreateAsync(other.Id, new CreateCharacterRequest("Stranger"));
Assert.That(
async () => await Beats.CreateAsync(
_chapterId, new CreateBeatRequest("A beat", CharacterIds: [stranger.Id])),
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same project"));
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same novel"));
}
[Test]
public async Task Assigning_a_character_to_several_beats_leaves_their_other_characters_alone()
{
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
var ines = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines"));
var mara = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Mara"));
var first = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First", CharacterIds: [ines.Id]));
var second = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("Second"));
@@ -153,7 +153,7 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task Assigning_a_character_already_on_a_beat_does_not_duplicate_it()
{
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
var ines = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First", CharacterIds: [ines.Id]));
var assigned = await Beats.AssignCharacterAsync(
@@ -163,9 +163,9 @@ public class BeatServiceTests : ServiceTestFixture
}
[Test]
public async Task Assigning_a_character_from_another_project_returns_null()
public async Task Assigning_a_character_from_another_novel_returns_null()
{
var other = await Projects.CreateAsync(new CreateProjectRequest("Other Book"));
var other = await Novels.CreateAsync(new CreateNovelRequest("Other Book"));
var stranger = await Characters.CreateAsync(other.Id, new CreateCharacterRequest("Stranger"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First"));
@@ -177,7 +177,7 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task Assigning_to_an_unknown_beat_returns_null_rather_than_partially_applying()
{
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
var ines = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First"));
var result = await Beats.AssignCharacterAsync(
@@ -190,7 +190,7 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task Moving_beats_appends_them_to_the_end_of_the_target_chapter()
{
var other = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Second landfall"));
var other = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Second landfall"));
await Beats.CreateAsync(other.Id, new CreateBeatRequest("Already there"));
var first = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First"));
var second = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("Second"));
@@ -216,9 +216,9 @@ public class BeatServiceTests : ServiceTestFixture
}
[Test]
public async Task Moving_to_a_chapter_in_another_project_returns_null()
public async Task Moving_to_a_chapter_in_another_novel_returns_null()
{
var other = await Projects.CreateAsync(new CreateProjectRequest("Other Book"));
var other = await Novels.CreateAsync(new CreateNovelRequest("Other Book"));
var otherChapter = await Chapters.CreateAsync(other.Id, new CreateChapterRequest("Elsewhere"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First"));
@@ -228,7 +228,7 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task Moving_an_unknown_beat_returns_null_rather_than_partially_applying()
{
var other = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Second landfall"));
var other = await Chapters.CreateAsync(_novelId, new CreateChapterRequest("Second landfall"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest("First"));
var result = await Beats.MoveAsync(_chapterId, new MoveBeatsRequest(other.Id, [beat.Id, Guid.NewGuid()]));
@@ -265,7 +265,7 @@ public class BeatServiceTests : ServiceTestFixture
[Test]
public async Task An_empty_CharacterIds_list_clears_a_beats_characters_since_null_means_leave_it_alone()
{
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
var ines = await Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines"));
var beat = await Beats.CreateAsync(_chapterId, new CreateBeatRequest(
"She burns the atlas", CharacterIds: [ines.Id]));