Pular para o conteúdo principal

Google Drive — mídia de utilizador

Documentação técnica da integração de foto de perfil, icon e banner com Google Drive.

Diagrama: arquitetura.drawio
Guia frontend: FRONTEND_USER_MEDIA_DRIVE.md


Visão geral

O backend recebe imagens (base64 ou multipart), envia para um Shared Drive via conta de serviço, persiste URL/ID em user_branding.metadata e serve bytes pelas rotas GET .../icon|banner.

Frontend → UsersController → UsersService → GoogleDriveService → Shared Drive (arkus)

UserRepository → PostgreSQL (metadata sem base64 no detalhe)

Estrutura de pastas no Drive

Raiz configurada em GOOGLE_DRIVE_USER_MEDIA_FOLDER_ID (Shared Drive arkus):

{Shared Drive}/
{Nome da Empresa}/ ← nome fantasia ou razão social (JOIN company)
profile/ ← icon / foto de perfil
banner/
documents/ ← reservado (documentos ainda base64 no PG)
_sem_empresa/ ← utilizador sem empresa
_system/health-probe/ ← probes do health check

Utilitários: src/modules/storage/google-drive-folder.util.ts, google-drive-media.types.ts.


Variáveis de ambiente

Onde configurar: ficheiro .env na raiz do projeto — desenvolvimento e produção (VPS, Docker env_file, PM2). Copie de .env.example; não versionar o .env.

VariávelDescrição
GOOGLE_DRIVE_USER_MEDIA_FOLDER_IDID da pasta raiz (Shared Drive arkus)
GOOGLE_DRIVE_SERVICE_ACCOUNT_JSONProdução: JSON completo da conta de serviço numa linha
GOOGLE_APPLICATION_CREDENTIALSDev: caminho relativo/absoluto ao JSON; em prod prefira SERVICE_ACCOUNT_JSON

Conta de serviço: partilhar o Shared Drive com o client_email do JSON (papel Content manager ou superior).

Exemplo .env (produção)

GOOGLE_DRIVE_USER_MEDIA_FOLDER_ID=0AKcaVeNV8V6AUk9PVA
GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"...","client_email":"...@....iam.gserviceaccount.com",...}

Após alterar o .env, reinicie a API e confira GET /api/storage/drive/health.


Deploy no Dokploy (VPS)

O Dockerfile só copia dist/ e node_modulesnão leva .env nem crm-drive-*.json (este último está no .gitignore de propósito).

Por isso, em produção no Dokploy, as variáveis Google não vêm do repositório Git — configuram-se no painel do Dokploy:

  1. Abra o projeto → Environment (ou Environment Variables).

  2. Adicione (ou cole do teu .env local):

    NomeValor
    GOOGLE_DRIVE_USER_MEDIA_FOLDER_ID0AKcaVeNV8V6AUk9PVA
    GOOGLE_DRIVE_SERVICE_ACCOUNT_JSONJSON completo da service account numa linha
  3. Não uses GOOGLE_APPLICATION_CREDENTIALS com caminho relativo no Dokploy — dentro do contentor não existe o ficheiro crm-drive-....json a menos que o cries à mão.

  4. Redeploy da aplicação.

  5. Teste: GET https://<teu-dominio>/api/storage/drive/health"ok": true.

O que fica no repositório vs no Dokploy

OndeO quê
Git (repo)Código, Dockerfile, .env.example (sem segredos)
Dokploy (Environment).env de produção: POSTGRES_*, JWT_SECRET, GOOGLE_DRIVE_*, etc.
Nunca no Gitcrm-drive-*.json, chaves privadas, passwords

Alternativa no Dokploy: secção Env File — colar o conteúdo inteiro do teu .env local (incluindo Google), equivalente a ter o ficheiro no VPS sem o commitares.

Para gerar a linha GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON a partir do JSON local:

node -e "console.log('GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON='+JSON.stringify(require('./crm-drive-upload-496417-de28c351e298.json')))"

Copia a saída para o Environment do Dokploy (ou substitui no .env local e usa Env File).


Endpoints

Upload / atualização

TipoMétodoRotaPersistência
UtilizadorPATCH/api/users/me/banneruser_branding.metadata.bannerImage*
UtilizadorPOST/api/users/me/mediaidem (campo banner)
Tenant (empresa)PATCH/api/tenants/id/{tenantId}/bannertenants.metadata.bannerImage*
TenantPATCH/api/tenants/nome/{nome}/banneridem

Leitura

TipoMétodoRota
UtilizadorGET/api/users/me/banner
TenantGET/api/tenants/id/{tenantId}/banner
TenantGET/api/tenants/nome/{nome}/banner
Login públicoGET/api/tenants/{slug}/nomedata.banner.dataUrl (URL Drive ou base64 legado)

Diagnóstico (público)

MétodoRotaDescrição
GET/api/storage/drive/healthAutenticação e permissões
GET/api/storage/drive/test-company-folder?companyName=Cria pasta de teste + upload PNG

Campos em user_branding.metadata

TipoCampos
Perfil / iconprofileImageUrl, profileImageDriveFileId, profileImageMimeType, profileImageSizeBytes
BannerbannerImageUrl, bannerImageDriveFileId, bannerImageMimeType, bannerImageSizeBytes

Coluna imagem_logotipo: preenchida com URL se couber em 255 caracteres.

Sanitização no detalhe

GET /api/users/{uuid} e GET /api/users/me removem icon.base64 e banner.base64 do metadata (sanitizeUserBrandingMetadataForDetail em user.repository.ts).


Código principal

ArquivoResponsabilidade
src/modules/storage/google-drive.service.tsUpload, download, health, pastas
src/modules/storage/storage-health.controller.tsRotas /storage/drive/*
src/modules/users/users.service.tsOrquestração upload/leitura
src/repositories/user-branding.repository.tsPersistência Drive metadata
src/common/utils/user-branding-drive-metadata.util.tsHelpers de metadata

Limites e formatos

  • Tamanho máximo: 10 MB (MAX_BRANDING_IMAGE_BYTES)
  • MIME: JPEG, PNG, SVG (BRANDING_IMAGE_MIME_TYPES)
  • Ficheiros no Drive: permissão anyone reader (link público)

Script manual

node scripts/test-drive-company-folder.js "Nome Empresa" profile

Pendências conhecidas

  • Documentos de utilizador (user_documents) ainda em base64 no PostgreSQL
  • Pastas antigas com UUID no Drive (novos uploads usam nome da empresa)