journal/README.md
Jacob Schmidt d0fac4199a Initial commit: Journal.Core library + Sidecar console app
- 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>
2026-02-21 02:01:00 -06:00

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)