136 lines
6.0 KiB
Markdown
136 lines
6.0 KiB
Markdown
# 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.
|
||
|
||
## Playlists
|
||
|
||
Playlist and entry metadata is authoritative in SQLite. The API surface is:
|
||
|
||
- `GET /api/playlists`
|
||
- `POST /api/playlists`
|
||
- `GET /api/playlists/:playlistId`
|
||
- `PATCH /api/playlists/:playlistId`
|
||
- `DELETE /api/playlists/:playlistId`
|
||
- `POST /api/playlists/:playlistId/entries`
|
||
- `PATCH /api/playlists/:playlistId/entries/:entryId`
|
||
- `DELETE /api/playlists/:playlistId/entries/:entryId`
|
||
- `PUT /api/playlists/:playlistId/entries/order`
|
||
- `POST /api/playlists/:playlistId/activate`
|
||
|
||
Entry positions are zero-based and contiguous. Reordering requires the complete
|
||
ordered list as `{ "entryIds": [...] }`. An asset can occur only once in a
|
||
playlist, but the same asset can be used independently by any number of other
|
||
playlists. Only ready, unexpired assets can be newly added.
|
||
|
||
Entry duration overrides may be null (inherit the global setting) or 1,000 to
|
||
3,600,000 milliseconds. Presentation overrides are nullable; darkening is an
|
||
inherit/off/on boolean and blur always comes from the global setting. Schedule
|
||
instants use epoch milliseconds or ISO strings with an explicit `Z` or numeric
|
||
UTC offset, and intervals are evaluated as `[start, end)`.
|
||
|
||
Playlist detail responses include effective duration and presentation,
|
||
server-evaluated eligibility, immutable display/thumbnail URLs, and the
|
||
settings revision and evaluation time used. Playlist revisions increment once
|
||
per semantic content change. Exact no-ops and reads do not increment them.
|
||
|
||
Activation is explicit. A request is held in the existing pending-activation
|
||
state while a playable normal slide is mid-duration; otherwise the target
|
||
playlist is prepared immediately at its first currently playable entry. The
|
||
output-off flag remains only a visual gate, and static hold is preserved while
|
||
the hidden normal playlist changes. Phase 3A does not include the advancing
|
||
playback timer that will consume pending activation at a slide boundary.
|
||
|
||
Socket.IO publishes `playlists:changed` as a refetch invalidation after
|
||
committed playlist changes and after settings or referenced-asset edits that
|
||
affect resolved playlist data. Socket.IO is not a mutation API.
|
||
|
||
## 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.
|