Implement Phase 2 asset storage pipeline
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# Announcements v2
|
||||
|
||||
Announcements is a small Express, React, Socket.IO, MQTT, SQLite, and
|
||||
S3-compatible signage application. Version 2 is being built in phases and is
|
||||
intended to run beside the existing production version until cutover.
|
||||
|
||||
## Development
|
||||
|
||||
The repository is a pnpm workspace with one lockfile. It requires Node.js
|
||||
22.13 or newer and pnpm 10.29.3.
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm check
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
Only the Nginx web service publishes a host port (5175 by default). The API and
|
||||
SeaweedFS S3 endpoint remain private on Compose networks. Replace the example
|
||||
S3 credentials before starting the stack.
|
||||
|
||||
## Library assets
|
||||
|
||||
`POST /api/assets` accepts multipart uploads in the repeated `file` or `files`
|
||||
field. An optional `display_name` (or `displayName`) is accepted for a
|
||||
single-file upload. Supported decoded formats are still JPEG, PNG, and WebP;
|
||||
extensions and multipart content types are not trusted. Animated, multipage,
|
||||
malformed, unsupported, oversized, and excessive-pixel inputs are rejected.
|
||||
|
||||
Default limits are:
|
||||
|
||||
- 25 MiB per file (`UPLOAD_MAX_FILE_BYTES`)
|
||||
- 20 files per request (`UPLOAD_MAX_FILES`)
|
||||
- 40 million decoded pixels per image (`IMAGE_MAX_INPUT_PIXELS`)
|
||||
|
||||
Nginx accepts up to 512 MiB per request, enough for the default aggregate plus
|
||||
multipart overhead. If the backend limits are raised, update
|
||||
`client_max_body_size` in `client/nginx.conf` as well. Proxied API requests use
|
||||
a ten-minute inactivity timeout so a valid large batch can finish processing.
|
||||
|
||||
Original bytes are archived unchanged. Content identity is the SHA-256 of
|
||||
those bytes, so uploading the same content returns the existing asset even
|
||||
when the filename differs. Display-name collisions with different content
|
||||
return `409 ASSET_NAME_CONFLICT`. Multi-file uploads validate every new image
|
||||
locally before persisting any of them. If a later storage operation fails,
|
||||
already completed items remain valid and retrying the request is idempotent.
|
||||
|
||||
Sharp produces two metadata-free, auto-oriented WebP renditions:
|
||||
|
||||
- display: inside-fit at most 1920×1080, quality 82
|
||||
- thumbnail: inside-fit at most 480×270, quality 72
|
||||
|
||||
Neither rendition crops or upscales. Transparency is flattened against black.
|
||||
Presentation effects such as blur, shadows, darkening, and framing are not
|
||||
baked into stored media.
|
||||
|
||||
## Asset and media API
|
||||
|
||||
- `GET /api/assets`
|
||||
- `POST /api/assets`
|
||||
- `GET /api/assets/:assetId`
|
||||
- `PATCH /api/assets/:assetId`
|
||||
- `GET /api/assets/:assetId/references`
|
||||
- `DELETE /api/assets/:assetId`
|
||||
- `GET /api/assets/:assetId/original`
|
||||
- `GET /media/assets/:assetId/display/:sha256.webp`
|
||||
- `GET /media/assets/:assetId/thumbnail/:sha256.webp`
|
||||
|
||||
PATCH accepts only display name and expiration. A null expiration is permanent.
|
||||
Manual deletion refuses playlist, current playback, and static references. It
|
||||
marks an asset as deleting before idempotently removing its three objects and
|
||||
removes SQLite metadata only after storage succeeds. A failed processing row
|
||||
can be inspected, safely deleted, and uploaded again; no automatic retry loop
|
||||
is run.
|
||||
|
||||
Media URLs are derived from SQLite metadata and never accept object keys. The
|
||||
display and thumbnail routes stream private S3 objects through the API with a
|
||||
one-year immutable cache policy. Original downloads use private, no-store
|
||||
caching.
|
||||
|
||||
## Storage and health
|
||||
|
||||
The API uses a small path-style S3 adapter configured by `S3_ENDPOINT`,
|
||||
`S3_REGION`, `S3_BUCKET`, `S3_ACCESS_KEY_ID`, and `S3_SECRET_ACCESS_KEY`.
|
||||
Connection timeout, socket-inactivity timeout (`S3_REQUEST_TIMEOUT_MS`),
|
||||
attempts, and background probe interval are configurable in `.env.example`.
|
||||
Startup verifies or safely creates only the configured bucket.
|
||||
|
||||
Storage outages do not stop the API or fail process readiness. `/health/ready`
|
||||
reports storage as degraded while remaining HTTP 200 when SQLite and the API
|
||||
runtime are healthy. `/api/health` and `/api/status` expose cached, redacted
|
||||
storage state and recover after a successful probe or storage operation.
|
||||
Reference in New Issue
Block a user