Reorganise by feature, rename to Novelly, add Aspire and a pre-push hook
The layered split into Domain/Application/Infrastructure/Api was forcing organisation by layer: adding one capability meant touching four projects and four folders that each held a slice of it. Those four projects are now one feature-organised Novelly.Api, where each folder — Projects, Characters, Chapters, Beats, Scenes, Tags, Agent — holds its entity, DTOs, service and endpoints together. Common/ holds what genuinely crosses features (the patch semantics, the two exception types, DraftStatus) and Data/ holds the DbContext and migrations. Six .NET projects become five: the three layer projects are gone, and Novelly.AppHost and Novelly.ServiceDefaults are new. - Namespaces move from NovelSoftware.* to Novelly.*, including the entity type names recorded in the EF model snapshots. The migration ids are untouched, so an existing novel.db still migrates cleanly — verified against a fresh file. - Aspire orchestration mirrors the mic-check setup: the AppHost starts the API on :5080 and the Vite dev server on :5173, and the API picks up OpenTelemetry, health checks and service discovery from ServiceDefaults. /health and /alive now answer in development. - A Husky pre-push hook runs scripts/ci/prepush.sh: build, test, then a web build. The scripts are plain bash so CI can run the same steps. - The MCP server's env var is now NOVELLY_API_URL. Verified beyond the build: 44 tests pass, the web client builds, the API was exercised over curl (project/chapter/beat/tag round trip, tag cross-reference, 503 on the agent without a key while conversation listing still returns 200), the MCP server was driven over stdio JSON-RPC (26 tools, errors still surface the API's own message rather than being flattened), and the AppHost was run to confirm both resources come up and Vite proxies /api through to the API. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S56bfZMGe1hnhpWP4CjjNw
This commit is contained in:
co-authored by
Claude Opus 5
parent
30e0c6926e
commit
725758ccd9
+5
-1
@@ -428,10 +428,14 @@ FodyWeavers.xsd
|
|||||||
*.msm
|
*.msm
|
||||||
*.msp
|
*.msp
|
||||||
|
|
||||||
## Novel Software
|
## Novelly
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
*.db
|
*.db
|
||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
mcp-server/
|
mcp-server/
|
||||||
|
|
||||||
|
# Toolchains installed locally by scripts/ci/lib.sh
|
||||||
|
.dotnet/
|
||||||
|
.node/
|
||||||
|
|||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
exec ./scripts/ci/prepush.sh
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"novel-software": {
|
"novelly": {
|
||||||
"command": "./mcp-server/NovelSoftware.Mcp",
|
"command": "./mcp-server/Novelly.Mcp",
|
||||||
"env": {
|
"env": {
|
||||||
"NOVELSOFTWARE_API_URL": "http://localhost:5080"
|
"NOVELLY_API_URL": "http://localhost:5080"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,28 @@ Guidance for Claude Code (claude.ai/code) in this repo.
|
|||||||
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
Novel Software: software for planning and writing a novel. ASP.NET Core 10, C#, TypeScript, React. Chapter outlines, character dossiers, prose drafting, an embedded Claude agent, and an MCP server over the same API.
|
Novelly: software for planning and writing a novel. ASP.NET Core 10, C#, TypeScript, React, .NET Aspire. Chapter outlines, character dossiers, prose drafting, an embedded Claude agent, and an MCP server over the same API.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- `src/NovelSoftware.Domain/` — entities and enums, no dependencies
|
- `src/Novelly.Api/` — the whole back end, organised by feature. One folder per feature holds its
|
||||||
- `src/NovelSoftware.Application/` — services, DTOs, the agent tool-use loop
|
entity, DTOs, service and endpoints together: `Projects/`, `Characters/`, `Chapters/`, `Beats/`,
|
||||||
- `src/NovelSoftware.Infrastructure/` — EF Core + SQLite, Anthropic SDK client
|
`Scenes/`, `Tags/`, `Agent/`. `Common/` holds what genuinely crosses features; `Data/` holds the
|
||||||
- `src/NovelSoftware.Api/` — minimal API endpoints
|
`DbContext` and EF migrations.
|
||||||
- `src/NovelSoftware.Mcp/` — MCP stdio server
|
- `src/Novelly.AppHost/` — .NET Aspire orchestration; run this to bring up the API and the web client
|
||||||
- `src/NovelSoftware.Web/` — React + Vite client
|
- `src/Novelly.ServiceDefaults/` — shared Aspire wiring: OpenTelemetry, health checks, service discovery
|
||||||
|
- `src/Novelly.Mcp/` — MCP stdio server
|
||||||
|
- `src/Novelly.Web/` — React + Vite client
|
||||||
- `tests/` — test suite
|
- `tests/` — test suite
|
||||||
- `docs/` — documentation
|
- `docs/` — documentation
|
||||||
|
- `scripts/ci/` — bash CI steps; `prepush.sh` is what the Husky pre-push hook runs
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
- Use latest .NET + latest supported nuget packages for that version
|
- Use latest .NET + latest supported nuget packages for that version
|
||||||
- Set `langVersion` to latest in all csproj files; enable nullable
|
- Set `langVersion` to latest in all csproj files; enable nullable
|
||||||
- Organize code by feature/area, not type
|
- Organize code by feature/area, not layer or type. A new capability adds files to one feature folder
|
||||||
|
rather than a row to each of an entity/DTO/service/endpoint folder
|
||||||
- New features need unit tests covering logic as much as possible
|
- New features need unit tests covering logic as much as possible
|
||||||
- Modified file: check missing test coverage, all tests pass
|
- Modified file: check missing test coverage, all tests pass
|
||||||
|
|
||||||
@@ -64,8 +68,10 @@ Novel Software: software for planning and writing a novel. ASP.NET Core 10, C#,
|
|||||||
|
|
||||||
Build and tests passing is not the same as working. For anything touching an endpoint, the agent loop, or the MCP server, run it:
|
Build and tests passing is not the same as working. For anything touching an endpoint, the agent loop, or the MCP server, run it:
|
||||||
|
|
||||||
- API: `ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/NovelSoftware.Api`, then exercise the route with curl
|
- Everything at once: `dotnet run --project src/Novelly.AppHost` — Aspire starts the API on :5080 and
|
||||||
- Web: `cd src/NovelSoftware.Web && npm run dev` — proxies `/api` to :5080
|
the Vite dev server on :5173, with the dashboard for logs and traces
|
||||||
|
- API alone: `ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`, then exercise the route with curl
|
||||||
|
- Web alone: `cd src/Novelly.Web && npm run dev` — proxies `/api` to :5080
|
||||||
- MCP: build it, then drive it over stdio JSON-RPC (`initialize` → `notifications/initialized` → `tools/list` → `tools/call`)
|
- MCP: build it, then drive it over stdio JSON-RPC (`initialize` → `notifications/initialized` → `tools/list` → `tools/call`)
|
||||||
|
|
||||||
Several real bugs here — SQLite refusing to ORDER BY a DateTimeOffset, the agent's model client throwing at construction and taking read-only endpoints down with it — passed the build and the test suite and only showed up when the app actually ran.
|
Several real bugs here — SQLite refusing to ORDER BY a DateTimeOffset, the agent's model client throwing at construction and taking read-only endpoints down with it — passed the build and the test suite and only showed up when the app actually ran.
|
||||||
@@ -83,4 +89,6 @@ Several real bugs here — SQLite refusing to ORDER BY a DateTimeOffset, the age
|
|||||||
|
|
||||||
- Anthropic model id lives in `appsettings.json` under `Agent:Model`. Don't hardcode it.
|
- Anthropic model id lives in `appsettings.json` under `Agent:Model`. Don't hardcode it.
|
||||||
- API key comes from `ANTHROPIC_API_KEY` or `Agent:ApiKey` — never commit one. The app must stay fully usable without a key; only the agent endpoints require it.
|
- API key comes from `ANTHROPIC_API_KEY` or `Agent:ApiKey` — never commit one. The app must stay fully usable without a key; only the agent endpoints require it.
|
||||||
- EF migrations: `dotnet ef migrations add <Name> -p src/NovelSoftware.Infrastructure -s src/NovelSoftware.Api -o Persistence/Migrations`. The API migrates on boot.
|
- EF migrations: `dotnet ef migrations add <Name> -p src/Novelly.Api -o Data/Migrations`. The API migrates on boot.
|
||||||
|
- `git push` runs `scripts/ci/prepush.sh` through Husky: build, test, then a web build. Run `npm install`
|
||||||
|
once at the repo root to install the hook.
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<Solution>
|
|
||||||
<Folder Name="/src/">
|
|
||||||
<Project Path="src/NovelSoftware.Api/NovelSoftware.Api.csproj" />
|
|
||||||
<Project Path="src/NovelSoftware.Application/NovelSoftware.Application.csproj" />
|
|
||||||
<Project Path="src/NovelSoftware.Domain/NovelSoftware.Domain.csproj" />
|
|
||||||
<Project Path="src/NovelSoftware.Infrastructure/NovelSoftware.Infrastructure.csproj" />
|
|
||||||
<Project Path="src/NovelSoftware.Mcp/NovelSoftware.Mcp.csproj" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/tests/">
|
|
||||||
<Project Path="tests/NovelSoftware.Tests/NovelSoftware.Tests.csproj" />
|
|
||||||
</Folder>
|
|
||||||
</Solution>
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/Solution Items/">
|
||||||
|
<File Path=".editorconfig" />
|
||||||
|
<File Path="CLAUDE.md" />
|
||||||
|
<File Path="README.md" />
|
||||||
|
<File Path="package.json" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/src/">
|
||||||
|
<Project Path="src/Novelly.Api/Novelly.Api.csproj" />
|
||||||
|
<Project Path="src/Novelly.AppHost/Novelly.AppHost.csproj" />
|
||||||
|
<Project Path="src/Novelly.Mcp/Novelly.Mcp.csproj" />
|
||||||
|
<Project Path="src/Novelly.ServiceDefaults/Novelly.ServiceDefaults.csproj" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/tests/">
|
||||||
|
<Project Path="tests/Novelly.Api.Tests/Novelly.Api.Tests.csproj" />
|
||||||
|
</Folder>
|
||||||
|
</Solution>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Novel Software
|
# Novelly
|
||||||
|
|
||||||
Software for planning and writing a novel. You outline the book, keep character
|
Software for planning and writing a novel. You outline the book, keep character
|
||||||
dossiers, break chapters into scenes, and draft prose — with a Claude-powered agent
|
dossiers, break chapters into scenes, and draft prose — with a Claude-powered agent
|
||||||
@@ -14,23 +14,40 @@ same edit.
|
|||||||
|
|
||||||
| Piece | Built with |
|
| Piece | Built with |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `NovelSoftware.Api` | ASP.NET Core 10 minimal APIs, OpenAPI |
|
| `Novelly.Api` | ASP.NET Core 10 minimal APIs, EF Core 10 + SQLite, Anthropic SDK, OpenAPI |
|
||||||
| `NovelSoftware.Application` | Services, DTOs, the agent tool-use loop |
|
| `Novelly.AppHost` | .NET Aspire orchestration for the API and the web client |
|
||||||
| `NovelSoftware.Domain` | Entities and enums, no dependencies |
|
| `Novelly.ServiceDefaults` | Shared OpenTelemetry, health checks and service discovery |
|
||||||
| `NovelSoftware.Infrastructure` | EF Core 10 + SQLite, Anthropic SDK client |
|
| `Novelly.Mcp` | MCP stdio server (`ModelContextProtocol`) |
|
||||||
| `NovelSoftware.Mcp` | MCP stdio server (`ModelContextProtocol`) |
|
| `Novelly.Web` | React 19, TypeScript, Vite, TanStack Query, Tailwind v4 |
|
||||||
| `NovelSoftware.Web` | React 19, TypeScript, Vite, TanStack Query, Tailwind v4 |
|
|
||||||
|
The back end is one project organised by feature, not by layer. Each feature folder —
|
||||||
|
`Projects/`, `Characters/`, `Chapters/`, `Beats/`, `Scenes/`, `Tags/`, `Agent/` — holds its
|
||||||
|
entity, DTOs, service and endpoints together, so adding a capability means touching one
|
||||||
|
folder rather than four. `Common/` holds what genuinely crosses features and `Data/` holds
|
||||||
|
the `DbContext` and migrations.
|
||||||
|
|
||||||
## Running it
|
## Running it
|
||||||
|
|
||||||
Prerequisites: .NET 10 SDK and Node 20+.
|
Prerequisites: .NET 10 SDK and Node 20+.
|
||||||
|
|
||||||
|
Everything at once, through Aspire:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/Novelly.Web && npm install && cd -
|
||||||
|
dotnet run --project src/Novelly.AppHost
|
||||||
|
```
|
||||||
|
|
||||||
|
That starts the API on :5080 and the Vite dev server on :5173, and opens the Aspire
|
||||||
|
dashboard for logs, traces and metrics across both.
|
||||||
|
|
||||||
|
Or run the two halves separately:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. API — creates and migrates novel.db on first run, listens on :5080
|
# 1. API — creates and migrates novel.db on first run, listens on :5080
|
||||||
ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/NovelSoftware.Api
|
ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api
|
||||||
|
|
||||||
# 2. Web — dev server on :5173, proxies /api to :5080
|
# 2. Web — dev server on :5173, proxies /api to :5080
|
||||||
cd src/NovelSoftware.Web && npm install && npm run dev
|
cd src/Novelly.Web && npm install && npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open http://localhost:5173.
|
Open http://localhost:5173.
|
||||||
@@ -48,16 +65,19 @@ and everything else keeps working.
|
|||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet test # 44 tests
|
dotnet test # 44 tests
|
||||||
cd src/NovelSoftware.Web && npm run build # typecheck + bundle
|
cd src/Novelly.Web && npm run build # typecheck + bundle
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`git push` runs both through a Husky pre-push hook (`scripts/ci/prepush.sh`). Install the
|
||||||
|
hook once with `npm install` at the repo root.
|
||||||
|
|
||||||
Tests run against real in-memory SQLite rather than the EF in-memory provider, so they
|
Tests run against real in-memory SQLite rather than the EF in-memory provider, so they
|
||||||
exercise the cascade deletes and query translation the app actually ships with.
|
exercise the cascade deletes and query translation the app actually ships with.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`src/NovelSoftware.Api/appsettings.json`:
|
`src/Novelly.Api/appsettings.json`:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
@@ -139,7 +159,7 @@ its own — it is a second front end, not a second implementation.
|
|||||||
Build it, then point your MCP client at the produced binary:
|
Build it, then point your MCP client at the produced binary:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet publish src/NovelSoftware.Mcp -c Release -o ./mcp-server
|
dotnet publish src/Novelly.Mcp -c Release -o ./mcp-server
|
||||||
```
|
```
|
||||||
|
|
||||||
`.mcp.json` (or Claude Desktop's config):
|
`.mcp.json` (or Claude Desktop's config):
|
||||||
@@ -147,9 +167,9 @@ dotnet publish src/NovelSoftware.Mcp -c Release -o ./mcp-server
|
|||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"novel-software": {
|
"novelly": {
|
||||||
"command": "/absolute/path/to/mcp-server/NovelSoftware.Mcp",
|
"command": "/absolute/path/to/mcp-server/Novelly.Mcp",
|
||||||
"env": { "NOVELSOFTWARE_API_URL": "http://localhost:5080" }
|
"env": { "NOVELLY_API_URL": "http://localhost:5080" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "novelly",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "novelly",
|
||||||
|
"devDependencies": {
|
||||||
|
"husky": "^9.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/husky": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"husky": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/typicode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "novelly",
|
||||||
|
"private": true,
|
||||||
|
"description": "Repo-root package - hosts the Husky pre-push hook only.",
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "husky"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"husky": "^9.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared helpers for scripts/ci/*.sh. Everything here is plain bash so the same script
|
||||||
|
# runs identically on a developer's machine and on a bare CI runner.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Repo root, regardless of the caller's cwd.
|
||||||
|
CI_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
# Prepends a locally installed .NET SDK to PATH when `dotnet` is missing, so a runner
|
||||||
|
# with no SDK preinstalled behaves the same as a dev machine.
|
||||||
|
ensure_dotnet() {
|
||||||
|
if command -v dotnet > /dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local install_dir="$CI_ROOT/.dotnet"
|
||||||
|
if [[ ! -x "$install_dir/dotnet" ]]; then
|
||||||
|
log "dotnet not found on PATH; installing the .NET SDK"
|
||||||
|
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "/tmp/dotnet-install.sh"
|
||||||
|
bash "/tmp/dotnet-install.sh" --channel STS --install-dir "$install_dir"
|
||||||
|
rm -f "/tmp/dotnet-install.sh"
|
||||||
|
fi
|
||||||
|
export PATH="$install_dir:$PATH"
|
||||||
|
export DOTNET_ROOT="$install_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_node() {
|
||||||
|
command -v npm > /dev/null 2>&1 || fail "npm not found on PATH; install Node.js to build the web client"
|
||||||
|
}
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Husky pre-push hook body. Compiles the solution, runs the test suite and builds the
|
||||||
|
# web client, so broken code never leaves the workstation.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")" && source ./lib.sh
|
||||||
|
cd "$CI_ROOT"
|
||||||
|
|
||||||
|
ensure_dotnet
|
||||||
|
ensure_node
|
||||||
|
|
||||||
|
log "Building the solution (Debug)"
|
||||||
|
dotnet build Novelly.slnx -c Debug
|
||||||
|
|
||||||
|
log "Running Novelly.Api.Tests"
|
||||||
|
dotnet test tests/Novelly.Api.Tests/Novelly.Api.Tests.csproj -c Debug
|
||||||
|
|
||||||
|
log "Installing web dependencies (npm ci)"
|
||||||
|
npm --prefix src/Novelly.Web ci
|
||||||
|
|
||||||
|
log "Building the web client (vite build)"
|
||||||
|
npm --prefix src/Novelly.Web run build
|
||||||
|
|
||||||
|
log "prepush.sh complete - ok to push"
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NovelSoftware.Infrastructure\NovelSoftware.Infrastructure.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="2.11.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NovelSoftware.Domain\NovelSoftware.Domain.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.10" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.10" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace NovelSoftware.Domain;
|
|
||||||
|
|
||||||
/// <summary>The role a character plays in the story.</summary>
|
|
||||||
public enum CharacterRole
|
|
||||||
{
|
|
||||||
Protagonist,
|
|
||||||
Antagonist,
|
|
||||||
Deuteragonist,
|
|
||||||
Supporting,
|
|
||||||
Minor,
|
|
||||||
Mentor,
|
|
||||||
LoveInterest,
|
|
||||||
Foil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>How far along a chapter or scene is in the drafting pipeline.</summary>
|
|
||||||
public enum DraftStatus
|
|
||||||
{
|
|
||||||
Planned,
|
|
||||||
Outlined,
|
|
||||||
Drafted,
|
|
||||||
Revised,
|
|
||||||
Final
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Who produced a message in an agent conversation.</summary>
|
|
||||||
public enum AgentRole
|
|
||||||
{
|
|
||||||
User,
|
|
||||||
Assistant
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Agent;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>A tool the model may call, described in the shape the Messages API expects.</summary>
|
/// <summary>A tool the model may call, described in the shape the Messages API expects.</summary>
|
||||||
public record AgentToolDefinition(string Name, string Description, JsonElement InputSchema);
|
public record AgentToolDefinition(string Name, string Description, JsonElement InputSchema);
|
||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Projects;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>A chat thread between the writer and the embedded agent, scoped to one project.</summary>
|
/// <summary>A chat thread between the writer and the embedded agent, scoped to one project.</summary>
|
||||||
public class AgentConversation
|
public class AgentConversation
|
||||||
+1
-3
@@ -1,6 +1,4 @@
|
|||||||
using NovelSoftware.Domain;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
|
||||||
|
|
||||||
public record ConversationSummaryDto(
|
public record ConversationSummaryDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Agent;
|
namespace Novelly.Api.Agent;
|
||||||
using NovelSoftware.Application.Dtos;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class AgentEndpoints
|
public static class AgentEndpoints
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
|
/// <summary>Who produced a message in an agent conversation.</summary>
|
||||||
|
public enum AgentRole
|
||||||
|
{
|
||||||
|
User,
|
||||||
|
Assistant
|
||||||
|
}
|
||||||
+3
-4
@@ -1,11 +1,10 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Anthropic;
|
|
||||||
using Anthropic.Models.Messages;
|
using Anthropic.Models.Messages;
|
||||||
|
using Anthropic;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NovelSoftware.Application;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Application.Agent;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Anthropic;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Talks to the Anthropic Messages API. Translates between the application's
|
/// Talks to the Anthropic Messages API. Translates between the application's
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Agent;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Small builder for the JSON Schema objects tool definitions need. Hand-writing these
|
/// Small builder for the JSON Schema objects tool definitions need. Hand-writing these
|
||||||
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain;
|
using Novelly.Api.Data;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Projects;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Agent;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The embedded writing agent. Runs the tool-use loop against the model, persists the
|
/// The embedded writing agent. Runs the tool-use loop against the model, persists the
|
||||||
+8
-4
@@ -1,9 +1,13 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Beats;
|
||||||
using NovelSoftware.Application.Services;
|
using Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Domain;
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Agent;
|
namespace Novelly.Api.Agent;
|
||||||
|
|
||||||
/// <summary>The outcome of running a tool: what to hand back to the model, and whether it failed.</summary>
|
/// <summary>The outcome of running a tool: what to hand back to the model, and whether it failed.</summary>
|
||||||
public record AgentToolResult(string Content, bool IsError);
|
public record AgentToolResult(string Content, bool IsError);
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Beats;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One row of a chapter's outline: a short label, who it belongs to, what happened, and
|
/// One row of a chapter's outline: a short label, who it belongs to, what happened, and
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Beats;
|
||||||
|
|
||||||
public record BeatDto(
|
public record BeatDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Beats;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class BeatEndpoints
|
public static class BeatEndpoints
|
||||||
{
|
{
|
||||||
+5
-3
@@ -1,8 +1,10 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Data;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Beats;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beats are a chapter's outline: a flat, ordered table rather than a tree. Everything
|
/// Beats are a chapter's outline: a flat, ordered table rather than a tree. Everything
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Beats;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Chapters;
|
||||||
|
|
||||||
/// <summary>A chapter: an ordered container of scenes plus its own planning fields.</summary>
|
/// <summary>A chapter: an ordered container of scenes plus its own planning fields.</summary>
|
||||||
public class Chapter
|
public class Chapter
|
||||||
+5
-3
@@ -1,7 +1,9 @@
|
|||||||
using NovelSoftware.Domain;
|
using Novelly.Api.Beats;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Chapters;
|
||||||
|
|
||||||
public record ChapterSummaryDto(
|
public record ChapterSummaryDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class ChapterEndpoints
|
public static class ChapterEndpoints
|
||||||
{
|
{
|
||||||
+5
-3
@@ -1,8 +1,10 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Data;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Chapters;
|
||||||
|
|
||||||
public class ChapterService(INovelDbContext db, TagService tags)
|
public class ChapterService(INovelDbContext db, TagService tags)
|
||||||
{
|
{
|
||||||
+4
-1
@@ -1,4 +1,7 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Characters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A character dossier. Every field beyond <see cref="Name"/> is optional so a writer can
|
/// A character dossier. Every field beyond <see cref="Name"/> is optional so a writer can
|
||||||
+2
-3
@@ -1,7 +1,6 @@
|
|||||||
using NovelSoftware.Domain;
|
using Novelly.Api.Tags;
|
||||||
using NovelSoftware.Domain.Entities;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Characters;
|
||||||
|
|
||||||
public record CharacterDto(
|
public record CharacterDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Characters;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class CharacterEndpoints
|
public static class CharacterEndpoints
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Novelly.Api.Characters;
|
||||||
|
|
||||||
|
/// <summary>The role a character plays in the story.</summary>
|
||||||
|
public enum CharacterRole
|
||||||
|
{
|
||||||
|
Protagonist,
|
||||||
|
Antagonist,
|
||||||
|
Deuteragonist,
|
||||||
|
Supporting,
|
||||||
|
Minor,
|
||||||
|
Mentor,
|
||||||
|
LoveInterest,
|
||||||
|
Foil
|
||||||
|
}
|
||||||
+5
-3
@@ -1,8 +1,10 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Data;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Characters;
|
||||||
|
|
||||||
public class CharacterService(INovelDbContext db, TagService tags)
|
public class CharacterService(INovelDbContext db, TagService tags)
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace NovelSoftware.Application;
|
namespace Novelly.Api.Common;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Thrown when the agent is asked to run but has no model credentials. This is a
|
/// Thrown when the agent is asked to run but has no model credentials. This is a
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Novelly.Api.Common;
|
||||||
|
|
||||||
|
/// <summary>How far along a chapter or scene is in the drafting pipeline.</summary>
|
||||||
|
public enum DraftStatus
|
||||||
|
{
|
||||||
|
Planned,
|
||||||
|
Outlined,
|
||||||
|
Drafted,
|
||||||
|
Revised,
|
||||||
|
Final
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace NovelSoftware.Application;
|
namespace Novelly.Api.Common;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Thrown when a service is asked for an entity that does not exist. The API translates
|
/// Thrown when a service is asked for an entity that does not exist. The API translates
|
||||||
+16
-10
@@ -1,17 +1,23 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Novelly.Api.Agent;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Novelly.Api.Beats;
|
||||||
using NovelSoftware.Application;
|
using Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Application.Agent;
|
using Novelly.Api.Characters;
|
||||||
using NovelSoftware.Application.Services;
|
using Novelly.Api.Data;
|
||||||
using NovelSoftware.Infrastructure.Anthropic;
|
using Novelly.Api.Projects;
|
||||||
using NovelSoftware.Infrastructure.Persistence;
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure;
|
namespace Novelly.Api.Common;
|
||||||
|
|
||||||
public static class DependencyInjection
|
/// <summary>
|
||||||
|
/// Wires up every feature's services in one place. Endpoints, the embedded agent and the
|
||||||
|
/// MCP server all resolve the same instances, so a capability added here is available to
|
||||||
|
/// all three.
|
||||||
|
/// </summary>
|
||||||
|
public static class NovellyServiceRegistration
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddNovelSoftware(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddNovelly(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var connectionString = configuration.GetConnectionString("Novel")
|
var connectionString = configuration.GetConnectionString("Novel")
|
||||||
?? "Data Source=novel.db";
|
?? "Data Source=novel.db";
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Novelly.Api.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patch semantics shared by every update endpoint: a null value leaves the field
|
||||||
|
/// untouched, an empty string clears it.
|
||||||
|
/// </summary>
|
||||||
|
internal static class Patch
|
||||||
|
{
|
||||||
|
public static string? Apply(string? current, string? incoming) => incoming switch
|
||||||
|
{
|
||||||
|
null => current,
|
||||||
|
"" => null,
|
||||||
|
_ => incoming
|
||||||
|
};
|
||||||
|
}
|
||||||
+8
-2
@@ -1,7 +1,13 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Agent;
|
||||||
|
using Novelly.Api.Beats;
|
||||||
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application;
|
namespace Novelly.Api.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The persistence surface the application services depend on. Infrastructure supplies
|
/// The persistence surface the application services depend on. Infrastructure supplies
|
||||||
+34
-34
@@ -2,13 +2,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Novelly.Api.Data;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using NovelSoftware.Infrastructure.Persistence;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
namespace Novelly.Api.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(NovelDbContext))]
|
[DbContext(typeof(NovelDbContext))]
|
||||||
[Migration("20260806023249_InitialSchema")]
|
[Migration("20260806023249_InitialSchema")]
|
||||||
@@ -20,7 +20,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.10");
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.10");
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -47,7 +47,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Conversations");
|
b.ToTable("Conversations");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -82,7 +82,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("AgentMessages");
|
b.ToTable("AgentMessages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -134,7 +134,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Chapters");
|
b.ToTable("Chapters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -205,7 +205,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Characters");
|
b.ToTable("Characters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -234,7 +234,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("CharacterRelationships");
|
b.ToTable("CharacterRelationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.OutlineNode", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.OutlineNode", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -282,7 +282,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("OutlineNodes");
|
b.ToTable("OutlineNodes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -322,7 +322,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Projects");
|
b.ToTable("Projects");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -383,9 +383,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Scenes");
|
b.ToTable("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Conversations")
|
.WithMany("Conversations")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -394,9 +394,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.AgentConversation", "Conversation")
|
b.HasOne("Novelly.Api.Agent.AgentConversation", "Conversation")
|
||||||
.WithMany("Messages")
|
.WithMany("Messages")
|
||||||
.HasForeignKey("ConversationId")
|
.HasForeignKey("ConversationId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -405,14 +405,14 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Conversation");
|
b.Navigation("Conversation");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Chapters")
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -423,9 +423,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Characters")
|
.WithMany("Characters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -434,15 +434,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "Character")
|
b.HasOne("Novelly.Api.Characters.Character", "Character")
|
||||||
.WithMany("Relationships")
|
.WithMany("Relationships")
|
||||||
.HasForeignKey("CharacterId")
|
.HasForeignKey("CharacterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "RelatedCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "RelatedCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RelatedCharacterId")
|
.HasForeignKey("RelatedCharacterId")
|
||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
@@ -453,19 +453,19 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("RelatedCharacter");
|
b.Navigation("RelatedCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.OutlineNode", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.OutlineNode", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.OutlineNode", "Parent")
|
b.HasOne("Novelly.Api.Chapters.OutlineNode", "Parent")
|
||||||
.WithMany("Children")
|
.WithMany("Children")
|
||||||
.HasForeignKey("ParentId")
|
.HasForeignKey("ParentId")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("OutlineNodes")
|
.WithMany("OutlineNodes")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -478,15 +478,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany("Scenes")
|
.WithMany("Scenes")
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
@@ -496,27 +496,27 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("PovCharacter");
|
b.Navigation("PovCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Messages");
|
b.Navigation("Messages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Scenes");
|
b.Navigation("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Relationships");
|
b.Navigation("Relationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.OutlineNode", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.OutlineNode", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Children");
|
b.Navigation("Children");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Chapters");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
+1
-1
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
namespace Novelly.Api.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class InitialSchema : Migration
|
public partial class InitialSchema : Migration
|
||||||
+42
-42
@@ -2,13 +2,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Novelly.Api.Data;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using NovelSoftware.Infrastructure.Persistence;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
namespace Novelly.Api.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(NovelDbContext))]
|
[DbContext(typeof(NovelDbContext))]
|
||||||
[Migration("20260806031243_ReplaceOutlineWithBeatsAndTags")]
|
[Migration("20260806031243_ReplaceOutlineWithBeatsAndTags")]
|
||||||
@@ -65,7 +65,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("CharacterTags", (string)null);
|
b.ToTable("CharacterTags", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -92,7 +92,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Conversations");
|
b.ToTable("Conversations");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -127,7 +127,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("AgentMessages");
|
b.ToTable("AgentMessages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Beat", b =>
|
modelBuilder.Entity("Novelly.Api.Beats.Beat", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -173,7 +173,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Beats");
|
b.ToTable("Beats");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -225,7 +225,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Chapters");
|
b.ToTable("Chapters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -296,7 +296,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Characters");
|
b.ToTable("Characters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -325,7 +325,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("CharacterRelationships");
|
b.ToTable("CharacterRelationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -365,7 +365,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Projects");
|
b.ToTable("Projects");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -426,7 +426,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Scenes");
|
b.ToTable("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Tag", b =>
|
modelBuilder.Entity("Novelly.Api.Tags.Tag", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -457,13 +457,13 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("BeatTag", b =>
|
modelBuilder.Entity("BeatTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Beat", null)
|
b.HasOne("Novelly.Api.Beats.Beat", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BeatsId")
|
.HasForeignKey("BeatsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -472,13 +472,13 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("ChapterTag", b =>
|
modelBuilder.Entity("ChapterTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", null)
|
b.HasOne("Novelly.Api.Chapters.Chapter", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ChaptersId")
|
.HasForeignKey("ChaptersId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -487,22 +487,22 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("CharacterTag", b =>
|
modelBuilder.Entity("CharacterTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", null)
|
b.HasOne("Novelly.Api.Characters.Character", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CharactersId")
|
.HasForeignKey("CharactersId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Conversations")
|
.WithMany("Conversations")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -511,9 +511,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.AgentConversation", "Conversation")
|
b.HasOne("Novelly.Api.Agent.AgentConversation", "Conversation")
|
||||||
.WithMany("Messages")
|
.WithMany("Messages")
|
||||||
.HasForeignKey("ConversationId")
|
.HasForeignKey("ConversationId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -522,20 +522,20 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Conversation");
|
b.Navigation("Conversation");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Beat", b =>
|
modelBuilder.Entity("Novelly.Api.Beats.Beat", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany("Beats")
|
.WithMany("Beats")
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "Character")
|
b.HasOne("Novelly.Api.Characters.Character", "Character")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CharacterId")
|
.HasForeignKey("CharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Scene", "Scene")
|
b.HasOne("Novelly.Api.Scenes.Scene", "Scene")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("SceneId")
|
.HasForeignKey("SceneId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
@@ -547,14 +547,14 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Scene");
|
b.Navigation("Scene");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Chapters")
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -565,9 +565,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Characters")
|
.WithMany("Characters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -576,15 +576,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "Character")
|
b.HasOne("Novelly.Api.Characters.Character", "Character")
|
||||||
.WithMany("Relationships")
|
.WithMany("Relationships")
|
||||||
.HasForeignKey("CharacterId")
|
.HasForeignKey("CharacterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "RelatedCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "RelatedCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RelatedCharacterId")
|
.HasForeignKey("RelatedCharacterId")
|
||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
@@ -595,15 +595,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("RelatedCharacter");
|
b.Navigation("RelatedCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany("Scenes")
|
.WithMany("Scenes")
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
@@ -613,9 +613,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("PovCharacter");
|
b.Navigation("PovCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Tag", b =>
|
modelBuilder.Entity("Novelly.Api.Tags.Tag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Tags")
|
.WithMany("Tags")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -624,24 +624,24 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Messages");
|
b.Navigation("Messages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Beats");
|
b.Navigation("Beats");
|
||||||
|
|
||||||
b.Navigation("Scenes");
|
b.Navigation("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Relationships");
|
b.Navigation("Relationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Chapters");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
+1
-1
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
namespace Novelly.Api.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class ReplaceOutlineWithBeatsAndTags : Migration
|
public partial class ReplaceOutlineWithBeatsAndTags : Migration
|
||||||
+42
-42
@@ -2,12 +2,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Novelly.Api.Data;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using NovelSoftware.Infrastructure.Persistence;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
namespace Novelly.Api.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(NovelDbContext))]
|
[DbContext(typeof(NovelDbContext))]
|
||||||
partial class NovelDbContextModelSnapshot : ModelSnapshot
|
partial class NovelDbContextModelSnapshot : ModelSnapshot
|
||||||
@@ -62,7 +62,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("CharacterTags", (string)null);
|
b.ToTable("CharacterTags", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -89,7 +89,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Conversations");
|
b.ToTable("Conversations");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -124,7 +124,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("AgentMessages");
|
b.ToTable("AgentMessages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Beat", b =>
|
modelBuilder.Entity("Novelly.Api.Beats.Beat", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -170,7 +170,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Beats");
|
b.ToTable("Beats");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -222,7 +222,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Chapters");
|
b.ToTable("Chapters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -293,7 +293,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Characters");
|
b.ToTable("Characters");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -322,7 +322,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("CharacterRelationships");
|
b.ToTable("CharacterRelationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -362,7 +362,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Projects");
|
b.ToTable("Projects");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -423,7 +423,7 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Scenes");
|
b.ToTable("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Tag", b =>
|
modelBuilder.Entity("Novelly.Api.Tags.Tag", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -454,13 +454,13 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("BeatTag", b =>
|
modelBuilder.Entity("BeatTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Beat", null)
|
b.HasOne("Novelly.Api.Beats.Beat", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BeatsId")
|
.HasForeignKey("BeatsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -469,13 +469,13 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("ChapterTag", b =>
|
modelBuilder.Entity("ChapterTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", null)
|
b.HasOne("Novelly.Api.Chapters.Chapter", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ChaptersId")
|
.HasForeignKey("ChaptersId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -484,22 +484,22 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("CharacterTag", b =>
|
modelBuilder.Entity("CharacterTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", null)
|
b.HasOne("Novelly.Api.Characters.Character", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CharactersId")
|
.HasForeignKey("CharactersId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Tag", null)
|
b.HasOne("Novelly.Api.Tags.Tag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("TagsId")
|
.HasForeignKey("TagsId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Conversations")
|
.WithMany("Conversations")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -508,9 +508,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentMessage", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentMessage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.AgentConversation", "Conversation")
|
b.HasOne("Novelly.Api.Agent.AgentConversation", "Conversation")
|
||||||
.WithMany("Messages")
|
.WithMany("Messages")
|
||||||
.HasForeignKey("ConversationId")
|
.HasForeignKey("ConversationId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -519,20 +519,20 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Conversation");
|
b.Navigation("Conversation");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Beat", b =>
|
modelBuilder.Entity("Novelly.Api.Beats.Beat", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany("Beats")
|
.WithMany("Beats")
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "Character")
|
b.HasOne("Novelly.Api.Characters.Character", "Character")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CharacterId")
|
.HasForeignKey("CharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Scene", "Scene")
|
b.HasOne("Novelly.Api.Scenes.Scene", "Scene")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("SceneId")
|
.HasForeignKey("SceneId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
@@ -544,14 +544,14 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Scene");
|
b.Navigation("Scene");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Chapters")
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -562,9 +562,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Characters")
|
.WithMany("Characters")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -573,15 +573,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.CharacterRelationship", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.CharacterRelationship", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "Character")
|
b.HasOne("Novelly.Api.Characters.Character", "Character")
|
||||||
.WithMany("Relationships")
|
.WithMany("Relationships")
|
||||||
.HasForeignKey("CharacterId")
|
.HasForeignKey("CharacterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "RelatedCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "RelatedCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RelatedCharacterId")
|
.HasForeignKey("RelatedCharacterId")
|
||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
@@ -592,15 +592,15 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("RelatedCharacter");
|
b.Navigation("RelatedCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Scene", b =>
|
modelBuilder.Entity("Novelly.Api.Scenes.Scene", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Chapter", "Chapter")
|
b.HasOne("Novelly.Api.Chapters.Chapter", "Chapter")
|
||||||
.WithMany("Scenes")
|
.WithMany("Scenes")
|
||||||
.HasForeignKey("ChapterId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Character", "PovCharacter")
|
b.HasOne("Novelly.Api.Characters.Character", "PovCharacter")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PovCharacterId")
|
.HasForeignKey("PovCharacterId")
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
@@ -610,9 +610,9 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("PovCharacter");
|
b.Navigation("PovCharacter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Tag", b =>
|
modelBuilder.Entity("Novelly.Api.Tags.Tag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NovelSoftware.Domain.Entities.Project", "Project")
|
b.HasOne("Novelly.Api.Projects.Project", "Project")
|
||||||
.WithMany("Tags")
|
.WithMany("Tags")
|
||||||
.HasForeignKey("ProjectId")
|
.HasForeignKey("ProjectId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -621,24 +621,24 @@ namespace NovelSoftware.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Project");
|
b.Navigation("Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.AgentConversation", b =>
|
modelBuilder.Entity("Novelly.Api.Agent.AgentConversation", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Messages");
|
b.Navigation("Messages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Chapter", b =>
|
modelBuilder.Entity("Novelly.Api.Chapters.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Beats");
|
b.Navigation("Beats");
|
||||||
|
|
||||||
b.Navigation("Scenes");
|
b.Navigation("Scenes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Character", b =>
|
modelBuilder.Entity("Novelly.Api.Characters.Character", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Relationships");
|
b.Navigation("Relationships");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelSoftware.Domain.Entities.Project", b =>
|
modelBuilder.Entity("Novelly.Api.Projects.Project", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Chapters");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
+9
-4
@@ -1,9 +1,14 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using NovelSoftware.Application;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Agent;
|
||||||
|
using Novelly.Api.Beats;
|
||||||
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Infrastructure.Persistence;
|
namespace Novelly.Api.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores a <see cref="DateTimeOffset"/> as UTC ticks. SQLite has no native type for it
|
/// Stores a <see cref="DateTimeOffset"/> as UTC ticks. SQLite has no native type for it
|
||||||
+5
-4
@@ -1,25 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NovelSoftware.Application\NovelSoftware.Application.csproj" />
|
<ProjectReference Include="..\Novelly.ServiceDefaults\Novelly.ServiceDefaults.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Anthropic" Version="12.39.0" />
|
<PackageReference Include="Anthropic" Version="12.39.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.10" />
|
<PackageReference Include="Microsoft.OpenApi" Version="2.11.0" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Api.Endpoints;
|
using Novelly.Api.Agent;
|
||||||
using NovelSoftware.Application;
|
using Novelly.Api.Beats;
|
||||||
using NovelSoftware.Infrastructure;
|
using Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Infrastructure.Persistence;
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Data;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
using Novelly.Api.Scenes;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddNovelSoftware(builder.Configuration);
|
builder.AddServiceDefaults();
|
||||||
|
builder.Services.AddNovelly(builder.Configuration);
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
|
|
||||||
@@ -63,6 +69,8 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.MapOpenApi();
|
app.MapOpenApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.MapDefaultEndpoints();
|
||||||
|
|
||||||
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" })).WithTags("Health");
|
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" })).WithTags("Health");
|
||||||
|
|
||||||
app.MapProjectEndpoints()
|
app.MapProjectEndpoints()
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Agent;
|
||||||
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Tags;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Projects;
|
||||||
|
|
||||||
/// <summary>A single novel and everything that belongs to it.</summary>
|
/// <summary>A single novel and everything that belongs to it.</summary>
|
||||||
public class Project
|
public class Project
|
||||||
+1
-3
@@ -1,6 +1,4 @@
|
|||||||
using NovelSoftware.Domain.Entities;
|
namespace Novelly.Api.Projects;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
|
||||||
|
|
||||||
public record ProjectSummaryDto(
|
public record ProjectSummaryDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Projects;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class ProjectEndpoints
|
public static class ProjectEndpoints
|
||||||
{
|
{
|
||||||
+3
-17
@@ -1,8 +1,8 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Data;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Projects;
|
||||||
|
|
||||||
public class ProjectService(INovelDbContext db)
|
public class ProjectService(INovelDbContext db)
|
||||||
{
|
{
|
||||||
@@ -71,17 +71,3 @@ public class ProjectService(INovelDbContext db)
|
|||||||
await db.Projects.FirstOrDefaultAsync(p => p.Id == id, ct)
|
await db.Projects.FirstOrDefaultAsync(p => p.Id == id, ct)
|
||||||
?? throw new NotFoundException(nameof(Project), id);
|
?? throw new NotFoundException(nameof(Project), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Patch semantics shared by every update endpoint: a null value leaves the field
|
|
||||||
/// untouched, an empty string clears it.
|
|
||||||
/// </summary>
|
|
||||||
internal static class Patch
|
|
||||||
{
|
|
||||||
public static string? Apply(string? current, string? incoming) => incoming switch
|
|
||||||
{
|
|
||||||
null => current,
|
|
||||||
"" => null,
|
|
||||||
_ => incoming
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Common;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Scenes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A scene inside a chapter. The goal/conflict/outcome trio is the unit the agent
|
/// A scene inside a chapter. The goal/conflict/outcome trio is the unit the agent
|
||||||
+2
-3
@@ -1,7 +1,6 @@
|
|||||||
using NovelSoftware.Domain;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain.Entities;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Scenes;
|
||||||
|
|
||||||
public record SceneDto(
|
public record SceneDto(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Scenes;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class SceneEndpoints
|
public static class SceneEndpoints
|
||||||
{
|
{
|
||||||
+4
-3
@@ -1,8 +1,9 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Chapters;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Common;
|
||||||
|
using Novelly.Api.Data;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Scenes;
|
||||||
|
|
||||||
public class SceneService(INovelDbContext db)
|
public class SceneService(INovelDbContext db)
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
namespace NovelSoftware.Domain.Entities;
|
using Novelly.Api.Beats;
|
||||||
|
using Novelly.Api.Chapters;
|
||||||
|
using Novelly.Api.Characters;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
|
||||||
|
namespace Novelly.Api.Tags;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A free-form label scoped to one project. Tags are the cross-reference mechanism:
|
/// A free-form label scoped to one project. Tags are the cross-reference mechanism:
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using NovelSoftware.Domain.Entities;
|
namespace Novelly.Api.Tags;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Dtos;
|
|
||||||
|
|
||||||
public record TagDto(Guid Id, string Name, string? Color);
|
public record TagDto(Guid Id, string Name, string? Color);
|
||||||
|
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
using NovelSoftware.Application.Dtos;
|
namespace Novelly.Api.Tags;
|
||||||
using NovelSoftware.Application.Services;
|
|
||||||
|
|
||||||
namespace NovelSoftware.Api.Endpoints;
|
|
||||||
|
|
||||||
public static class TagEndpoints
|
public static class TagEndpoints
|
||||||
{
|
{
|
||||||
+4
-3
@@ -1,8 +1,9 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NovelSoftware.Application.Dtos;
|
using Novelly.Api.Common;
|
||||||
using NovelSoftware.Domain.Entities;
|
using Novelly.Api.Data;
|
||||||
|
using Novelly.Api.Projects;
|
||||||
|
|
||||||
namespace NovelSoftware.Application.Services;
|
namespace Novelly.Api.Tags;
|
||||||
|
|
||||||
public class TagService(INovelDbContext db)
|
public class TagService(INovelDbContext db)
|
||||||
{
|
{
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"NovelSoftware": "Debug",
|
"Novelly": "Debug",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
var builder = DistributedApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Port 5080 is pinned to match src/Novelly.Web's Vite proxy default and the curl-based
|
||||||
|
// smoke checks in CLAUDE.md, so the API sits at the same address whether it is started
|
||||||
|
// on its own with `dotnet run` or through this AppHost.
|
||||||
|
var api = builder.AddProject<Projects.Novelly_Api>("api")
|
||||||
|
.WithHttpEndpoint(port: 5080, name: "http");
|
||||||
|
|
||||||
|
builder.AddViteApp("web", "../Novelly.Web", "dev")
|
||||||
|
.WithReference(api)
|
||||||
|
.WaitFor(api)
|
||||||
|
.WithHttpEndpoint(port: 5173, name: "http")
|
||||||
|
.WithExternalHttpEndpoints();
|
||||||
|
|
||||||
|
builder.Build().Run();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Aspire.AppHost.Sdk/13.4.6">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Novelly.Api\Novelly.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Aspire.Hosting.JavaScript" Version="13.4.6" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<UserSecretsId>4f9d2a13-6c85-4b0e-9a7c-2d3f18b6e0c4</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:17244;http://localhost:15050",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
|
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21229",
|
||||||
|
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22051"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "http://localhost:15050",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
|
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19033",
|
||||||
|
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20209"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Dashboard": {
|
||||||
|
"Frontend": {
|
||||||
|
"AuthMode": "Unsecured"
|
||||||
|
},
|
||||||
|
"Otlp": {
|
||||||
|
"AuthMode": "Unsecured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"Aspire.Hosting.Dcp": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"appHost": {
|
||||||
|
"path": "Novelly.AppHost.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ using System.Net.Http.Json;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp;
|
namespace Novelly.Mcp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Thin wrapper over the NovelSoftware REST API. The MCP server deliberately owns no
|
/// Thin wrapper over the Novelly REST API. The MCP server deliberately owns no
|
||||||
/// domain logic of its own — it is a second front end onto the same API the web client
|
/// domain logic of its own — it is a second front end onto the same API the web client
|
||||||
/// uses, so an edit made from Claude Code and one made in the browser are the same edit.
|
/// uses, so an edit made from Claude Code and one made in the browser are the same edit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -51,7 +51,7 @@ public class NovelApiClient(HttpClient http)
|
|||||||
{
|
{
|
||||||
// The API not being up is the most common failure here, and a bare connection
|
// The API not being up is the most common failure here, and a bare connection
|
||||||
// exception tells the model nothing actionable.
|
// exception tells the model nothing actionable.
|
||||||
return Error($"Could not reach the NovelSoftware API at {http.BaseAddress}. Is it running? ({ex.Message})");
|
return Error($"Could not reach the Novelly API at {http.BaseAddress}. Is it running? ({ex.Message})");
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = await response.Content.ReadAsStringAsync(ct);
|
var body = await response.Content.ReadAsStringAsync(ct);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NovelSoftware.Mcp;
|
using Novelly.Mcp;
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder(args);
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ builder.Logging.ClearProviders();
|
|||||||
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);
|
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);
|
||||||
builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
||||||
|
|
||||||
var apiBaseUrl = builder.Configuration["NOVELSOFTWARE_API_URL"] ?? "http://localhost:5080";
|
var apiBaseUrl = builder.Configuration["NOVELLY_API_URL"] ?? "http://localhost:5080";
|
||||||
|
|
||||||
builder.Services.AddHttpClient<NovelApiClient>(client =>
|
builder.Services.AddHttpClient<NovelApiClient>(client =>
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp.Tools;
|
namespace Novelly.Mcp.Tools;
|
||||||
|
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public static class BeatTools
|
public static class BeatTools
|
||||||
+1
-1
@@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp.Tools;
|
namespace Novelly.Mcp.Tools;
|
||||||
|
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public static class CharacterTools
|
public static class CharacterTools
|
||||||
+1
-1
@@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp.Tools;
|
namespace Novelly.Mcp.Tools;
|
||||||
|
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public static class ManuscriptTools
|
public static class ManuscriptTools
|
||||||
@@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp.Tools;
|
namespace Novelly.Mcp.Tools;
|
||||||
|
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public static class ProjectTools
|
public static class ProjectTools
|
||||||
@@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
namespace NovelSoftware.Mcp.Tools;
|
namespace Novelly.Mcp.Tools;
|
||||||
|
|
||||||
[McpServerToolType]
|
[McpServerToolType]
|
||||||
public static class TagTools
|
public static class TagTools
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.ServiceDiscovery;
|
||||||
|
using OpenTelemetry;
|
||||||
|
using OpenTelemetry.Metrics;
|
||||||
|
using OpenTelemetry.Trace;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
// Adds common Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
|
||||||
|
// This project should be referenced by each service project in your solution.
|
||||||
|
// To learn more about using this project, see https://aka.ms/aspire/service-defaults
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
private const string HealthEndpointPath = "/health";
|
||||||
|
private const string AlivenessEndpointPath = "/alive";
|
||||||
|
|
||||||
|
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||||
|
{
|
||||||
|
builder.ConfigureOpenTelemetry();
|
||||||
|
|
||||||
|
builder.AddDefaultHealthChecks();
|
||||||
|
|
||||||
|
builder.Services.AddServiceDiscovery();
|
||||||
|
|
||||||
|
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||||
|
{
|
||||||
|
// Turn on resilience by default
|
||||||
|
http.AddStandardResilienceHandler();
|
||||||
|
|
||||||
|
// Turn on service discovery by default
|
||||||
|
http.AddServiceDiscovery();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Uncomment the following to restrict the allowed schemes for service discovery.
|
||||||
|
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
|
||||||
|
// {
|
||||||
|
// options.AllowedSchemes = ["https"];
|
||||||
|
// });
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||||
|
{
|
||||||
|
builder.Logging.AddOpenTelemetry(logging =>
|
||||||
|
{
|
||||||
|
logging.IncludeFormattedMessage = true;
|
||||||
|
logging.IncludeScopes = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddOpenTelemetry()
|
||||||
|
.WithMetrics(metrics =>
|
||||||
|
{
|
||||||
|
metrics.AddAspNetCoreInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation()
|
||||||
|
.AddRuntimeInstrumentation();
|
||||||
|
})
|
||||||
|
.WithTracing(tracing =>
|
||||||
|
{
|
||||||
|
tracing.AddSource(builder.Environment.ApplicationName)
|
||||||
|
.AddAspNetCoreInstrumentation(tracing =>
|
||||||
|
// Exclude health check requests from tracing
|
||||||
|
tracing.Filter = context =>
|
||||||
|
!context.Request.Path.StartsWithSegments(HealthEndpointPath)
|
||||||
|
&& !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)
|
||||||
|
)
|
||||||
|
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
|
||||||
|
//.AddGrpcClientInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.AddOpenTelemetryExporters();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||||
|
{
|
||||||
|
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
|
||||||
|
|
||||||
|
if (useOtlpExporter)
|
||||||
|
{
|
||||||
|
builder.Services.AddOpenTelemetry().UseOtlpExporter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
|
||||||
|
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
|
||||||
|
//{
|
||||||
|
// builder.Services.AddOpenTelemetry()
|
||||||
|
// .UseAzureMonitor();
|
||||||
|
//}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
|
||||||
|
{
|
||||||
|
builder.Services.AddHealthChecks()
|
||||||
|
// Add a default liveness check to ensure app is responsive
|
||||||
|
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebApplication MapDefaultEndpoints(this WebApplication app)
|
||||||
|
{
|
||||||
|
// Adding health checks endpoints to applications in non-development environments has security implications.
|
||||||
|
// See https://aka.ms/aspire/healthchecks for details before enabling these endpoints in non-development environments.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
// All health checks must pass for app to be considered ready to accept traffic after starting
|
||||||
|
app.MapHealthChecks(HealthEndpointPath);
|
||||||
|
|
||||||
|
// Only health checks tagged with the "live" tag must pass for app to be considered alive
|
||||||
|
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
|
||||||
|
{
|
||||||
|
Predicate = r => r.Tags.Contains("live")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<IsAspireSharedProject>true</IsAspireSharedProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.8.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.17.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.17.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.17.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.17.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.17.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "novelsoftware-web",
|
"name": "novelly-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "novelsoftware-web",
|
"name": "novelly-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "novelsoftware-web",
|
"name": "novelly-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -1,4 +1,4 @@
|
|||||||
// Mirrors the DTOs in NovelSoftware.Application.Dtos. Enums travel as their names.
|
// Mirrors the DTOs in the Novelly.Api feature folders. Enums travel as their names.
|
||||||
|
|
||||||
export type CharacterRole =
|
export type CharacterRole =
|
||||||
| 'Protagonist'
|
| 'Protagonist'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user