Guia para o admin do TryPost: o que criar em cada console para ligar Google Drive, Google Photos, Canva e Unsplash no composer, e como habilitar HEIC (fotos do iPhone).
Cada fonte é opcional. Ela só aparece no menu de mídia do composer (botão dividido ao lado do upload, seta ▾)
quando a flag dela está ligada e as chaves estão preenchidas. Nada reaproveita as credenciais do login
com Google/YouTube (GOOGLE_CLIENT_ID) nem do Google Business (GOOGLE_BUSINESS_*).
URLs usadas neste guia
| Ambiente | URL do app |
|---|---|
| Local (Herd) | https://app.trypost.test |
| Produção (Cloud) | https://app.trypost.it (padrão de APP_URL em config/app.php) |
Depois de mudar o .env, rode php artisan config:clear (e reinicie os workers em produção).
Os valores de exemplo abaixo são falsos, só para mostrar o formato. Nunca commite chaves reais.
Drive e Photos usam o mesmo projeto e o mesmo OAuth client. O login roda no servidor, igual ao Google
Business e ao YouTube: o composer abre uma janela em /integrations/google/start, o Google pede consentimento e
volta para GOOGLE_MEDIA_CLIENT_REDIRECT (/integrations/google/callback), e o servidor troca o código pelo token
(PKCE S256, state de uso único preso ao usuário e ao workspace, access_type=online, um escopo por fonte). Por
isso o client precisa do client secret e do URI de redirecionamento cadastrado.
- Acesse https://console.cloud.google.com e crie um projeto novo, ex.: TryPost Media. Não use o projeto do login/YouTube nem o do Google Business (evita nova revisão dos escopos já aprovados).
- Em APIs e serviços → Biblioteca, ative:
- Google Picker API (Drive)
- Google Drive API (Drive)
- Photos Picker API (Photos)
- Google Auth Platform → Branding: nome do app TryPost, e-mail de suporte, logo, links de política
de privacidade e termos (
https://trypost.it/terms), domínio autorizadotrypost.it. - Público-alvo: Externo. Enquanto estiver em Teste, adicione seu e-mail em Usuários de teste.
- Acesso a dados → Adicionar escopos:
https://www.googleapis.com/auth/drive.file— não sensível. Só os arquivos que o usuário escolher no Picker ficam acessíveis. Publicar em produção não exige revisão por causa dele.https://www.googleapis.com/auth/photospicker.mediaitems.readonly— sensível. Em produção exige verificação do Google (justificativa do escopo, vídeo demo do fluxo do Photos e URL da política de privacidade). Sem verificação: tela "app não verificado" e limite de 100 usuários.- Nunca adicione
drive.readonly: é escopo restrito e exige auditoria CASA.
- Quando for para produção: Publicar app. Para o Photos, envie a verificação e só depois ligue
GOOGLE_PHOTOS_ENABLED=trueno Cloud.
- Credenciais → Criar credenciais → ID do cliente OAuth → Aplicativo da Web, nome "TryPost Media Web".
- URIs de redirecionamento autorizados: o valor exato de
GOOGLE_MEDIA_CLIENT_REDIRECT(protocolo, subdomínio e sem barra no fim):https://app.trypost.it/integrations/google/callback- local: o Google só aceita domínio público (
.testé recusado). Como no Google Business, use uma URL pública (ex.:herd share), cadastrehttps://<host-publico>/integrations/google/callbacke troque o host deGOOGLE_MEDIA_CLIENT_REDIRECTno.envà mão.
- Origens JavaScript autorizadas: não são mais usadas pelo login (ele não roda no navegador). Pode deixar vazio. A restrição por referenciador da API key (1.4) continua valendo para o Picker do Drive.
- Copie o ID do cliente →
GOOGLE_MEDIA_CLIENT_IDe a chave secreta do cliente (começa comGOCSPX-) →GOOGLE_MEDIA_CLIENT_SECRET. O secret fica só no servidor.
- Credenciais → Criar credenciais → Chave de API.
- Restrições de aplicativo → Sites (referenciadores HTTP):
https://app.trypost.it/*https://app.trypost.test/*https://docs.google.com/*(o iframe do Picker roda nesse domínio)
- Restrições de API → Restringir chave → Google Picker API.
- Copie a chave →
GOOGLE_MEDIA_API_KEY. Ela começa comAIza; não confunda com o client secret (GOCSPX-...), que vai emGOOGLE_MEDIA_CLIENT_SECRET.
- IAM e administrador → Configurações do mesmo projeto.
- Copie o Número do projeto (só dígitos, não o ID do projeto) →
GOOGLE_MEDIA_APP_ID.
# Exemplo FALSO
GOOGLE_MEDIA_CLIENT_ID=123456789012-abc123def456.apps.googleusercontent.com
GOOGLE_MEDIA_CLIENT_SECRET=GOCSPX-EXEMPLO-FALSO-000000000000
GOOGLE_MEDIA_CLIENT_REDIRECT="${APP_URL}/integrations/google/callback"
GOOGLE_MEDIA_API_KEY=AIzaSyEXEMPLO-FALSO-0000000000000000
GOOGLE_MEDIA_APP_ID=123456789012
GOOGLE_DRIVE_ENABLED=true # aparece com client id, secret, redirect, API key e App ID
GOOGLE_PHOTOS_ENABLED=false # ligue só depois da verificação do escopo sensível (precisa de id, secret e redirect)
# GOOGLE_PHOTOS_MAX_ITEMS=10 # itens por escolha no Photos; nunca passa de 10 (limite da bandeja de ideias)- Abra o composer (Criar post) e clique na seta ▾ do botão de mídia.
- Google Drive: aparece no menu → janela de login do Google (abre no clique) → a janela fecha sozinha → Picker → escolha um arquivo → vira um tile no composer (primeiro "enviando", depois a miniatura).
- Google Photos: com a flag ligada, a mesma janela faz o login e segue para o Photos Picker; marque e
desmarque à vontade (até
GOOGLE_PHOTOS_MAX_ITEMS, padrão 10) e confirme com Concluído. Cada item vira um tile próprio, na ordem escolhida; um item que falhar (ex.: vídeo ainda processando) mostra o erro só no tile dele.
| Sintoma | Causa / correção |
|---|---|
| Item não aparece no menu | Flag desligada ou alguma chave GOOGLE_MEDIA_* vazia (Photos precisa de id, secret e redirect; Drive também de API key e App ID); rode php artisan config:clear |
redirect_uri_mismatch (erro 400 na janela do Google) |
GOOGLE_MEDIA_CLIENT_REDIRECT diferente do URI cadastrado em URIs de redirecionamento autorizados (protocolo, subdomínio, barra final) |
Console recusa https://app.trypost.test/... ("must end with a public top-level domain") |
Use uma URL pública (herd share), cadastre o callback dela e troque o host de GOOGLE_MEDIA_CLIENT_REDIRECT |
| Janela mostra "Não foi possível conectar ao Google" | Client secret errado/vazio (invalid_client), ou a janela foi aberta por outro usuário/workspace; confira GOOGLE_MEDIA_CLIENT_SECRET |
Picker abre em branco / The API developer key is invalid |
API key sem https://docs.google.com/* nos referenciadores, ou sem a Google Picker API na restrição |
| Picker mostra "arquivo não pode ser aberto" / 403 no download | GOOGLE_MEDIA_APP_ID não é o número deste projeto |
| "Este app não foi verificado" no Photos | Escopo sensível sem verificação; use usuários de teste até aprovar |
access_denied |
Usuário fora da lista de testes enquanto o app está em Teste |
Docs oficiais:
- https://developers.google.com/identity/protocols/oauth2/web-server
- https://developers.google.com/workspace/drive/picker/guides/overview
- https://developers.google.com/workspace/drive/api/guides/api-specific-auth
- https://developers.google.com/photos/picker/guides/get-started-picker
- https://developers.google.com/photos/picker/guides/media-items
- https://developers.google.com/photos/picker/reference/rest/v1/sessions (
maxItemCount) - https://support.google.com/cloud/answer/13463073 (verificação)
Na prática é só Cloud: exige uma integração pública aprovada pelo Canva. Integração privada só funciona dentro do seu time e só no plano Enterprise.
-
Ative MFA na conta Canva que vai ser dona da integração (o Developer Portal exige).
-
Acesse https://www.canva.com/developers/integrations/connect-api → Create an integration → escolha Public.
-
Configuration → Credentials:
- copie o Client ID →
CANVA_CLIENT_ID; - gere o Client secret →
CANVA_CLIENT_SECRET(aparece uma vez só; guarde num cofre).
- copie o Client ID →
-
Scopes: marque só
design:content:write(criar design)design:content:read(exportar PNG)design:meta:read(return navigation e reabrir design com "Editar no Canva")
Não marque
profile:read,asset:*nemfolder:*. -
Authentication → Redirect URLs (rota
app.integrations.canva.callback):https://app.trypost.it/integrations/canva/callback- local:
https://app.trypost.test/integrations/canva/callback(se o portal recusar, usehttp://127.0.0.1:<porta>/integrations/canva/callback). Remova as URLs locais antes de enviar para revisão.
-
Return navigation: ligue e defina a Return URL (rota
app.integrations.canva.return):https://app.trypost.it/integrations/canva/return- local:
https://app.trypost.test/integrations/canva/return(sem isso o Canva responde 400 ao abrir o design). Antes de enviar para revisão, deixe a de produção.
-
Nome TryPost, ícone, e rode o Submission checklist (nome público, redirect não local, nenhuma API em preview, guia de marca do Canva: o item do menu se chama "Canva" e usa o logo).
-
Submit for review. O Canva abre um ticket (Jira Service Desk) e toda a conversa segue por lá. Não há SLA publicado (conte com 1 a 3 semanas). Até aprovar, só membros do time dono conseguem conectar.
# Exemplo FALSO
CANVA_ENABLED=true
CANVA_CLIENT_ID=OC-EXEMPLOfalso123
CANVA_CLIENT_SECRET=cnvcaEXEMPLOfalsoNaoUse0000000000As URLs de callback e retorno não vão no .env: vêm das rotas nomeadas (APP_URL + caminho).
- Composer → seta ▾ do botão de mídia → Canva ▸ → escolha um tamanho (ex.: Quadrado 1:1).
- Abre um popup: na primeira vez, login/autorização do Canva; depois, o editor do Canva.
- Clique Return to TryPost no Canva → o popup fecha e o PNG aparece como tile.
- Passe o mouse no tile → selo Canva ("Editar no Canva") reabre o mesmo design e substitui o tile ao voltar.
| Sintoma | Causa / correção |
|---|---|
invalid_redirect_uri / redirect mismatch |
URL em Redirect URLs diferente de APP_URL + /integrations/canva/callback (protocolo, subdomínio, barra final) |
| Volta do Canva não traz a imagem | Return navigation desligada ou Return URL errada |
invalid_scope / 403 ao exportar |
Falta design:content:read ou design:meta:read |
| "Popup bloqueado" | O navegador bloqueou; o usuário precisa permitir popups para o domínio |
| Outros usuários não conseguem conectar | Integração ainda não aprovada (só o time dono funciona) |
| "Editar no Canva" falha | O design pertence à conta Canva de outra pessoa (comportamento esperado) |
Docs oficiais:
- https://www.canva.dev/docs/connect/
- https://www.canva.dev/docs/connect/authentication/
- https://www.canva.dev/docs/connect/appendix/scopes/
- https://www.canva.dev/docs/connect/return-navigation-guide/
- https://www.canva.dev/docs/connect/creating-integrations/
- https://www.canva.dev/docs/connect/submitting-integrations/
- https://www.canva.dev/docs/connect/submission-checklist/
Já integrado; a variável mantém o nome atual.
-
Acesse https://unsplash.com/oauth/applications → New Application, aceite as API Guidelines, nome TryPost.
-
Copie a Access Key →
UNSPLASH_ACCESS_KEY. A Secret Key não é usada (UNSPLASH_SECRET_KEYpode ficar vazia). -
O app começa em Demo (50 requisições/hora), suficiente para testar e para self-host pequeno.
-
Para produção, peça Production na página do app. O revisor confere:
- crédito ao fotógrafo e ao Unsplash com
?utm_source=trypost&utm_medium=referral; - uso direto (hotlink) das URLs de imagem que a API devolve;
- chamada ao
download_locationquando a foto é usada (download tracking).
Mande screenshots do modal do Unsplash no composer mostrando o crédito.
- crédito ao fotógrafo e ao Unsplash com
# Exemplo FALSO
UNSPLASH_ACCESS_KEY=exemploFALSO_abc123DEF456ghi789- Composer → seta ▾ → Unsplash → busca → clique numa foto → vira tile com o crédito salvo.
- Item não aparece:
UNSPLASH_ACCESS_KEYvazia. 401 OAuth error: The access token is invalid: chave errada ou revogada.403 Rate Limit Exceeded: limite do modo Demo; peça Production.
Docs: https://unsplash.com/documentation · https://help.unsplash.com/en/articles/2511245-unsplash-api-guidelines
HEIC/HEIF é aceito quando o PHP tem Imagick com suporte a HEIC e MEDIA_HEIC_CONVERSION=true. O arquivo é
convertido para JPEG no upload. Sem isso, o upload de .heic mostra "Este servidor não converte fotos HEIC.
Exporte como JPG e tente de novo."
O Herd já vem com Imagick + HEIC. Confira:
php -r 'var_dump(extension_loaded("imagick") && in_array("HEIC", Imagick::queryFormats("HEI*")));'bool(true) = pronto.
Na imagem Alpine, adicione os pacotes e a extensão (o plano de implementação faz essa mudança):
RUN apk add --no-cache imagemagick imagemagick-heic \
&& apk add --no-cache --virtual .build-deps $PHPIZE_DEPS imagemagick-dev \
&& pecl install imagick \
&& docker-php-ext-enable imagick \
&& apk del .build-depsDepois do build, rode o mesmo php -r de 4.1 dentro do container.
- Confirme que a extensão imagick está ativa no ambiente e que
Imagick::queryFormats('HEI*')listaHEIC(rode o comando de 4.1 pelo console de comandos do Cloud). - Se listar:
MEDIA_HEIC_CONVERSION=true. Se não:MEDIA_HEIC_CONVERSION=falseaté ter suporte (os usuários recebem a mensagem "exporte como JPG").
Arraste um .heic para o composer: o tile vira uma imagem JPEG normal e o editor (lápis) abre.
| Variável | Padrão | Efeito |
|---|---|---|
MEDIA_UPLOAD_RETENTION_HOURS |
24 |
Uploads temporários não usados são apagados depois disso (media:prune-uploads) |
POST_HISTORY_RETENTION_DAYS |
730 |
Posts publicados mais antigos são apagados com a mídia (posts:prune-history). 0/vazio é recusado |
- Projeto Google "TryPost Media" com Picker API, Drive API e Photos Picker API
- OAuth client Web com client secret em
GOOGLE_MEDIA_CLIENT_SECRETe o URI de redirecionamentoGOOGLE_MEDIA_CLIENT_REDIRECT(/integrations/google/callback) cadastrado - API key restrita a referenciadores (incluindo
https://docs.google.com/*) e à Google Picker API -
GOOGLE_MEDIA_APP_ID= número do projeto - Escopos:
drive.fileephotospicker.mediaitems.readonly; verificação enviada antes de ligar Photos - Integração Canva pública, MFA ativo, 3 escopos, redirect
/integrations/canva/callback, return/integrations/canva/return, enviada para revisão - Unsplash em Production (crédito + download tracking)
- Imagick com HEIC no Docker e no Laravel Cloud, ou
MEDIA_HEIC_CONVERSION=false -
php artisan config:cleare teste de cada item pelo menu ▾ do composer
Faça na ordem. Os passos 1 e 2 vêm antes do deploy, porque o passo 4 apaga arquivos de forma irreversível.
- Snapshot do storage. Tire um snapshot do bucket (R2/S3) ou ligue o versionamento. O
media:adopt-libraryapaga os arquivos que só existiam na biblioteca de mídia, e não há ferramenta de export. - Ensaio da migração da biblioteca. Numa cópia da produção, rode
php artisan media:adopt-library --dry-run(mostra linhas da biblioteca, referências a copiar, órfãos a apagar, arquivos faltando e posts em Publishing que vão adiar) e uma adoção real de um workspace grande, para confirmar que cabe no tempo do job. - Imagem Docker. Rebuild e publique a imagem com o
docker/Dockerfilenovo (imagemagick,imagemagick-dev,imagemagick-heicepecl imagick). - HEIC no Laravel Cloud. Confirme que o Imagick de lá converte HEIC (seção 4.3). Se não converter, defina
MEDIA_HEIC_CONVERSION=false; o upload de HEIC então é recusado com uma mensagem clara.
Todas estão no .env.example e no docker/.env.docker.example. As obrigatórias para cada fonte estão nas seções
1–3. Novas neste release, com padrão seguro:
| Variável | Padrão | Para quê |
|---|---|---|
MEDIA_UPLOAD_RETENTION_HOURS |
24 |
Uploads temporários não usados |
POST_HISTORY_RETENTION_DAYS |
730 |
Histórico de posts publicados (2 anos) |
MEDIA_HEIC_CONVERSION |
true |
Converter HEIC/HEIF para JPEG |
GOOGLE_MEDIA_CLIENT_SECRET |
vazio | Client secret do OAuth client de mídia (login do Drive/Photos no servidor); obrigatório para as duas fontes |
GOOGLE_MEDIA_CLIENT_REDIRECT |
${APP_URL}/integrations/google/callback |
Tem que bater com o URI cadastrado no Google |
GOOGLE_PHOTOS_MAX_ITEMS |
10 |
Itens por escolha no Photos (nunca acima de 10) |
GOOGLE_PHOTOS_DOWNLOAD_HOSTS |
lh3–lh6.googleusercontent.com, video-downloads.googleusercontent.com |
Confirmar o host real dos vídeos no teste manual; ajustar se for outro |
CANVA_CODE_CHALLENGE_METHOD |
s256 |
A doc do Canva se contradiz (s256/S256); confirmar no primeiro login |
CANVA_DOWNLOAD_HOSTS |
*.canva.com |
Hosts permitidos para baixar a exportação |
CANVA_EDITOR_HOSTS |
*.canva.com |
Hosts permitidos no link "Editar no Canva" |
Depois de mudar variáveis: php artisan config:clear.
- Comando de deploy, só neste release, depois das migrations:
php artisan release:trypost-2 --force(a adoção da biblioteca é o passo 3 dele e termina no próprio processo; roteiro emDEPLOYMENT.md§3). Omedia:adopt-libraryavulso fica emapp/Console/Commands/Scripts/(no Docker ele roda nodocker/entrypoint.sha cada boot e não faz nada depois que tudo foi migrado). - Filas novas. Os workers/Horizon precisam consumir
media-importserss-feeds(supervisores novos emconfig/horizon.php). Em quem usaqueue:work:--queue=default,media-imports,rss-feeds,.... Sem isso, Google Drive, Google Photos e Canva nunca importam e os feeds RSS não atualizam. - Scheduler já agenda sozinho:
media:prune-uploads(de hora em hora),posts:prune-history(diário) erss-feeds:poll(a cada 5 min). Só confirme que o scheduler está rodando.
- Se o
release:trypost-2deixou algum workspace para a fila, acompanhe os jobsAdoptWorkspaceLibraryJobno Horizon até zerarem. Se algum workspace falhar de vez, aparece um erro no log; rodephp artisan release:trypost-2 --forcede novo. - O
release:trypost-2termina commedia:audit(só leitura); confira que todas as checagens dão 0, ou rodephp artisan media:auditde novo depois que a fila drenar. - Janela até a migração terminar: duplicar um post ou recuperar um rascunho pode manter a mídia antiga da biblioteca até ela ser migrada, e o texto alternativo por IA dá 404 nessas mídias. Some sozinho quando a adoção termina.
- Proxy: se o servidor usa
HTTP_PROXY/HTTPS_PROXY, a fixação de IP contra DNS rebinding não vale (quem resolve o DNS é o proxy). Vale para self-hosted.
- Google Drive: escolher um arquivo pelo menu ▾ no composer e no editor de ideias; o seletor do Google por cima do composer recebe clique e teclado; testar com VoiceOver e só teclado; primeiro clique depois de recarregar a página no Safari (a janela de login não pode ser bloqueada); recusar o escopo mostra o aviso certo; a URL da janela nunca carrega o token.
- Google Photos: escolher várias fotos e um vídeo, desmarcar um e confirmar com Concluído; cada item vira
um tile na ordem escolhida; confirmar que o Picker para em
GOOGLE_PHOTOS_MAX_ITEMS; cancelar um tile ainda pendente; anotar o host do download do vídeo (ajustarGOOGLE_PHOTOS_DOWNLOAD_HOSTSse preciso); fechar a janela sem escolher não adiciona nada. - Canva: conectar a conta (confirmar
s256); criar um design, clicar "Return to TryPost" e ver o bloco virar imagem na bandeja; abrir um design existente pelo painel de projetos e voltar; revogar o acesso no Canva e ver a reconexão pedida; conferir o host do download da exportação. - Editar no Canva: num rascunho, num post agendado e numa cópia de post já publicado, editar a imagem e salvar; um colega com outra conta do Canva recebe o erro claro; testar também num canal com mídia própria e no editor de ideias.
- Changelog + docs.trypost.it + instruções do MCP (mudanças que quebram):
- saíram
GET /api/assets,GET /api/assets/{media}ePOST /api/posts/{post}/media/from-asset; - saíram as ferramentas MCP
list-assets-tool,get-asset-tooleattach-existing-asset-tool; - um item de mídia agora é exatamente um de
upload_token,urlouid(+alt/meta); snapshots antigos (id+path+url) são recusados; upload_tokené de uso único e expira em 24 h; IDs de mídia são por post;- um
idde mídia antiga da biblioteca falha como ID desconhecido; - posts publicados há mais de 2 anos são apagados com a mídia;
- apagar um post apaga os arquivos dele (TikTok/Instagram às vezes ainda buscam o arquivo logo após publicar);
- posts agendados com mídia fora das proporções da rede falham na publicação;
- novos:
POST /api/uploads,POST /api/posts/{post}/media/from-upload, HEIC aceito.
- saíram
- E-mail do release: histórico de 2 anos e fim da biblioteca de mídia (arquivos só da biblioteca foram apagados no deploy, sem export).