File Sharing

Upload, download, and share files with your team.

5 min read

Uploading Files

File uploads are submitted as multipart form data to the REST API. The server validates the MIME type against a fixed allowlist before writing the file to disk. Files that fail validation are rejected with a 415 Unsupported Media Type response before any bytes are written.

POST /api/files operator / admin
Upload a file. Body must be multipart/form-data with a file field.

MIME Allowlist

The following MIME types are accepted:

Type Extensions
Images .jpg, .jpeg, .png, .gif, .webp
Documents .pdf, .txt, .md
Spatial data .geojson, .gpx, .kml
Audio .webm (Opus), .ogg, .mp3, .wav
Video .mp4, .webm, .mov
Archives .zip

Executable files, scripts, and binary formats outside the allowlist are blocked. Validation is performed on both MIME type and file extension to prevent bypass via content-type spoofing.

Drag-and-Drop UI

The slide-out Files panel accepts drag-and-drop uploads. Dragging one or more files onto the panel highlights a drop zone. Releasing the files begins the upload immediately. A progress bar tracks the upload percentage for each file. Multiple files can be queued and upload sequentially. Completed uploads appear in the file list in real time as the server confirms each one.

Downloading

GET /api/files/:id/download all authenticated
Download the binary content of a file. Returns the file with appropriate Content-Type and Content-Disposition: attachment headers.

All downloads require a valid JWT. The server checks the token on every download request, preventing unauthenticated access to file contents even when a direct URL is known. In the client, download links are constructed with the current session token appended as a query parameter so the browser's native download behavior works without requiring custom fetch logic.

File List Metadata

GET /api/files all authenticated
List all files with metadata. Results ordered by upload timestamp descending.

Each entry in the response includes:

  • id — unique file identifier
  • filename — original uploaded filename
  • mime_type — validated MIME type
  • size — file size in bytes
  • uploader_callsign — callsign of the user who uploaded the file
  • created_at — ISO 8601 upload timestamp

Spatial Content

GET /api/files/:id/content all authenticated
Parse and return the spatial content of a GeoJSON or GPX file as a normalized GeoJSON FeatureCollection with a computed bounding box.

When a file is a recognized spatial format, this endpoint returns a parsed representation rather than the raw bytes. GPX files are converted to GeoJSON server-side — the same pipeline used by the Map Overlays system. The response envelope looks like:

{
  "type": "FeatureCollection",
  "features": [ /* ... */ ],
  "bbox": [-118.5, 33.9, -118.1, 34.2]
}

The bbox array follows the GeoJSON convention: [minLon, minLat, maxLon, maxLat]. Clients can pass this directly to MapLibre's fitBounds method to zoom the map to the file's extent.

This endpoint is the foundation for the overlay loading workflow. Uploading a GeoJSON or GPX file and then enabling it as an overlay uses this content endpoint to read the parsed geometry into the map source.

Media Playback

Audio and video files can be played directly from the Files panel without downloading. Playback uses authenticated blob URLs — the client fetches the file binary with a JWT header, then creates a temporary in-browser URL for the media element.

  • Audio files — play inline with a play/stop toggle button on each row. Clicking play on a different file stops the current one. Blob URLs are revoked when playback ends or the panel closes.
  • Video files — open in an overlay modal with native browser controls (play, pause, seek, fullscreen). Closing the modal revokes the blob URL.

Voice recordings created via the optional PTT recording toggle appear as regular .webm audio files in the Files panel and can be played back, renamed, or downloaded like any other file.

Real-Time Notifications

File upload and deletion events are broadcast over Socket.IO to all connected clients. This allows every user's Files panel to update its list in real time without polling.

Event Direction Payload
file:created Server → all clients Full file metadata object (same fields as the list endpoint)
file:updated Server → all clients Updated file metadata object (e.g. after rename)
file:deleted Server → all clients { id } — the deleted file's identifier

Storage

Uploaded files are written to the server filesystem within a dedicated directory that is mounted as a named Docker volume. This means file contents survive container restarts and image updates. The volume is declared in docker-compose.yml and persists independently of the application containers.

File metadata — name, size, MIME type, uploader, and upload timestamp — is stored in the PostgreSQL files table. The on-disk path is recorded in the storage_path column and is used by the download and content endpoints to locate the file on the filesystem.

To back up uploaded files, copy the Docker volume data directory alongside a PostgreSQL dump. Both are included in the admin Data Export ZIP archive — see Data Export & Import for details.

Permissions

File sharing enforces role-based access control at every endpoint.

Action Observer Operator Admin
List files Yes Yes Yes
Download file Yes Yes Yes
View spatial content Yes Yes Yes
Upload file No Yes Yes
Rename own file No Yes Yes
Rename any file No No Yes
Delete own file No Yes Yes
Delete any file No No Yes
DELETE /api/files/:id uploader or admin
Delete a file and its metadata. The server checks that the requesting user is the original uploader or has the admin role before removing the file from disk and database.
PATCH /api/files/:id uploader or admin
Rename a file. Send { "filename": "new-name.ext" } as JSON. The filename is sanitized before storage. Only the original uploader or an admin can rename a file.

When authentication is disabled (development mode), all endpoints behave as if the requester has the admin role. Enable AUTH_REQUIRED=true before deploying to a multi-user environment. See Authentication & RBAC for configuration details.