Backup do banco

Backup/exportação do banco MySQL pelo Hub. Um administrador pede um backup em JSON ou SQL, com escopo todo, tabelas ou parcial (por período e/ou lote de linhas). A rota grava um job pendente e enfileira uma task Celery; o worker gera um .zip no servidor, que fica disponível para download por BACKUP_RETENCAO_DIAS dias. O download é feito por token de uso único.

Dois routers no mesmo prefixo

O prefixo /backups monta dois routers com proteções diferentes. O router de operação usa _SCOPE_DEP e, rota a rota, o guard somente_admin_humano (administrador com JWT de pessoa; API Key recebe 403). O router de download é público (sem Bearer): o Hub responde 302 para a URL de download e o navegador não envia header no redirect, então a única credencial é o token de uso único (TTL de 60 s, consumido no primeiro uso).

Base path

Todos os endpoints abaixo são montados sob o prefixo:

/api/v1/backups

A URL completa em produção é https://api.contabilidadealcance.com.br/api/v1/backups.

Escopos necessários

EscopoOperações
backups:readGET /, GET /tabelas, GET /{job_id}
backups:writePOST /, POST /{job_id}/download-token, DELETE /{job_id}
(nenhum)GET /{job_id}/download - sem Bearer, autenticado só pelo token na query

O escopo é derivado da rota (prefixo /backups → recurso backups; GET = read, POST/DELETE = write). Além do escopo, todas as rotas do router de operação exigem perfil = administrador e token_type == "user".

Endpoints

GET /backups/tabelas

Catálogo de tabelas exportáveis, lido do information_schema do MySQL (inclui tabelas sem model mapeado). É a lista usada pelo multiselect do Hub e pela validação do POST.

Parâmetros

Este endpoint não recebe parâmetros.

Request

Sem corpo de requisição.

Response 200 OK

{
  "status": "success",
  "message": "Tabelas recuperadas com sucesso",
  "data": [
    {
      "nome": "lembrete",
      "linhas_estimadas": 18420,
      "colunas_data": ["data_hora_envio", "data_hora_criacao"]
    }
  ]
}

linhas_estimadas é a estatística TABLE_ROWS do InnoDB (aproximada). colunas_data lista as colunas DATE/DATETIME/TIMESTAMP - as únicas aceitas em periodo.coluna. Escopo: backups:read + administrador com JWT de pessoa

GET /backups/

Lista os backups, mais recentes primeiro.

Parâmetros

ParâmetroTipoLocalObrigatórioDescrição
limitintquerynãoQuantidade máxima (padrão 50, entre 1 e 200).

Request

Sem corpo de requisição.

Response 200 OK

{
  "status": "success",
  "message": "Backups recuperados com sucesso",
  "data": [
    {
      "id": 31,
      "formato": "sql",
      "escopo": "parcial",
      "tabelas": ["lembrete"],
      "periodo": { "coluna": "data_hora_criacao", "de": "2026-09-01", "ate": "2026-09-30" },
      "lote_linhas": null,
      "compactar": true,
      "status": "concluido",
      "progresso_pct": 100,
      "etapa": null,
      "tamanho_bytes": 482133,
      "sha256": "9f2c4e7a1b0d3c5e6f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e",
      "nome_arquivo": "backup_31.zip",
      "erro": null,
      "solicitante_id": 7,
      "solicitante_login": "admin.hub",
      "ip": "10.0.0.15",
      "criado_em": "2026-10-14T10:00:00",
      "iniciado_em": "2026-10-14T10:00:03",
      "concluido_em": "2026-10-14T10:00:41",
      "expira_em": "2026-10-21T10:00:41"
    }
  ]
}

Escopo: backups:read + administrador com JWT de pessoa

POST /backups/

Solicita um backup. A geração é assíncrona: a resposta volta com o job em pendente e o progresso é acompanhado por GET /backups/{job_id}. Rate limit de 10 requisições por 60 s (bucket backups-write).

Parâmetros

Sem parâmetros de rota ou query. Corpo BackupJobCreate:

CampoTipoObrigatórioDescrição
formatostrsimjson ou sql.
escopostrsimtodo, tabelas ou parcial.
tabelaslist[str]condicionalObrigatória (ao menos 1) em tabelas/parcial; proibida em todo. Máx. 500, sem repetição.
periodoPeriodoBackupcondicionalSó em parcial: coluna (DATE/DATETIME), de, ate (ate não pode ser anterior a de).
lote_linhasintcondicionalSó em parcial, entre 1000 e 1000000.
compactarboolnãoPadrão true. Nesta versão o backup é sempre .zip; false retorna 422.

O escopo parcial exige periodo e/ou lote_linhas. As tabelas precisam existir no catálogo e, com periodo, ao menos uma delas precisa ter a coluna informada.

Request

{
  "formato": "sql",
  "escopo": "parcial",
  "tabelas": ["lembrete"],
  "periodo": { "coluna": "data_hora_criacao", "de": "2026-09-01", "ate": "2026-09-30" }
}

Response 202 Accepted

Envelope com o BackupJobOut recém-criado (status: "pendente", progresso_pct: 0) e a mensagem "Backup enfileirado".

