SQLite-backed (Microsoft.Data.Sqlite, WAL) minimal API storing NestRecord
metadata plus the .nest archive as a BLOB. Endpoints: GET/POST /api/nests,
GET /api/nests/{id}[/file], PUT /api/nests/{id}/file, PUT
/api/nests/{id}/metadata, DELETE /api/nests/{id}, GET /healthz. Multipart
upload contract matches RemoteNestRepository (metadata JSON part + file
part). Added to OpenNest.sln, builds standalone on Linux. Dockerfile
publishes to a runtime image listening on :8090 with a /app/data volume
for the SQLite file. docs/nest-storage.md documents the wire contract,
endpoints and deployment.
Full curl round trip verified manually against a running instance:
upload (server-assigned id + computed fileSize), list, get, byte-exact
file download, metadata-only update (archive unchanged), file update
(new archive persisted), 404s for unknown ids, delete, post-delete 404.
85 lines
3.6 KiB
Markdown
85 lines
3.6 KiB
Markdown
# Nest storage: File mode vs. Database mode
|
|
|
|
The desktop app can save nests two ways:
|
|
|
|
- **File mode** (default, unchanged behavior): Save/Save As write a `.nest` ZIP
|
|
archive to disk via the normal file dialog. See [nest-file-format.md](nest-file-format.md).
|
|
- **Database mode**: Save uploads the nest to a central `OpenNest.Server`
|
|
instance shared by every shop PC, instead of writing a local file. A separate
|
|
**File > Export .nest...** command is always available (in both modes) for
|
|
producing a local file to share or back up.
|
|
|
|
The mode and server address are stored per-PC at `%APPDATA%\OpenNest\storage.json`
|
|
(`OpenNest.Data.NestStorageSettings`), defaulting to File mode so existing
|
|
installs are unaffected until an operator opts in via **File > Storage Mode...**.
|
|
|
|
## Wire contract
|
|
|
|
`OpenNest.Data.NestRecord` is the metadata DTO shared by the client
|
|
(`RemoteNestRepository`) and the server (`OpenNest.Server`). JSON is camelCase
|
|
with enums serialized as strings (`JsonSerializerDefaults.Web` +
|
|
`JsonStringEnumConverter(JsonNamingPolicy.CamelCase)`), matching the rest of
|
|
`OpenNest.Data`'s local JSON stores.
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `id` | guid | Client-generated on first save; kept on updates. |
|
|
| `name` | string | |
|
|
| `customer` | string | |
|
|
| `dateCreated` / `dateModified` | datetime | Set by the client from the nest's own metadata. |
|
|
| `material` | string | |
|
|
| `thickness` | number | |
|
|
| `status` | `"quote"` \| `"toBeCut"` \| `"hasBeenCut"` | Default `quote`. |
|
|
| `plateCount` / `partCount` | integer | Computed client-side at save time (non-cutoff parts only). |
|
|
| `comments` | string | |
|
|
| `madeBy` | string | |
|
|
| `fileSize` | integer | Server-computed; ignored on upload. |
|
|
| `savedAt` | datetime | Server-computed; ignored on upload. |
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Body | Response |
|
|
|---|---|---|---|
|
|
| GET | `/healthz` | — | `{ "status": "ok" }` |
|
|
| GET | `/api/nests` | — | `NestRecord[]`, newest `savedAt` first |
|
|
| GET | `/api/nests/{id}` | — | `NestRecord` or 404 |
|
|
| GET | `/api/nests/{id}/file` | — | `.nest` archive bytes (`application/zip`) or 404 |
|
|
| POST | `/api/nests` | multipart: `metadata` (JSON `NestRecord`) + `file` (`.nest` bytes) | `NestRecord` with server-assigned `id` when the client sends an empty guid |
|
|
| PUT | `/api/nests/{id}/file` | multipart: `metadata` + `file` | Updated `NestRecord`, or 404 if `id` is unknown |
|
|
| PUT | `/api/nests/{id}/metadata` | JSON `NestRecord` | Updated `NestRecord` (archive untouched), or 404 |
|
|
| DELETE | `/api/nests/{id}` | — | 204, or 404 |
|
|
|
|
There is no authentication; this is a LAN-only service. Do not expose it
|
|
outside the shop network without adding one.
|
|
|
|
## Storage
|
|
|
|
`OpenNest.Server.NestDatabase` uses SQLite (`Microsoft.Data.Sqlite`) with
|
|
`journal_mode=WAL`. Each row holds the metadata columns plus the `.nest`
|
|
archive as a `BLOB`. The database file path comes from `--database=<path>`,
|
|
then `OPENNEST_DB`, defaulting to `./data/nests.db`.
|
|
|
|
## Running
|
|
|
|
Locally:
|
|
|
|
```sh
|
|
dotnet run --project OpenNest.Server/OpenNest.Server.csproj
|
|
```
|
|
|
|
Listens on `ASPNETCORE_URLS` (default Kestrel ports) unless overridden.
|
|
|
|
Docker (built from the repository root so it can see `OpenNest.Data`):
|
|
|
|
```sh
|
|
docker build -f OpenNest.Server/Dockerfile -t opennest-server .
|
|
docker run -d -p 8090:8090 -v opennest-data:/app/data opennest-server
|
|
```
|
|
|
|
The image listens on `:8090` and stores `nests.db` under `/app/data`, which
|
|
should be a named volume or bind mount so nests survive container recreation.
|
|
|
|
Point the desktop app's **File > Storage Mode...** server URL at
|
|
`http://<host>:8090` (no trailing slash required; `RemoteNestRepository`
|
|
trims it).
|