36 KiB
Convites Digitais — Manual de Instruções
Este documento reúne tudo o que é preciso para instalar, publicar e manter os Convites Digitais no servidor. É para o administrador do sistema.
- Parte 1 — Deploy: instalação, configuração, operação, backup e solução de problemas.
- Parte 2 — Histórico de alterações: o que mudou em cada versão e como atualizar o servidor.
Para usar o sistema (criar eventos e convites, enviar, acompanhar respostas, PDFs), veja o MANUAL-USUARIO.md. Ele é escrito para o organizador da festa, sem termos técnicos, e pode ser entregue a quem vai gerenciar os eventos.
Para desenvolver ou alterar o código (arquitetura, banco de dados, desenvolvimento local, serviço Python), veja o README.md.
Sumário
Parte 1 — Deploy
- Visão geral da arquitetura
- Pré-requisitos
- Copiar o projeto para o servidor
- Configurar as variáveis de ambiente
- Subir a aplicação
- Verificar se está tudo funcionando
- Publicar via Cloudflare
- Atualizar para uma nova versão
- Backup e restauração
- Operação do dia a dia
- Solução de problemas
Parte 2 — Histórico de alterações
- Versão de 05/10/2026
- Versão de 06/10/2026 — PDFs dos convites
- Versão de 07/10/2026 — Mensagem do WhatsApp personalizada
Parte 1 — Deploy
1. Visão geral da arquitetura
A aplicação roda em três containers gerenciados pelo Docker Compose:
| Serviço | Imagem | Função |
|---|---|---|
app |
construída pelo Dockerfile do projeto |
Servidor Next.js (páginas, área do organizador, RSVP) na porta 3000 |
db |
postgres:17-alpine |
Banco de dados. Os dados ficam no volume pgdata |
pdf-service |
construída por pdf-service/Dockerfile (Python) |
Gera os PDFs personalizados dos convites. Só o app acessa |
Convidado / Organizador
│
▼
Cloudflare (HTTPS) ──► Servidor :3000 ──► container app ──► container db (Postgres)
│ └─ volume pgdata
│ HTTP interno (http://pdf-service:8000)
▼
container pdf-service
│
volume app-storage ◄──────┴── (app e pdf-service: PDFs modelo e gerados)
Pontos importantes:
- O banco e o
pdf-servicenão ficam expostos para fora do servidor; só o containerappos acessa. - Os PDFs (modelos e gerados) ficam no volume Docker
app-storage, fora do banco. O banco guarda só as informações sobre eles. - As migrations (criação/atualização das tabelas) são aplicadas automaticamente toda vez que o container
appsobe. - Os dados ficam no volume Docker
pgdatae sobrevivem a reinícios e rebuilds dos containers.
2. Pré-requisitos
No servidor (ex.: 192.168.18.97):
-
Docker com o plugin Compose. Para conferir:
docker --version docker compose version -
Acesso à internet durante o build — o build baixa as dependências npm e as fontes do Google.
-
Porta livre para a aplicação (padrão
3000; pode ser alterada emAPP_PORT).
3. Copiar o projeto para o servidor
Copie a pasta do projeto para o servidor sem as pastas node_modules, .next e dist (elas são recriadas no build).
Exemplo com rsync, a partir da máquina onde está o código:
rsync -av --exclude node_modules --exclude .next --exclude dist ./ usuario@192.168.18.97:/opt/convite/
Troque
usuarioe/opt/convite/pelo usuário e pela pasta usados nas outras aplicações do servidor.
Arquivos essenciais que precisam estar no servidor:
Dockerfile
compose.yml
.dockerignore
.env.example
package.json
package-lock.json
next.config.ts
tsconfig.json
postcss.config.mjs
drizzle.config.ts
drizzle/ (migrations SQL)
scripts/ (script de migration)
pdf-service/ (serviço Python de PDFs)
src/
public/
4. Configurar as variáveis de ambiente
No servidor, dentro da pasta do projeto:
cd /opt/convite
cp .env.example .env
nano .env
Variáveis
| Variável | Obrigatória | Exemplo | Descrição |
|---|---|---|---|
POSTGRES_DB |
não | convite |
Nome do banco |
POSTGRES_USER |
não | convite |
Usuário do banco |
POSTGRES_PASSWORD |
sim | Xk29fLq81 |
Senha do banco. Só letras e números |
DATABASE_URL |
não* | — | Usada apenas fora do Docker. No compose é montada automaticamente |
APP_PORT |
não | 3000 |
Porta publicada no servidor |
APP_URL |
recomendada | https://convite.seudominio.com |
Endereço público do app, sem barra no final. Usado no card de pré-visualização do WhatsApp |
ORGANIZER_PASSWORD |
sim | — | Senha de acesso à área do organizador |
SESSION_SECRET |
sim | — | Chave secreta de 32+ caracteres usada na verificação da senha |
COOKIE_SECURE |
sim | false |
false para acesso por http://IP; true atrás do Cloudflare (HTTPS) |
RUN_MIGRATIONS |
não | true |
false desliga a migration automática na subida |
INVITE_LINK_BASE_URL |
não | https://convite.n1b3.com.br |
Endereço usado no link gravado em cada PDF: <valor>/r/<token>, sem barra no final. Em branco, usa o APP_URL. Só defina se o link do PDF tiver de ser diferente do endereço do app |
INVITE_LINK_PATTERN |
não | com.br/r/ |
Trecho que identifica, no PDF modelo, o link a ser trocado pelo link de cada convite |
PDF_SERVICE_URL |
não* | http://pdf-service:8000 |
Endereço do serviço de PDFs. No compose é definido automaticamente |
STORAGE_DIR |
não* | /storage |
Pasta dos PDFs. No compose é definida automaticamente (volume app-storage) |
* Não precisa alterar para o deploy com Docker. Essas variáveis só importam ao rodar fora do Docker (ver README.md).
Gerar o SESSION_SECRET
Em qualquer máquina com Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
Ou no próprio servidor, sem Node:
openssl rand -base64 32 | tr -d '/+='
Cole o resultado em SESSION_SECRET=.
Exemplo de .env preenchido
POSTGRES_DB=convite
POSTGRES_USER=convite
POSTGRES_PASSWORD=Xk29fLq81mPz
APP_PORT=3000
APP_URL=http://192.168.18.97:3000
ORGANIZER_PASSWORD=uma-senha-forte-para-o-admin
SESSION_SECRET=3qD0x9bN1pY7sKcVb2T8wM4rH6jLz5aEoUfGiQ
COOKIE_SECURE=false
INVITE_LINK_PATTERN=com.br/r/
Os PDFs usam o
APP_URLcomo endereço do link. Para um endereço diferente, acrescenteINVITE_LINK_BASE_URL=https://....
🔒 O
.envcontém senhas. Não envie esse arquivo por e-mail/chat e restrinja o acesso:chmod 600 .env.
5. Subir a aplicação
docker compose up -d --build
O que acontece, em ordem:
- As imagens do
appe dopdf-servicesão construídas (pode levar alguns minutos na primeira vez). - O Postgres e o
pdf-servicesobem; oappaguarda o banco ficar saudável. - O
appaplica as migrations pendentes. - O servidor Next.js inicia na porta
APP_PORT.
6. Verificar se está tudo funcionando
Status dos containers — os três devem estar running (e db e pdf-service como healthy):
docker compose ps
Logs do app:
docker compose logs -f app
Saída esperada (resumida):
Migrations aplicadas.
▲ Next.js 16.x
- Local: http://0.0.0.0:3000
✓ Ready
No navegador:
| Endereço | Deve mostrar |
|---|---|
http://192.168.18.97:3000/ |
Página inicial "Convites Digitais de Aniversário" |
http://192.168.18.97:3000/admin |
Tela de login do organizador |
Entre com a ORGANIZER_PASSWORD. Se cair na lista de eventos (vazia), o deploy está concluído.
7. Publicar via Cloudflare
Quando o Cloudflare for apontar para a aplicação (mesmo padrão das outras aplicações do servidor):
-
Configure o domínio/túnel no Cloudflare apontando para
http://<servidor>:<APP_PORT>. -
No
.env, altere:COOKIE_SECURE=true APP_URL=https://<seu-domínio> -
Recrie o container do app:
docker compose up -d app -
Acesse
https://<seu-domínio>/admine faça login para confirmar.
⚠️ Depois de
COOKIE_SECURE=true, o login pelo IP em HTTP deixa de funcionar — isso é esperado. Use sempre o domínio HTTPS.
Os links de convite são gerados a partir do endereço em que o organizador está navegando. Por isso, depois da publicação, gere/copie os links acessando o /admin pelo domínio, e não pelo IP.
8. Atualizar para uma nova versão
-
Copie o código novo para o servidor (mesmo comando
rsyncdo passo 3). O.envdo servidor não é sobrescrito, pois não está na pasta de origem — confira antes. -
Faça um backup do banco (ver seção 9) — recomendado sempre que houver mudança no banco.
-
Reconstrua e suba o app:
docker compose up -d --build app -
Verifique os logs (
docker compose logs -f app). Novas migrations são aplicadas automaticamente. -
Consulte o Histórico de alterações para saber se a versão pede algum passo extra (ex.: variável nova no
.env).
Os dados não são perdidos em atualizações.
9. Backup e restauração
Backup
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql
Gera um arquivo como backup-2026-11-10.sql na pasta atual. Guarde uma cópia fora do servidor.
Backup automático diário (opcional) — adicione ao crontab -e do servidor:
0 3 * * * cd /opt/convite && docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > /opt/convite/backups/backup-$(date +\%F).sql
(crie a pasta antes: mkdir -p /opt/convite/backups)
Backup dos PDFs
Os PDFs modelo e gerados ficam no volume app-storage, não entram no pg_dump. Para copiá-los:
docker run --rm -v convite_app-storage:/storage -v "$PWD":/backup alpine tar czf /backup/pdfs-$(date +%F).tar.gz -C /storage .
Para restaurar (com os containers parados):
docker run --rm -v convite_app-storage:/storage -v "$PWD":/backup alpine sh -c 'tar xzf /backup/pdfs-AAAA-MM-DD.tar.gz -C /storage && chown -R 1001:1001 /storage'
Os PDFs gerados podem ser refeitos a qualquer momento (botão Gerar PDFs de todos os convites). O que vale guardar é o PDF modelo de cada evento.
Restauração
A restauração deve ser feita em um banco vazio. Para recriar o banco do zero:
docker compose down
docker volume rm convite_pgdata # ⚠️ APAGA todos os dados atuais
docker compose up -d db
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"' < backup-AAAA-MM-DD.sql
docker compose up -d app
⚠️ Nunca use
docker compose down -vsem querer apagar o banco — o-vremove os volumespgdataeapp-storage(PDFs).
10. Operação do dia a dia
| Tarefa | Comando |
|---|---|
| Ver status | docker compose ps |
| Ver logs do app | docker compose logs -f app |
| Ver logs do banco | docker compose logs -f db |
| Ver logs do serviço de PDFs | docker compose logs -f pdf-service |
| Reiniciar o serviço de PDFs | docker compose restart pdf-service |
| Reiniciar o app | docker compose restart app |
| Parar tudo (mantém dados) | docker compose down |
| Subir novamente | docker compose up -d |
| Console SQL | docker compose exec db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"' |
| Trocar a senha do organizador | Edite ORGANIZER_PASSWORD no .env e rode docker compose up -d app |
| Derrubar todas as sessões abertas | No /admin, clique em Sair de todos (no topo da página) |
Os containers estão com restart: unless-stopped: voltam sozinhos se o servidor reiniciar.
11. Solução de problemas
| Sintoma | Causa provável | Solução |
|---|---|---|
defina POSTGRES_PASSWORD no .env ao subir |
Variável ausente | Preencha POSTGRES_PASSWORD no .env |
App reiniciando em loop; log com Falha ao aplicar migrations |
Banco inacessível ou senha errada | Confira docker compose logs db e as variáveis POSTGRES_* |
Erro de senha no Postgres após trocar POSTGRES_PASSWORD |
A senha só é definida na primeira criação do volume | Volte a senha antiga no .env ou altere no banco: ALTER USER convite PASSWORD 'nova'; |
Login do /admin sempre volta para a tela de login |
COOKIE_SECURE=true com acesso via HTTP |
Use false no acesso por IP, ou acesse pelo domínio HTTPS |
Erro SESSION_SECRET não definida no log |
Variável vazia ou curta | Gere um valor de 32+ caracteres (seção 4) |
| "Senha incorreta." no login | ORGANIZER_PASSWORD diferente da digitada |
Confira o .env e recrie o app após alterar |
| "Muitas tentativas incorretas" no login | 5 senhas erradas em 15 minutos no mesmo IP | Aguarde o tempo indicado, ou reinicie o app (docker compose restart app) para zerar o bloqueio |
| Link colado no WhatsApp aparece sem imagem | APP_URL ausente ou errada, ou o WhatsApp guardou uma versão antiga |
Ajuste APP_URL no .env, rode docker compose up -d app e teste com um convite que ainda não foi compartilhado |
| Build falha baixando fontes/pacotes | Servidor sem internet no build | Libere o acesso à internet durante o docker compose build |
| Porta 3000 em uso | Outra aplicação na mesma porta | Altere APP_PORT no .env |
| Link do convite abre com o IP em vez do domínio | Link copiado acessando o admin pelo IP | Acesse o /admin pelo domínio e copie de novo |
| "Endereço do link não configurado" ao gerar PDF | Nem APP_URL nem INVITE_LINK_BASE_URL no .env |
Defina APP_URL (seção 4) e rode docker compose up -d pdf-service |
| "Serviço de PDF indisponível" ao enviar modelo ou gerar PDF | Container pdf-service parado ou com erro |
docker compose ps e docker compose logs pdf-service; suba com docker compose up -d pdf-service |
| "Nenhum link de convite (…/r/…) foi encontrado neste PDF" | O modelo não tem um link clicável com INVITE_LINK_PATTERN |
Confira o link no PDF (precisa ser um link clicável, não só texto) ou ajuste INVITE_LINK_PATTERN |
| Convite com "Erro ao gerar" | Falha ao gerar aquele PDF | A mensagem aparece no card. Veja docker compose logs app pdf-service e clique em Gerar novamente |
| "Geração interrompida pela reinicialização do servidor" | O app reiniciou durante a geração | Clique em Gerar só pendentes e com erro |
Erro de permissão (Permission denied) em /storage nos logs |
Arquivos do volume com outro dono | docker compose run --rm --user root --entrypoint chown pdf-service -R 1001:1001 /storage |
| PDF baixado com link errado | APP_URL (ou INVITE_LINK_BASE_URL, se definida) errada |
Corrija no .env, rode docker compose up -d pdf-service e gere os PDFs de novo |
11.1 Diagnóstico dos PDFs
| Onde olhar | Comando |
|---|---|
| Saúde do serviço de PDFs | docker compose ps pdf-service (deve estar healthy) |
| Logs da geração (lado Python) | docker compose logs -f pdf-service — linhas PDF gerado convite=... ou o erro completo |
| Logs da geração (lado app) | docker compose logs -f app — linhas Falha ao gerar o PDF do convite ... |
| Arquivos no volume | docker compose exec app ls -R /storage/events |
| Lotes recentes | docker compose exec db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB" -c "select status, processed, failed, total, created_at from pdf_jobs order by created_at desc limit 5"' |
| Testar o serviço por dentro | docker compose exec app wget -qO- http://pdf-service:8000/health |
Para erros no código do serviço Python (traceback, links de um PDF, nível de log), veja "Manutenção do pdf-service" no README.md.
Parte 2 — Histórico de alterações
12. Versão de 05/10/2026
12.1 Resumo
| # | Alteração | Para quem muda algo | Exige ação no deploy? |
|---|---|---|---|
| 1 | Visual neutro (Slate) na área do organizador e na página inicial | Organizador | Não |
| 2 | Limite de tentativas de login | Organizador | Não |
| 3 | Sessões guardadas no banco + botão Sair de todos | Organizador | Sim: migration automática; todos precisam entrar de novo |
| 4 | Nova variável APP_URL |
Quem faz o deploy | Sim: adicionar ao .env |
| 5 | Card de pré-visualização do convite no WhatsApp | Convidado | Depende do item 4 |
| 6 | Exportação da lista de convidados em CSV | Organizador | Não |
| 7 | Projeto renomeado para Convites Digitais e documentação atualizada | Todos | Não |
Os passos para atualizar o servidor estão na seção 12.9.
12.2 Visual neutro na área do organizador
Antes: login, painel e página inicial usavam as cores e o 🦁 do tema Safári, mesmo em eventos com outro tema.
Agora:
- Paleta Slate / Grafite: fundo cinza-claro liso, cards brancos, textos em cinza-azulado e botões principais em índigo.
- O 🦁 foi trocado por um ícone de calendário no login, no cabeçalho do painel e na página inicial.
- A página inicial agora diz "Convites Digitais" em vez de "Convites Safari".
- Contorno de foco, seleção de texto e checkboxes da área do organizador também usam índigo.
- A página "Convite não encontrado" ficou neutra (ícone de lupa, fundo cinza-claro).
- A cor da barra do navegador no celular agora é cinza-claro no app e a cor do tema na página de cada convite.
O que continua igual, de propósito:
- As cores de status: verde = presente, vermelho = ausente, âmbar = pendente.
- A prévia do tema no formulário de evento (mostra as cores do tema escolhido).
- O botão WhatsApp (verde).
- As páginas dos convites, que continuam com o tema escolhido para cada evento.
Arquivos: src/app/admin/**, src/components/admin/**, src/app/page.tsx, src/app/r/[token]/not-found.tsx, src/app/globals.css (classe .admin-ui) e src/components/ui/index.ts. Button e Input ganharam a opção tone="neutral"; o padrão continua sendo as cores do tema, usadas no convite.
12.3 Limite de tentativas de login
Antes: a senha do /admin podia ser testada sem limite.
Agora:
- Depois de 5 senhas erradas em 15 minutos, o login fica bloqueado por 15 minutos para aquele IP.
- Durante o bloqueio a senha nem é conferida, e a tela mostra quantos minutos faltam.
- Um login certo zera o contador.
Detalhes técnicos:
- Os contadores ficam na memória do app. Reiniciar o container (
docker compose restart app) zera todos os bloqueios. - O IP vem do cabeçalho
cf-connecting-ip, enviado pelo Cloudflare. Sem ele, o app usax-forwarded-foroux-real-ip. - ⚠️ Quem acessa o servidor direto pelo IP, sem passar pelo Cloudflare, consegue forjar esses cabeçalhos e escapar do limite. A proteção completa é o servidor aceitar só tráfego vindo do Cloudflare (Cloudflare Tunnel ou regra de firewall).
Arquivos: src/lib/login-rate-limit.ts (novo) e src/app/admin/auth-actions.ts.
12.4 Sessões no banco e "Sair de todos"
Antes: o cookie de sessão se validava sozinho, por uma assinatura. Sair só apagava o cookie do navegador: uma cópia dele continuava valendo por até 7 dias. Para derrubar todas as sessões era preciso trocar o SESSION_SECRET e recriar o app.
Agora:
- Cada login cria uma linha na tabela nova
organizer_sessions. O cookie guarda um token aleatório, e o banco guarda só o hash dele. - Toda página e ação do organizador confere se a sessão ainda existe e não venceu (continua durando 7 dias).
- Sair apaga a sessão no banco, então uma cópia do cookie para de funcionar.
- Sair de todos, um botão novo no topo do painel com confirmação, apaga as sessões de todos os dispositivos.
- Sessões vencidas são apagadas a cada novo login.
- O
SESSION_SECRETcontinua obrigatório: agora ele é usado na verificação da senha.
Impacto no deploy:
- Migration nova
drizzle/0004_organizer_sessions.sql, aplicada automaticamente quando o container sobe. - Os cookies antigos deixam de valer: depois da atualização, todos precisam entrar de novo.
Arquivos: src/db/schema.ts, src/lib/auth.ts, src/app/admin/auth-actions.ts, src/app/admin/(painel)/layout.tsx e drizzle/0004_organizer_sessions.sql.
12.5 Variável APP_URL
Antes: o endereço base usado nos links das pré-visualizações estava fixo no código como http://localhost:3000.
Agora: esse endereço vem da variável APP_URL do .env.
| Situação | Valor de APP_URL |
|---|---|
| Acesso pelo IP (antes do Cloudflare) | http://192.168.18.97:3000 |
| Depois da publicação no Cloudflare | https://<seu-domínio> |
- Escreva sem barra no final.
- O valor é lido com o app rodando: depois de alterar, basta
docker compose up -d app(sem rebuild). - Se a variável faltar ou for inválida, o app usa
http://localhost:3000e registra um aviso no log. Nesse caso o card do WhatsApp (12.6) fica sem imagem.
Arquivos: src/lib/app-url.ts (novo), src/app/layout.tsx e .env.example.
12.6 Card de pré-visualização no WhatsApp
Antes: o link do convite colado no WhatsApp aparecia sem imagem e com um título genérico.
Agora: aparece um card com:
- imagem (1200×630) nas cores do tema do evento, com o selo e o ícone do tema, "Família, você está convidado para", o nome da festa, data · horário · local e "Toque para confirmar sua presença";
- título "Família, você está convidado!";
- descrição com o nome da festa, a data, o horário e o local.
Detalhes:
- A imagem mostra só o que a página do convite já mostra para quem tem o link. A lista de convidados não aparece.
- A imagem é gerada na hora, então edições no evento aparecem nos próximos compartilhamentos.
- O WhatsApp guarda o card de cada link por um tempo: links compartilhados antes da atualização podem continuar sem imagem.
- Os emojis da imagem são baixados da internet ao gerar o card: o servidor precisa de acesso à internet.
- A mensagem pronta do botão WhatsApp agora usa o ícone do tema do evento (antes era sempre 🦁).
Arquivos: src/app/r/[token]/opengraph-image.tsx (novo), src/app/r/[token]/page.tsx e src/components/admin/ShareInvitation.tsx.
12.7 Exportar a lista de convidados (CSV)
Agora: na página do evento, abaixo do Resumo de presença, em Exportar lista (CSV), há os botões:
| Botão | Conteúdo do arquivo |
|---|---|
| Todos | Todos os convidados do evento |
| Presentes | Só quem confirmou |
| Ausentes | Só quem recusou (e, depois do prazo, quem não respondeu) |
| Pendentes | Só quem ainda não respondeu |
- Colunas: Convite, Convidado, Tipo (Adulto/Adolescente/Criança), Status e Respondido em (horário de Brasília).
- Abre direto no Excel em português e no Google Planilhas, com colunas e acentos corretos (separador
;, UTF-8). - Nome do arquivo: parte do nome da festa e do filtro, ex.:
1-aniversario-do-arthur-presentes.csv. - Só funciona com o organizador logado. Sem login, o endereço redireciona para a tela de login.
- Nomes que começam com
=,+,-ou@recebem um apóstrofo na frente, para o Excel não tratá-los como fórmula.
Arquivos: src/app/admin/(painel)/eventos/[id]/exportar/route.ts (novo) e src/app/admin/(painel)/eventos/[id]/page.tsx.
12.8 Projeto renomeado e documentação
- Novo nome: "Convite Safari" passou a se chamar Convites Digitais no
README.md, noinstrucoes.mde na página inicial. - Documentação atualizada: o README e o
instrucoes.md, que na época também trazia o manual de uso (hoje noMANUAL-USUARIO.md, ver 14.2), passaram a descrever o que já existia e não estava documentado (temas, prazo de confirmação, convidados adolescentes, textos de selo e rodapé) e as novidades desta versão. - Mantido: o tema padrão no banco continua
safari, só para eventos antigos criados antes da escolha de tema. O tema Safári continua disponível normalmente.
12.9 Roteiro de atualização desta versão
-
Backup do banco (esta versão cria uma tabela nova):
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql -
Copie o código novo para o servidor (mesmo
rsyncda seção 3). -
Adicione
APP_URLao.envdo servidor (ver 12.5):APP_URL=http://192.168.18.97:3000 -
Reconstrua e suba o app:
docker compose up -d --build app -
Confira os logs (
docker compose logs -f app): deve aparecerMigrations aplicadas.e nenhum aviso deAPP_URL inválida. -
Valide:
- abra
/admin: a tela de login deve estar no visual neutro; entre com a senha (as sessões antigas foram encerradas); - abra um evento e baixe um CSV em Exportar lista (CSV);
- cole o link de um convite ainda não compartilhado no WhatsApp e confira o card com imagem.
- abra
-
Quando publicar no Cloudflare, além de
COOKIE_SECURE=true, troqueAPP_URLparahttps://<seu-domínio>e rodedocker compose up -d app.
13. Versão de 06/10/2026 — PDFs dos convites
13.1 Resumo
| # | Alteração | Para quem muda algo | Exige ação no deploy? |
|---|---|---|---|
| 1 | PDF modelo por evento (upload, substituição, download) | Organizador | Não |
| 2 | Geração do PDF personalizado por convite e em lote, com progresso | Organizador | Não |
| 3 | Novo container pdf-service (Python) e volume app-storage |
Quem faz o deploy | Sim: build do novo serviço |
| 4 | Novas variáveis opcionais INVITE_LINK_BASE_URL (padrão: APP_URL) e INVITE_LINK_PATTERN |
Quem faz o deploy | Só conferir se APP_URL está correto |
| 5 | Migration 0005_pdf_generation (colunas de PDF e tabela pdf_jobs) |
— | Automática na subida |
Os passos para atualizar o servidor estão na seção 13.3.
13.2 O que mudou
-
Para o organizador:
- seção PDF do convite na página do evento;
- status e botões de PDF em cada convite;
- geração em lote com progresso.
Uso detalhado na seção 2.11 do MANUAL-USUARIO.md.
-
No servidor:
- container novo
pdf-service(Python), sem porta publicada; - volume novo
app-storagecom os PDFs, montado em/storagenoappe nopdf-service; - migration
0005_pdf_generation, aplicada na subida.
- container novo
-
Variáveis:
INVITE_LINK_BASE_URL(opcional; em branco usa oAPP_URL) eINVITE_LINK_PATTERN(padrãocom.br/r/). Ver seção 4. -
Segurança:
- downloads e ações exigem o organizador logado;
- o convite precisa pertencer ao evento da URL;
- o navegador nunca envia caminhos de arquivo;
- o upload aceita só PDF de até 10 MB.
Arquitetura, banco, endpoints e manutenção do código estão no README.md.
Arquivos:
- Novos:
pdf-service/(Dockerfile,requirements.txt,app/main.py);src/lib/pdf/(storage.ts,service.ts,jobs.ts,state.ts,types.ts);src/app/admin/pdf-actions.ts;src/components/admin/EventPdf.tsx;src/instrumentation.ts;src/lib/slugify.ts;- rotas em
src/app/admin/(painel)/eventos/[id]/pdf/e.../convites/[conviteId]/pdf/; drizzle/0005_pdf_generation.sql.
- Alterados:
src/db/schema.ts;src/app/admin/actions.ts(apaga os PDFs ao excluir evento ou convite);- página do evento;
next.config.ts(limite de upload de 11 MB nas Server Actions);Dockerfile,compose.yml,.env.example,.dockerignoree.gitignore.
13.3 Roteiro de atualização desta versão
-
Backup do banco (esta versão altera tabelas):
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql -
Copie o código novo para o servidor (mesmo
rsyncda seção 3). Confira que a pastapdf-service/foi copiada. -
Confira o
.envdo servidor: oAPP_URLdeve ser o endereço público (https://convite.n1b3.com.br), porque os PDFs usam esse endereço no link. As variáveis novas são opcionais:# INVITE_LINK_BASE_URL=https://convite.n1b3.com.br # só se o link do PDF tiver de ser diferente do APP_URL INVITE_LINK_PATTERN=com.br/r/ # padrão; pode omitir -
Construa e suba tudo (o
pdf-serviceé novo):docker compose up -d --build -
Confira:
docker compose psdeve mostrar opdf-servicecomohealthy, e os logs do app devem mostrarMigrations aplicadas.. -
Valide:
- abra um evento e envie o PDF modelo em PDF do convite;
- gere o PDF de um convite e baixe o arquivo;
- abra o PDF e clique no link: deve abrir
<APP_URL>/r/<token do convite>; - clique em Gerar PDFs de todos os convites e acompanhe o progresso;
- rode
docker compose restarte confira que os PDFs continuam disponíveis para download.
14. Versão de 07/10/2026 — Mensagem do WhatsApp personalizada
14.1 Resumo
| # | Alteração | Para quem muda algo | Exige ação no deploy? |
|---|---|---|---|
| 1 | Campo Mensagem do WhatsApp no evento, com variáveis e formatação do WhatsApp | Organizador | Não |
| 2 | Pré-visualização da mensagem no formulário do evento | Organizador | Não |
| 3 | Migration 0006_event_whatsapp_message (coluna whatsapp_message em events) |
— | Automática na subida |
| 4 | Manual de uso separado em MANUAL-USUARIO.md |
Organizador e administrador | Não |
Eventos já existentes ficam com o campo vazio e passam a usar a nova mensagem padrão (ver 14.2) a partir dos próximos cliques no botão. Mensagens já enviadas não mudam.
14.2 O que mudou
Antes: a mensagem do botão WhatsApp era fixa no código: "Olá, Família! Vocês estão convidados para o festa ícone", seguida do link.
Agora:
-
O formulário do evento tem o campo Mensagem do WhatsApp, com:
- botões para inserir as variáveis
{nome},{evento},{data},{hora},{local},{endereco},{icone}e{link}; - uma linha de ajuda com a formatação do WhatsApp;
- três contadores: Modelo, Texto final (exemplo) e Link do WhatsApp (exemplo);
- uma pré-visualização no estilo do WhatsApp, que aplica negrito, itálico, riscado, monoespaçado e código. Em navegadores sem suporte (Safari < 16.4), a prévia aparece sem formatação, sem quebrar o formulário.
- botões para inserir as variáveis
-
O botão WhatsApp de cada convite monta o texto a partir do modelo do evento.
-
Botão novo Copiar mensagem em cada convite: copia o mesmo texto do botão WhatsApp, para enviar por outro aplicativo.
-
Nova mensagem padrão, usada com o campo vazio:
Olá, {nome}! Chegou o seu convite para: *{evento}* {icone} Confirme a presença: {link}A anterior ("…convidados para o festa") só combinava com nomes de festa masculinos no singular e com convites de família: "para o Festa da Maria" e "Vocês" num convite de uma pessoa só. A nova não depende do nome da festa nem de quantas pessoas há no convite.
-
Outros textos com a mesma correção:
Onde Antes Agora Título do card do link no WhatsApp "Família, você está convidado!" "Família, chegou o seu convite!" Imagem do card "Família, você está convidado para" "Família, chegou o seu convite para" Topo da página do convite (e prévia do tema no formulário do evento) "esperamos vocês para celebrar!" "estamos esperando por você(s)!" Links já compartilhados podem continuar mostrando o card antigo por um tempo, porque o WhatsApp guarda o card (ver 12.6).
-
Sem
{link}na mensagem, o link é adicionado no final. -
Limite de 950 caracteres, com margem abaixo de 1000 para o link do
wa.me. Ele vale para o modelo e também para o texto final, já com as variáveis trocadas.- O texto final é conferido no servidor ao criar ou editar o evento, com os nomes de todos os convites, e ao criar ou editar um convite.
- Nessa conferência, o link é estimado com o
APP_URLe um código de 8 caracteres. - A mensagem padrão (campo vazio) não é limitada.
-
Limite de 2300 caracteres no link codificado (
https://wa.me/?text=...), conferido nos mesmos momentos. Ele existe porque, no link, acentos ocupam 6 caracteres e emojis até 12. O WhatsApp não publica um limite oficial; os dois valores são margens de segurança. -
Mensagem por evento: não há mensagem diferente por convite. Para personalizar o texto de uma família, o organizador edita a mensagem no próprio WhatsApp antes de enviar.
Uso detalhado na seção 2.5.1 do MANUAL-USUARIO.md.
Arquivos:
- Novos:
src/lib/whatsapp-message.ts(mensagem padrão, variáveis, limite e montagem do texto);src/components/admin/WhatsAppMessageField.tsx(campo, botões de variáveis e pré-visualização);drizzle/0006_event_whatsapp_message.sql.
- Alterados:
src/db/schema.ts(colunawhatsappMessage);src/app/admin/actions.ts(leitura e validação do campo; conferência do texto final ao salvar evento e convite);src/components/admin/EventForm.tsx(campo novo; data, horário, local e endereço alimentam a pré-visualização);src/components/admin/ShareInvitation.tsx(usa o modelo do evento; botão Copiar mensagem);src/app/r/[token]/page.tsx,src/app/r/[token]/opengraph-image.tsxesrc/components/invitation/InvitationHero.tsx(frases neutras);- página do evento e página de edição do evento.
Documentação:
- O manual de uso, que era a antiga Parte 2 deste arquivo, foi movido para o MANUAL-USUARIO.md. Ele foi reescrito sem termos técnicos e ganhou a seção "Quando falar com o administrador".
- Este arquivo ficou só com o deploy (Parte 1) e o histórico, que passou a ser a Parte 2. As seções do histórico foram renumeradas de 16–18 para 12–14.
14.3 Roteiro de atualização desta versão
-
Backup do banco (esta versão adiciona uma coluna):
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql -
Copie o código novo para o servidor (mesmo
rsyncda seção 3). -
Reconstrua e suba o app:
docker compose up -d --build app -
Confira os logs (
docker compose logs -f app): deve aparecerMigrations aplicadas.. -
Valide:
- em um evento existente com o campo vazio, clique em WhatsApp num convite: deve aparecer a nova mensagem padrão ("Chegou o seu convite para: …");
- em Editar evento, escreva uma mensagem com
*negrito*e{nome}e confira a pré-visualização; - salve, clique em WhatsApp e confira no WhatsApp o nome da família, a formatação e o link.