Storage

Storage#

Rotas /v1/cloudstore/* · owner/admin · gated por CLOUDSTORE_ENABLED (off → 404)

O que é#

Buckets de objetos gerenciados em cima do Google Cloud Storage. Você cria buckets por projeto e faz upload/download/organização de arquivos pela API — sem tocar no console da GCP. Os buckets ficam no mesmo GCP project que hospeda as instâncias de Database daquele projeto.

O que dá pra fazer#

  • Criar/listar/remover buckets; ver o overview (contagem de objetos, bytes totais, settings ao vivo do GCS).
  • Upload (multipart), download (stream autenticado ou signed URL keyless), delete.
  • Pastas (prefixos foo/bar/): criar, ver stats (count + bytes), apagar recursivamente.
  • Power-ops de objeto: stat (metadata completa), copy, move/rename, meta (editar content-type/cache/metadata/storage-class).
  • Acesso S3-compatível: emitir chaves HMAC e apontar qualquer SDK do S3/R2 para storage.googleapis.com.

Como funciona por dentro (na prática)#

  • Control-plane + GCS real. A API guarda a control-row do bucket no tenant DB; o objeto vive no GCS. O nome real do bucket é globalmente único (ctc-<hash-projeto>-<nome>) — você sempre usa o id da control-row.
  • Signed URLs keyless (V4). objects/sign devolve uma URL que o GCS serve direto (storage.googleapis.com), sem passar pelo Catcher nem expor credencial — ideal para o navegador baixar/exibir. Default TTL 15 min, máx. 7 dias.
  • Pastas são prefixos. O GCS não tem pastas; uma "pasta" é um placeholder zero-byte com chave terminada em /. Os stats e a varredura ignoram o placeholder.
  • Content-Type normalizado no upload: tipos textuais ganham ; charset=utf-8 e tipos vazios/octet-stream são inferidos da extensão — a signed URL renderiza UTF-8 no navegador sem mojibake.
  • S3-compat isolado por cliente: cada projeto usa uma SA dedicada (ctc-store@<projeto>) com acesso só ao próprio projeto; o secret da chave HMAC só aparece na criação.

Fluxo típico (end-to-end)#

bash
KEY="X-API-Key: ctc_…" ; B=https://data-api.catcher.one ; JS='Content-Type: application/json'
PID=<project-uuid>   # mesmo projeto do Database (ver Getting Started)

# 1. Cria o bucket  (tenant_id é opcional — dedica o bucket a um cliente final; ver Tenants)
BID=$(curl -sX POST $B/v1/cloudstore/projects/$PID/buckets -H "$KEY" -H "$JS" \
  -d '{"name":"assets","location":"southamerica-east1","storage_class":"STANDARD"}' | jq -r .id)

# 2. Upload (multipart). 'key' opcional aceita prefixo (cria a "pasta")
curl -sX POST $B/v1/cloudstore/buckets/$BID/objects -H "$KEY" \
  -F "file=@./logo.png" -F "key=brand/logo.png"

# 3. Lista sob um prefixo
curl -s "$B/v1/cloudstore/buckets/$BID/objects?prefix=brand/" -H "$KEY"

# 4. Signed URL para o navegador baixar/exibir (15 min)
curl -s "$B/v1/cloudstore/buckets/$BID/objects/sign?key=brand/logo.png&ttl=900" -H "$KEY"
# → { "url": "https://storage.googleapis.com/…", "expires_in": 900 }

Referência de endpoints#

Buckets#

Método Rota O que faz
POST /v1/cloudstore/projects/{pid}/buckets Cria (name, location?, storage_class?, tenant_id?) → 201
GET /v1/cloudstore/projects/{pid}/buckets Lista
GET /v1/cloudstore/buckets/{id}/overview object_count+total_bytes + settings GCS (versioning, public-access-prevention, UBLA)
DELETE /v1/cloudstore/buckets/{id} Remove (GCS + control-row) → 204
Isolamento por tenant (end-customers)

Passe tenant_id ao criar o bucket para dedicá-lo a um cliente final: o bucket fica isolado por IAM no GCS — um bucket por cliente. Sem tenant_id, o bucket é de nível projeto (compartilhado entre seus clientes). É o mesmo tenant_id usado no Database, Vector e Cache — ver Tenants.

Objetos#

Método Rota O que faz
GET /v1/cloudstore/buckets/{id}/objects?prefix=&limit= Lista (key,size,content_type,updated,etag)
POST /v1/cloudstore/buckets/{id}/objects Upload multipart (file + key?)
DELETE /v1/cloudstore/buckets/{id}/objects?key= Remove → 204
GET /v1/cloudstore/buckets/{id}/objects/sign?key=&ttl= Signed URL (download direto do GCS)
GET /v1/cloudstore/buckets/{id}/objects/download?key= Stream pelo control-plane (autenticado)
GET /v1/cloudstore/buckets/{id}/objects/stat?key= Metadata completa (generation, md5, crc32c, custom x-goog-meta-*, …)
POST /v1/cloudstore/buckets/{id}/objects/copy Duplica ({src,dst}, rewrite server-side) → 201
POST /v1/cloudstore/buckets/{id}/objects/move Move/rename ({src,dst}, copy+delete) → 200
PATCH /v1/cloudstore/buckets/{id}/objects/meta?key= Edita content-type/cache/disposition/language/storage-class/metadata

Pastas#

Método Rota O que faz
POST /v1/cloudstore/buckets/{id}/folders Cria pasta vazia ({path}) → 201 {path:"docs/sub/"}
GET /v1/cloudstore/buckets/{id}/folders/stats?path= {count, total_bytes} sob o prefixo
DELETE /v1/cloudstore/buckets/{id}/folders?path= Apaga recursivamente tudo sob o prefixo → {deleted:N}

Acesso S3-compatível (HMAC)#

Método Rota O que faz
POST /v1/cloudstore/projects/{pid}/hmac-keys Emite chave (secret só aqui)
GET /v1/cloudstore/projects/{pid}/hmac-keys Lista
DELETE /v1/cloudstore/projects/{pid}/hmac-keys/{accessId} Revoga

Aponte qualquer SDK do S3/R2 para https://storage.googleapis.com com a chave/secret.

Códigos de erro do módulo#

INVALID_BUCKET_NAME (400) · BUCKET_ALREADY_EXISTS (409) · BUCKET_NOT_FOUND (404) · BUCKET_NOT_READY (409) · OBJECT_NOT_FOUND (404) · NO_READY_PROJECT (409, sem GCP project pronto no projeto) · S3_ACCESS_DISABLED_BY_POLICY (409, org policy bloqueia chaves de SA).

Gotchas#

  • Use sempre o id da control-row, não o nome real do GCS (ctc-…).
  • Para o navegador, prefira sign (download direto, sem custo de proxy); download é o stream autenticado pelo control-plane (útil server-side).
  • Apagar pasta é recursivo e definitivopath não pode ser vazio///./.. (guard contra apagar o bucket inteiro).
  • move não é atômico (copy + delete) — em falha no meio, pode sobrar a origem.
  • NO_READY_PROJECT → o projeto ainda não tem um GCP project provisionado (geralmente porque nenhuma instância de Database foi criada nele ainda).