Roteiro para levar à produção a branch feat/merge-post-platforms-into-posts, na
ordem. Cada post passa a guardar o seu canal e o resultado da publicação na
própria linha de posts; a tabela post_platforms é copiada para posts e
removida no mesmo deploy.
Pré-requisito antes do merge: marcar a
mainatual (o 2.0 que está no ar,311767990) com a tag e a releasev2.0.0. Hoje a última tag é av1.1.0, e sem av2.0.0o self-hosted não tem por onde passar pelorelease:trypost-2, que esta versão remove.
O roteiro do deploy do TryPost 2.0 (release:trypost-2) fica na tag v2.0.0.
Self-hosted que ainda está na v1.1.0 precisa passar por ela antes: atualizar
para a v2.0.0, rodar php artisan release:trypost-2 --force e só então
atualizar para esta versão. A primeira migration desta versão para com uma
mensagem explicando isso se encontrar posts com mais de um canal.
Por ser um deploy só (sem releases intermediários), ele roda em manutenção, com todas as filas drenadas: nenhum job antigo, guardando o id de um destino que deixa de existir, pode sobrar para o código novo.
-
Ensaio numa cópia da produção (banco e bucket próprios), com o código desta branch:
php artisan migrate --force
As quatro migrations novas (
2026_10_08_2229*) devem terminar sem erro. A primeira resolve sozinha os restos do split do 2.0: apaga os destinos cuja conta sumiu e os destinos desligados que nunca publicaram, transforma em rascunho os posts não publicados sem canal, apaga (com a mídia, sem webhook) os posts publicados sem canal (os arquivos só depois do commit, e só os que nenhuma outra mídia usa) e resolve ospartially_publishedcom um destino só. Ela para, sem mudar nada, se ainda houver: post com mais de um destino, destino desligado que publicou,partially_publishedcom destino em andamento ou destino de outro workspace. A mensagem diz quantos de cada.Na cópia de produção de 09/10/2026 eram 45.520 destinos desligados, 478 rascunhos e 1 post com falha sem canal, e 139 posts publicados sem canal (em 4 workspaces, com 236 arquivos), apagados por decisão do Paulo. Anote quanto tempo o
migratelevou (estimativa: 20–60 s para ~20 mil posts). -
Se o ensaio parou na primeira migration, resolva os casos na produção antes do deploy (com o código que está no ar):
php artisan posts:split-legacy-active php artisan posts:audit-legacy --strict
O que sobrar (alvo preso em revisão, mídia que não copiou) é tratado à mão.
-
Script de deploy do Forge: adicione, depois de
$RESTART_QUEUES(),$FORGE_PHP artisan horizon:terminateSem ele o Horizon reinicia os workers dentro da pasta do release antigo e continua rodando o código antigo. Vale para todo deploy, não só este. Confira também que
storage/é compartilhado entre releases (readlink releases/*/storage). -
Comunicação: avise os usuários do horário da janela (alguns minutos sem publicar; os posts agendados para a janela saem logo depois). Avise quem usa a API, o MCP ou webhooks sobre a mudança de contrato (seção 4) antes do deploy.
- No release atual:
Web, Horizon e scheduler param (nenhum agendamento usa
php artisan down --retry=60 --secret=<token>
evenInMaintenanceMode). - Drene todas as filas com o código antigo (o
--forceprocessa mesmo em manutenção):Confira a lista de filas emphp artisan queue:work redis --queue=social-linkedin,social-linkedin-page,social-x,social-tiktok,social-youtube,social-facebook,social-instagram,social-instagram-facebook,social-threads,social-pinterest,social-bluesky,social-mastodon,social-telegram,social-discord,social-google_business,default,posthog,broadcasts,analytics,webhooks,media-imports,media-adoption,rss-feeds --force --stop-when-empty
config/horizon.php. Espere também os jobs com atraso das filassocial-*,default,broadcasts,posthogewebhooks(até 10 minutos, os retries de rede indisponível) e rode o comando de novo até o Redis não ter nada emqueues:<essas filas>:delayednem emqueues:*:reserved. Os atrasados da filaanalytics(métricas de stories, até 24 h) só carregam ids de publicação e podem ficar. Os jobs que já falharam (failed_jobs) com o código antigo não podem ser reexecutados depois do deploy (a classePostPlatformdeixa de existir): reexecute-os antes ou descarte-os. Confirme que não sobrou comando agendado rodando:ps -ef | grep "[a]rtisan" | grep -v horizon. E que nenhum post ficou publicando sem job (deve dar 0; o que sobrar oRecoverStuckPostsfalharia depois de 1 h). As consultas abaixo são de PostgreSQL (Cloud); no MySQL troquenow() - interval '1 hour'pornow() - interval 1 hour:E que nenhum post do Google Business está em revisão (deve dar 0; o JPEG de um post em revisão tem o id do destino antigo no nome e ficaria órfão no bucket):select count(*) from post_platforms where status in ('publishing', 'retrying') and retry_at is null and updated_at > now() - interval '1 hour';
select count(*) from post_platforms where platform = 'google_business' and status = 'pending_review';
- Deploy pelo Forge, com o build antes do
migrate, para um build quebrado falhar antes de qualquer escrita:composer install→artisan optimize→npm ci && npm run build→artisan migrate --force→$ACTIVATE_RELEASE()→$RESTART_QUEUES()→artisan horizon:terminate→artisan reverb:restart. - Teste rápido pelo link secreto, ainda em manutenção: abrir a página de publicar, abrir um post enviado e um agendado, salvar um rascunho.
-
php artisan up
- Horizon sem jobs falhos novos.
- Um post de teste publicado em cada rede conectada no workspace de testes.
- Um webhook de teste (
post.published) e o email de "publicado". - Os posts agendados para os minutos da janela saíram.
post_platformsnão existe mais eanalytics_publications.post_platform_idfoi removida.
- Antes do
up(migration, build ou teste rápido): ou corrija para frente com o app em manutenção, ou apague a linha da migration que falhou emmigrationse volte o release; as migrations podem rodar de novo (a cópia é idempotente e limpa cópias de destinos que sumiram). - Depois do
up: só correção para frente. O código novo grava a publicação só emposts; voltar o código antigo republicaria posts que já saíram.
Muda de uma vez, sem formato de compatibilidade:
- Objeto Post (API
GET/POST/PUT /posts, tools de post do MCP): semplatforms[]. No topo:publish_status,social_account,platform,content_type,meta,platform_url,error_message,display_name,display_username,display_avatar. - Criar post (API
POST /posts,create-post-tool):social_account_id,content_typeemetano topo. Enviarplatformsé erro de validação. - Editar post (API
PUT /posts/{id},update-post-tool):content_typeemetano topo;platformsé recusado. - Erros de validação:
meta.*econtent_type(antesplatforms.0.*); nos lotes,destinations.{i}.meta.*. - Status:
partially_publisheddeixa de existir; o resultado na rede fica empublish_status(pending,publishing,retrying,pending_review,published,failed,rejected). - Preview (API e
preview-post-tool) e métricas (API eget-post-metrics-tool): um objeto só, semplatforms[]nempost_platform_id. - Analytics: publicações e relatório trazem
post_id(sempost_platform_id). - Repurpose: cada post do item traz
platformepublish_status. - Webhooks: o payload
post.*traz os campos do canal no topo (incluindosocial_account_ideplatform_post_id, semerror_context). O eventopost.partially_publishedfoi removido; webhooks que o assinavam passam a assinarpost.publishedepost.failed. Entregas que já estavam na fila e o replay de logs antigos reenviam o payload no formato antigo.
Páginas da docs.trypost.it a atualizar: objeto Post e endpoints (create, update, list, show, preview, attach media), erros de validação, status, Webhooks (payload e lista de eventos), Repurpose items, métricas do post, publicações e relatório de Analytics, e a página do MCP.