Skip to main content

Uploads

Overview

The Uploads module provides file upload and management for Lumio accounts. Files are uploaded via multipart REST endpoints and stored in a configurable storage backend (e.g., S3-compatible object storage). Upload metadata (filename, content type, size, storage key, purpose) is tracked in PostgreSQL. The module enforces plan-based limits on maximum file size and total storage quota. Download URLs are generated as presigned URLs with configurable expiry.

Architecture

Backend

  • GraphQL (apps/api/src/graphql/uploads.rs) -- Queries for listing and fetching uploads. Mutation for deleting uploads (metadata only; storage cleanup is handled by the REST endpoint or background job).
  • REST (apps/api/src/routes/uploads.rs) -- Full REST CRUD: list uploads, get upload with presigned download URL, multipart file upload, and delete (storage + metadata).
  • Storage -- Configurable storage backend accessible via AppState.storage. Supports upload(), presigned_download_url(), and delete() operations.
  • Database (apps/api/src/db/uploads.rs) -- PostgreSQL operations for upload metadata CRUD.

Frontend

  • File manager with upload list, size display, and download links.
  • Drag-and-drop file upload with progress indicator.
  • Files are referenced by other features (e.g., overlay custom assets, alert sounds/images).

API

GraphQL Queries

QueryPermissionDescription
uploadsuploads:readList all uploads for the account. Includes a generated downloadUrl pointing to the REST endpoint.
upload(id: UUID)uploads:readGet a single upload by ID (account-scoped)

GraphQL Mutations

MutationPermissionDescription
deleteUpload(id: UUID)uploads:deleteDelete upload metadata from database. Note: storage cleanup is handled separately.

REST Endpoints

MethodPathPermissionDescription
GET/v1/uploadsuploads:readList all uploads for the account
GET/v1/uploads/{id}uploads:readGet upload details with presigned download URL (1-hour expiry)
POST/v1/uploadsuploads:createUpload a file via multipart form. Field name: file (required), purpose (optional). Enforces max upload size from config.
DELETE/v1/uploads/{id}uploads:deleteDelete upload: removes file from storage (best effort) and metadata from database

Upload Request Format

Multipart form data with fields:

  • file (required) -- The file to upload. Filename and content type are extracted from the content disposition.
  • purpose (optional) -- Purpose label for the upload (e.g., "alert_sound", "overlay_image").

Storage Key Format

{account_id}/{uuid_v7}.{extension}

Example: 550e8400-e29b-41d4-a716-446655440000/01912345-6789-7abc-def0-123456789abc.png

Permissions

PermissionDescription
uploads:readView and download uploaded files
uploads:createUpload new files
uploads:deleteDelete uploaded files

Database

TableDatabaseDescription
uploadsPostgreSQLid, account_id, uploaded_by (user UUID), filename, content_type, size_bytes, storage_key, purpose, created_at

Data Flow

Upload Flow

  1. Client sends a multipart POST to /v1/uploads with the file and optional purpose.
  2. Server reads the file data in chunks, checking against max_upload_size_bytes during upload.
  3. A unique storage key is generated: {account_id}/{uuid_v7}.{ext}.
  4. File is uploaded to the storage backend via storage.upload().
  5. Metadata (filename, content_type, size, storage_key, purpose) is saved to PostgreSQL.
  6. Response includes the upload metadata.

Download Flow

  1. Client requests GET /v1/uploads/{id}.
  2. Server verifies account ownership.
  3. If a storage backend is available, a presigned download URL is generated with 1-hour expiry.
  4. The upload metadata and download URL are returned.

Delete Flow

  1. Client requests DELETE /v1/uploads/{id}.
  2. Server verifies account ownership.
  3. File is deleted from storage (best effort -- logged if it fails).
  4. Metadata is deleted from PostgreSQL.

Plan-Based Limits

LimitFreeProEnterprise
Max upload size5 MBHigherHigher
Max total storage100 MBHigherHigher

The max_upload_size_bytes is checked during the upload stream, so oversized files are rejected early without consuming full bandwidth.

Key Files

PathDescription
apps/api/src/graphql/uploads.rsGraphQL queries and mutations
apps/api/src/routes/uploads.rsREST endpoints (list, get, upload, delete)
apps/api/src/db/uploads.rsDatabase operations for upload metadata
apps/api/src/state.rsAppState.storage -- storage backend reference