Documents Service
Stores documents in Azure Blob Storage, keeps their references and metadata in Postgres, and hands out short-lived SAS URLs.
The documents service holds files for every other service. Postgres owns the record — where the blob is, what it is called, what it is attached to — and Azure Blob Storage holds the bytes. No file content passes through this service in either direction: callers upload to and download from a SAS URL: A Shared Access Signature: a blob URL carrying a signed, time-limited grant of specific permissions. The documents service signs one per request with the storage account key and never stores it, so an expired URL cannot be replayed from a command response. that the service signs and that expires on its own.
- Domain
- Services
- Kind
- Go HTTP service
- Lifecycle
- Active
- flowctl project
- None
- Stacks
- Runs on its own
- Source
- services/documents
Run it locally
Azurite, the two-step upload, and curl examples.
Architecture
Tables, the storage-account catalog, and where a SAS is minted.
API
Endpoints, reference filters, and OpenAPI.
Operations
Azure Pipelines, Helm, storage configuration, and Service Bus topology.
Uploading takes two calls
A caller asks for a write SAS, PUTs the bytes to it, then tells this service the upload happened. The bytes never traverse this service, so a large file costs it nothing and an interrupted upload leaves no record behind.
Diagram source
sequenceDiagram
participant Caller
participant API as documents-api
participant PG as Postgres
participant Blob as Azure Blob Storage
Caller->>API: POST /v1/documents/sas
API-->>Caller: write SAS, blob_name, expiry
Caller->>Blob: PUT bytes to the SAS URL
Caller->>API: POST /v1/documents (blob_name)
API->>Blob: does the blob exist?
API->>PG: insert document, references, metadata, outbox event
API-->>Caller: the document recordThe blob name is {stem}_{uuidv7}.{ext}, chosen by the service rather than the caller, so two
uploads of lease.pdf cannot overwrite each other and a caller cannot aim an upload at a blob that
already belongs to another document.
What it owns
- Document records, with polymorphic references (
ref_type,ref_id,ref_category,is_external) andmeta_name/meta_valuemetadata - The storage-account catalog: which accounts exist, their visibility, containers, and SAS lifetime
- SAS signing for uploads and downloads
- The
documentsoutbox and itsdocuments.created/documents.updatedevents
What it does not own
The meaning of a reference. ref_type is a domain name and ref_id an id within it; this service
never resolves either, and does not check that the referenced entity exists. A caller that wants
its documents asks for them by the ids it already knows.
It also does not own file content lifecycle beyond the record: replacing a blob's bytes is not an operation today, and neither is deletion.
Authorization is not wired yet, matching services/template/README.md. Audit
columns use a placeholder actor (system).
Canonical service README: services/documents/README.md.