- Fragment model with validation, DTOs (immutable records), repository, service - Sidecar stdin/stdout JSON protocol for Tauri integration - DI wiring via ServiceCollectionExtensions - Scaffolded Journal.Api (not yet wired) Co-Authored-By: Warp <agent@warp.dev>
167 lines
5.9 KiB
Markdown
167 lines
5.9 KiB
Markdown
# Journal Backend (.NET)
|
|
|
|
A .NET 10 backend for the Project Journal app. Provides core journal functionality as a class library with a sidecar console app for Tauri integration and an optional HTTP API.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
backend/
|
|
├── Journal.Core/ Class library — all business logic
|
|
│ ├── Models/
|
|
│ │ ├── Fragment.cs Domain model (validated, owns Guid ID)
|
|
│ │ └── Command.cs Stdin command shape for sidecar protocol
|
|
│ ├── Dtos/
|
|
│ │ └── FragmentDtos.cs Immutable records for API boundary
|
|
│ │ ├── FragmentDto Read (what goes out)
|
|
│ │ ├── CreateFragmentDto Create (what comes in)
|
|
│ │ └── UpdateFragmentDto Update (partial, all fields optional)
|
|
│ ├── Repositories/
|
|
│ │ ├── IFragmentRepository.cs Interface (data access contract)
|
|
│ │ └── InMemoryFragmentRepository.cs In-memory implementation
|
|
│ ├── Services/
|
|
│ │ ├── IFragmentService.cs Interface (business logic contract)
|
|
│ │ └── FragmentService.cs Validates, calls repo, maps to DTOs
|
|
│ ├── Entry.cs Command dispatcher (stdin/stdout)
|
|
│ ├── ServiceCollectionExtensions.cs DI registration helper
|
|
│ └── Journal.Core.csproj
|
|
│
|
|
├── Journal.Sidecar/ Console app — Tauri sidecar bridge
|
|
│ ├── App.cs Boots DI container, runs Entry.RunAsync()
|
|
│ └── Journal.Sidecar.csproj References Journal.Core
|
|
│
|
|
├── Journal.Api/ Web API — HTTP endpoint wrapper (optional)
|
|
│ ├── Program.cs
|
|
│ └── Journal.Api.csproj
|
|
│
|
|
└── README.md
|
|
```
|
|
|
|
## Architecture
|
|
|
|
Each layer only knows about the one below it:
|
|
|
|
```
|
|
Sidecar (stdin/stdout) ──┐
|
|
├──► Services (business logic) ──► Repositories (data access)
|
|
API (HTTP/JSON) ─────────┘
|
|
```
|
|
|
|
- **Models** — Domain objects with validation. The source of truth.
|
|
- **DTOs** — Immutable records that cross the API boundary. Internal logic never leaks out.
|
|
- **Repositories** — Where data lives. Swap `InMemoryFragmentRepository` for SQLite/EF Core later without touching anything above.
|
|
- **Services** — Business rules, validation, orchestration. Doesn't know about HTTP or stdin.
|
|
- **Entry** — Transport adapter. Translates stdin/stdout JSON into service calls.
|
|
|
|
## Dependencies
|
|
|
|
- **Journal.Core** — `Microsoft.Extensions.DependencyInjection.Abstractions` (interface-only, lightweight)
|
|
- **Journal.Sidecar** — `Microsoft.Extensions.DependencyInjection` (full container implementation) + references `Journal.Core`
|
|
|
|
## Building
|
|
|
|
```powershell
|
|
# Build everything (building Sidecar also rebuilds Core if changed)
|
|
dotnet build backend\Journal.Sidecar\Journal.Sidecar.csproj
|
|
|
|
# Build just the library
|
|
dotnet build backend\Journal.Core\Journal.Core.csproj
|
|
|
|
# Format code
|
|
dotnet format backend\Journal.Core\Journal.Core.csproj
|
|
```
|
|
|
|
## Publishing
|
|
|
|
Publish as a single-file self-contained executable (no .NET runtime install needed):
|
|
|
|
```powershell
|
|
dotnet publish backend\Journal.Sidecar\Journal.Sidecar.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
|
|
```
|
|
|
|
Output: `backend\Journal.Sidecar\bin\Release\net10.0\win-x64\publish\Journal.Sidecar.exe` (~70MB, everything bundled)
|
|
|
|
To exclude debug symbols: add `-p:DebugType=none`
|
|
|
|
For a smaller build that requires .NET 10 on the target machine:
|
|
|
|
```powershell
|
|
dotnet publish backend\Journal.Sidecar\Journal.Sidecar.csproj -c Release -r win-x64 -p:PublishSingleFile=true
|
|
```
|
|
|
|
## Sidecar Protocol
|
|
|
|
The sidecar communicates over stdin/stdout using JSON lines. One JSON line in, one JSON line out.
|
|
|
|
### Command Format
|
|
|
|
```json
|
|
{
|
|
"action": "fragments.create",
|
|
"id": null,
|
|
"type": null,
|
|
"tag": null,
|
|
"payload": { "type": "!TRIGGER", "description": "stomach drop" }
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
- `action` — The operation to perform (e.g. `fragments.list`, `fragments.create`)
|
|
- `id` — Target entity ID (for get/update/delete)
|
|
- `type` / `tag` — Filter parameters (for search)
|
|
- `payload` — Request body, deserialized into the appropriate DTO per action
|
|
|
|
### Available Actions
|
|
|
|
| Action | Description | Requires |
|
|
|--------|-------------|----------|
|
|
| `fragments.list` | List all fragments | — |
|
|
| `fragments.get` | Get fragment by ID | `id` |
|
|
| `fragments.create` | Create a new fragment | `payload` (CreateFragmentDto) |
|
|
| `fragments.update` | Update a fragment | `id`, `payload` (UpdateFragmentDto) |
|
|
| `fragments.delete` | Delete a fragment | `id` |
|
|
| `fragments.search` | Search by type/tag | `type` and/or `tag` |
|
|
|
|
### Response Format
|
|
|
|
Success:
|
|
```json
|
|
{ "ok": true, "data": { "id": "abc-123", "type": "!TRIGGER", "description": "...", "time": "...", "tags": [] } }
|
|
```
|
|
|
|
Error:
|
|
```json
|
|
{ "ok": false, "error": "Description is required" }
|
|
```
|
|
|
|
## Extending with New Modules
|
|
|
|
The `Command` class is generic — new modules use the same dot-notation pattern:
|
|
|
|
```
|
|
vault.unlock → IVaultService (future)
|
|
vault.lock
|
|
entries.list → IEntryService (future)
|
|
entries.create
|
|
ai.analyze → IAiService (future)
|
|
ai.chat
|
|
search.query → ISearchService (future)
|
|
```
|
|
|
|
To add a module:
|
|
1. Create model, DTO, repository, and service in `Journal.Core/`
|
|
2. Register the new service in `ServiceCollectionExtensions.cs`
|
|
3. Inject the service into `Entry.cs` and add cases to the action switch
|
|
4. No changes needed to `Command.cs` or `App.cs`
|
|
|
|
## Dependency Injection
|
|
|
|
`ServiceCollectionExtensions.cs` wires everything up. Any host (sidecar, API, tests) calls:
|
|
|
|
```csharp
|
|
services.AddFragmentServices();
|
|
```
|
|
|
|
This registers:
|
|
- `IFragmentRepository` → `InMemoryFragmentRepository` (singleton — one shared store)
|
|
- `IFragmentService` → `FragmentService` (transient — fresh instance per request)
|