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ável | Descrição |
|---|---|
GOOGLE_DRIVE_USER_MEDIA_FOLDER_ID | ID da pasta raiz (Shared Drive arkus) |
GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON | Produção: JSON completo da conta de serviço numa linha |
GOOGLE_APPLICATION_CREDENTIALS | Dev: 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_modules — nã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:
-
Abra o projeto → Environment (ou Environment Variables).
-
Adicione (ou cole do teu
.envlocal):Nome Valor GOOGLE_DRIVE_USER_MEDIA_FOLDER_ID0AKcaVeNV8V6AUk9PVAGOOGLE_DRIVE_SERVICE_ACCOUNT_JSONJSON completo da service account numa linha -
Não uses
GOOGLE_APPLICATION_CREDENTIALScom caminho relativo no Dokploy — dentro do contentor não existe o ficheirocrm-drive-....jsona menos que o cries à mão. -
Redeploy da aplicação.
-
Teste:
GET https://<teu-dominio>/api/storage/drive/health→"ok": true.
O que fica no repositório vs no Dokploy
| Onde | O quê |
|---|---|
| Git (repo) | Código, Dockerfile, .env.example (sem segredos) |
| Dokploy (Environment) | .env de produção: POSTGRES_*, JWT_SECRET, GOOGLE_DRIVE_*, etc. |
| Nunca no Git | crm-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
| Tipo | Método | Rota | Persistência |
|---|---|---|---|
| Utilizador | PATCH | /api/users/me/banner | user_branding.metadata.bannerImage* |
| Utilizador | POST | /api/users/me/media | idem (campo banner) |
| Tenant (empresa) | PATCH | /api/tenants/id/{tenantId}/banner | tenants.metadata.bannerImage* |
| Tenant | PATCH | /api/tenants/nome/{nome}/banner | idem |
Leitura
| Tipo | Método | Rota |
|---|---|---|
| Utilizador | GET | /api/users/me/banner |
| Tenant | GET | /api/tenants/id/{tenantId}/banner |
| Tenant | GET | /api/tenants/nome/{nome}/banner |
| Login público | GET | /api/tenants/{slug}/nome → data.banner.dataUrl (URL Drive ou base64 legado) |
Diagnóstico (público)
| Método | Rota | Descrição |
|---|---|---|
GET | /api/storage/drive/health | Autenticação e permissões |
GET | /api/storage/drive/test-company-folder?companyName= | Cria pasta de teste + upload PNG |
Campos em user_branding.metadata
| Tipo | Campos |
|---|---|
| Perfil / icon | profileImageUrl, profileImageDriveFileId, profileImageMimeType, profileImageSizeBytes |
| Banner | bannerImageUrl, 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
| Arquivo | Responsabilidade |
|---|---|
src/modules/storage/google-drive.service.ts | Upload, download, health, pastas |
src/modules/storage/storage-health.controller.ts | Rotas /storage/drive/* |
src/modules/users/users.service.ts | Orquestração upload/leitura |
src/repositories/user-branding.repository.ts | Persistência Drive metadata |
src/common/utils/user-branding-drive-metadata.util.ts | Helpers 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
anyonereader (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)