Erros: 422 (validação estrutural ou tabela/coluna inexistente), 503 (fila indisponível - o job fica gravado com status: "falha"). Escopo: backups:write + administrador com JWT de pessoa

GET /backups/{job_id}

Detalhe de um backup.

Parâmetros

ParâmetroTipoLocalObrigatórioDescrição
job_idintpathsimId do job.

Request

Sem corpo de requisição.

Response 200 OK

Envelope com um BackupJobOut e a mensagem "Backup recuperado". Job inexistente → 404.

Escopo: backups:read + administrador com JWT de pessoa

POST /backups/{job_id}/download-token

Gera um token de download de uso único, válido por 60 s, e devolve a URL pronta. O token fica no Redis e é consumido no primeiro download. A base da URL vem da configuração API_PUBLIC_URL; vazia, usa a base da própria requisição. A emissão gera um evento na trilha de auditoria (o token nunca é registrado).

Parâmetros

ParâmetroTipoLocalObrigatórioDescrição
job_idintpathsimId do job.

Request

Sem corpo de requisição.

Response 200 OK

{
  "status": "success",
  "message": "Token gerado",
  "data": {
    "token": "q8V3m1lQe0J9x2R7tYk4Hc6Pn5Wb1Za8Ds0Fg3Lu2Io",
    "expira_em": "2026-10-14T10:05:12",
    "url": "https://api.contabilidadealcance.com.br/api/v1/backups/31/download?token=q8V3m1lQe0J9x2R7tYk4Hc6Pn5Wb1Za8Ds0Fg3Lu2Io"
  }
}

Erros: 404 (job inexistente), 409 (job ainda não concluido), 410 (expirado ou removido), 503 (Redis indisponível - não há fallback em memória). Escopo: backups:write + administrador com JWT de pessoa

GET /backups/{job_id}/download

Baixa o .zip do backup. Rota pública (sem Bearer, sem _SCOPE_DEP): a credencial é o token gerado no passo anterior, que só vale uma vez e precisa pertencer ao mesmo job_id. Rate limit de 30 requisições por 60 s por IP (bucket backups-download). Só o download bem-sucedido é gravado na trilha de auditoria (ator anônimo, com IP).

Parâmetros

ParâmetroTipoLocalObrigatórioDescrição
job_idintpathsimId do job.
tokenstrquerysimToken de uso único (1 a 128 caracteres).

Request

Sem corpo de requisição.

Response 200 OK

Arquivo binário (Content-Type: application/zip, Cache-Control: no-store), com o nome do arquivo no Content-Disposition.

Erros: 403 (token inválido, expirado, já utilizado ou de outro job), 410 (backup expirado, removido ou arquivo ausente no servidor), 503 (Redis indisponível). Escopo: nenhum - autenticação apenas pelo token

DELETE /backups/{job_id}

Remove o backup: apaga o arquivo do servidor e marca o job como removido (o registro permanece). Idempotente para job já removido.

Parâmetros

ParâmetroTipoLocalObrigatórioDescrição
job_idintpathsimId do job.

Request

Sem corpo de requisição.

Response 204 No Content

Sem corpo de resposta. Erros: 404 (job inexistente), 409 (job em executando). Escopo: backups:write + administrador com JWT de pessoa

Schemas

BackupJobOut

CampoTipoDescrição
idintId do job.
formatostrjson ou sql.
escopostrtodo, tabelas ou parcial.
tabelaslist[str]Tabelas pedidas (vazia em todo).
periodoPeriodoBackup | nullcoluna, de, ate.
lote_linhasint | nullTamanho do lote, quando pedido.
compactarboolSempre true nesta versão.
statusstrpendente, executando, concluido, falha, expirado ou removido.
progresso_pctintProgresso de 0 a 100.
etapastr | nullEtapa corrente da geração.
tamanho_bytesint | nullTamanho do .zip.
sha256str | nullHash do arquivo.
nome_arquivostr | nullNome do arquivo gerado.
errostr | nullMensagem curta quando status = falha.
solicitante_idintUsuário que pediu.
solicitante_loginstr | nullLogin do solicitante (snapshot).
ipstr | nullIP da requisição.
criado_emdatetimeCriação do job.
iniciado_emdatetime | nullInício da execução.
concluido_emdatetime | nullConclusão.
expira_emdatetime | nullFim da disponibilidade para download.

O caminho do arquivo no servidor nunca sai pela API.

DownloadTokenOut

CampoTipoDescrição
tokenstrToken de uso único.
expira_emdatetimeValidade do token (emissão + 60 s).
urlstrURL completa de GET /backups/{job_id}/download.

Notas

  • Ciclo de vida: pendente → executando → concluido → expirado; ou executando → falha; e qualquer estado → removido via DELETE.
  • Colunas sensíveis mascaradas: o backup mantém as tabelas, mas anula colunas de segredo (NULL no SQL, null no JSON) - por exemplo user.senha, portal_user.senha_hash, api_key.key_hash, customer.senha_gov, integration_setting.api_key, os tokens da Cora/TecnoSpeed e a coluna valor de configuracao_sistema. A lista central fica em app/domain/orquestracao/backup/sensiveis.py.
  • Datas: os campos de data do job são gravados em hora de São Paulo (naive).