mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-02 12:28:47 -04:00
feat(server): add OpenNest.Server SQLite nest storage API
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.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user