Compare commits
8 Commits
refactor/m
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bff12b122e | |||
| 7e371a3c7e | |||
| dccbf2a1a2 | |||
| ba5d0c03a4 | |||
| 1b865fbb09 | |||
| 309c51b620 | |||
| f4e6dc9fb0 | |||
| 1a294d6f1c |
@ -4,98 +4,33 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy para VM Dev
|
|
||||||
runs-on: omnichannel-uat
|
runs-on: omnichannel-uat
|
||||||
env:
|
|
||||||
DEPLOY_PATH: /opt/omnichannel
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Atualizar deploy
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Preflight
|
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
cd /opt/omnichannel
|
||||||
echo "Runner: $(hostname)"
|
git pull origin dev
|
||||||
docker --version
|
|
||||||
docker compose version
|
|
||||||
psql --version
|
|
||||||
|
|
||||||
- name: Verificar secrets obrigatórios
|
- name: Atualizar backend
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
cd /opt/omnichannel/backend
|
||||||
if [ -z "${DB_PASSWORD:-}" ]; then
|
git pull origin dev
|
||||||
echo "Secret DB_PASSWORD não configurado."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Sync arquivos para o servidor
|
- name: Atualizar frontend
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
cd /opt/omnichannel/frontend
|
||||||
rsync -az --delete \
|
git pull origin dev
|
||||||
--exclude='.git/' \
|
|
||||||
--exclude='.gitea/' \
|
|
||||||
--exclude='frontend/' \
|
|
||||||
--exclude='backend/' \
|
|
||||||
--exclude='uploads/' \
|
|
||||||
./ "$DEPLOY_PATH"/
|
|
||||||
|
|
||||||
- name: Executar migrations
|
- name: Copiar envs
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
PGHOST: ${{ secrets.DB_HOST }}
|
|
||||||
PGPORT: ${{ secrets.DB_PORT }}
|
|
||||||
PGUSER: ${{ secrets.DB_USER }}
|
|
||||||
PGDATABASE: ${{ secrets.DB_NAME }}
|
|
||||||
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
cp /home/desenvolvimento/.envs/omnichannel/deploy.env.development /opt/omnichannel/.env.development
|
||||||
PGHOST="${PGHOST//[$'\t\r\n ']}"
|
cp /home/desenvolvimento/.envs/omnichannel/backend.env.development /opt/omnichannel/backend/.env.development
|
||||||
PGPORT="${PGPORT//[$'\t\r\n ']}"
|
cp /home/desenvolvimento/.envs/omnichannel/frontend.env.development /opt/omnichannel/frontend/.env.development
|
||||||
export PGHOST PGPORT
|
|
||||||
|
|
||||||
echo "Inicializando controle de migrations..."
|
- name: Rebuild todos containers
|
||||||
psql -v ON_ERROR_STOP=1 -f "$DEPLOY_PATH/database/migrations/000_schema_migrations.sql"
|
|
||||||
|
|
||||||
echo "Aplicando migrations pendentes..."
|
|
||||||
for f in $(ls "$DEPLOY_PATH/database/migrations/"[0-9][0-9][0-9]_*.sql | sort); do
|
|
||||||
filename=$(basename "$f")
|
|
||||||
already_run=$(psql -tAc "SELECT COUNT(*) FROM schema_migrations WHERE filename = '$filename'")
|
|
||||||
if [ "$already_run" = "0" ]; then
|
|
||||||
echo "Aplicando: $filename"
|
|
||||||
psql -v ON_ERROR_STOP=1 -f "$f"
|
|
||||||
psql -c "INSERT INTO schema_migrations (filename) VALUES ('$filename') ON CONFLICT DO NOTHING"
|
|
||||||
echo "OK: $filename"
|
|
||||||
else
|
|
||||||
echo "Ignorando (ja aplicada): $filename"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Executar seeds (dev)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
PGHOST: ${{ secrets.DB_HOST }}
|
|
||||||
PGPORT: ${{ secrets.DB_PORT }}
|
|
||||||
PGUSER: ${{ secrets.DB_USER }}
|
|
||||||
PGDATABASE: ${{ secrets.DB_NAME }}
|
|
||||||
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
cd /opt/omnichannel
|
||||||
PGHOST="${PGHOST//[$'\t\r\n ']}"
|
docker compose up -d --build
|
||||||
PGPORT="${PGPORT//[$'\t\r\n ']}"
|
|
||||||
export PGHOST PGPORT
|
|
||||||
|
|
||||||
echo "Aplicando seeds de desenvolvimento..."
|
|
||||||
for f in $(ls "$DEPLOY_PATH/database/seeds/"*.sql | sort); do
|
|
||||||
echo "Seed: $(basename "$f")"
|
|
||||||
psql -v ON_ERROR_STOP=1 -f "$f"
|
|
||||||
done
|
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
name: Deploy Prod
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy para VM Prod
|
|
||||||
runs-on: self-hosted-prod
|
|
||||||
env:
|
|
||||||
DEPLOY_PATH: /opt/omnichannel
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Preflight
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "Runner: $(hostname)"
|
|
||||||
docker --version
|
|
||||||
docker compose version
|
|
||||||
psql --version
|
|
||||||
|
|
||||||
- name: Verificar secrets obrigatórios
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -z "${DB_PASSWORD:-}" ]; then
|
|
||||||
echo "Secret PROD_DB_PASSWORD não configurado."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Sync arquivos para o servidor
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
rsync -az --delete \
|
|
||||||
--exclude='.git/' \
|
|
||||||
--exclude='.gitea/' \
|
|
||||||
--exclude='frontend/' \
|
|
||||||
--exclude='backend/' \
|
|
||||||
--exclude='uploads/' \
|
|
||||||
./ "$DEPLOY_PATH"/
|
|
||||||
|
|
||||||
- name: Executar migrations
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
PGPASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
for f in $(ls "$DEPLOY_PATH/database/migrations/"*.sql | sort); do
|
|
||||||
echo "Aplicando: $(basename "$f")"
|
|
||||||
psql \
|
|
||||||
-h ${{ secrets.PROD_DB_HOST }} \
|
|
||||||
-p ${{ secrets.PROD_DB_PORT }} \
|
|
||||||
-U ${{ secrets.PROD_DB_USER }} \
|
|
||||||
-d ${{ secrets.PROD_DB_NAME }} \
|
|
||||||
-f "$f"
|
|
||||||
done
|
|
||||||
57
.gitea/workflows/diagnose-ssh.yml
Normal file
57
.gitea/workflows/diagnose-ssh.yml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
name: Diagnose SSH
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
diagnose:
|
||||||
|
name: Diagnostico SSH no servidor
|
||||||
|
runs-on: omnichannel-uat
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Status do sshd
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== systemctl status sshd ==="
|
||||||
|
sudo systemctl status sshd --no-pager || true
|
||||||
|
|
||||||
|
- name: Logs recentes do sshd
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== journalctl sshd (ultimas 50 linhas) ==="
|
||||||
|
sudo journalctl -u sshd -n 50 --no-pager || true
|
||||||
|
|
||||||
|
- name: Fail2ban status
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== fail2ban-client status ==="
|
||||||
|
sudo fail2ban-client status 2>/dev/null || echo "fail2ban nao instalado ou sem permissao"
|
||||||
|
echo ""
|
||||||
|
echo "=== fail2ban jail sshd ==="
|
||||||
|
sudo fail2ban-client status sshd 2>/dev/null || echo "jail sshd nao encontrado"
|
||||||
|
|
||||||
|
- name: IPs banidos no fail2ban
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== IPs banidos (iptables) ==="
|
||||||
|
sudo iptables -L f2b-sshd -n 2>/dev/null || sudo iptables -L -n 2>/dev/null | grep -A5 "f2b" || echo "Sem regras f2b encontradas"
|
||||||
|
|
||||||
|
- name: Conexoes SSH ativas e MaxStartups
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== Conexoes na porta 60000 ==="
|
||||||
|
ss -tnp | grep ':60000' || echo "Nenhuma conexao ativa"
|
||||||
|
echo ""
|
||||||
|
echo "=== MaxStartups configurado ==="
|
||||||
|
sudo sshd -T 2>/dev/null | grep -i maxstartups || grep -i maxstartups /etc/ssh/sshd_config || echo "Configuracao padrao (10:30:100)"
|
||||||
|
|
||||||
|
- name: Quem esta logado
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== Sessoes ativas ==="
|
||||||
|
who || true
|
||||||
|
echo ""
|
||||||
|
echo "=== Ultimos logins ==="
|
||||||
|
last -n 10 || true
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@ -4,6 +4,10 @@ dist
|
|||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Repos clonados separadamente dentro do deploy
|
||||||
|
/backend/
|
||||||
|
/frontend/
|
||||||
|
|
||||||
# Backend Specific Ignore
|
# Backend Specific Ignore
|
||||||
backend/node_modules
|
backend/node_modules
|
||||||
backend/dist
|
backend/dist
|
||||||
@ -15,10 +19,3 @@ backend/test-api-out.json
|
|||||||
# Frontend Specific Ignore
|
# Frontend Specific Ignore
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
frontend/dist
|
frontend/dist
|
||||||
|
|
||||||
/backend/
|
|
||||||
/frontend/
|
|
||||||
frontend/
|
|
||||||
backend/
|
|
||||||
|
|
||||||
docs/
|
|
||||||
48
README.md
48
README.md
@ -4,6 +4,29 @@ Plataforma omnichannel para atendimento com foco inicial em WhatsApp. O sistema
|
|||||||
|
|
||||||
O projeto foi construído para validar e evoluir um MVP de atendimento corporativo, com perfis de agente, supervisor e administrador.
|
O projeto foi construído para validar e evoluir um MVP de atendimento corporativo, com perfis de agente, supervisor e administrador.
|
||||||
|
|
||||||
|
## Clonando o Projeto
|
||||||
|
|
||||||
|
Este repositório funciona como o repositório de deploy/orquestração. O backend e o frontend ficam em repositórios separados e devem ser clonados dentro das pastas esperadas pelo `docker-compose.yml`.
|
||||||
|
|
||||||
|
Em uma pasta vazia:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-deploy.git .
|
||||||
|
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-backend.git backend
|
||||||
|
git clone https://chaleiradev.sothistelecom.com/Sothis/omnichannel-frontend.git frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Ao final, a estrutura deve ficar assim:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
omnichannel/
|
||||||
|
├── backend/
|
||||||
|
├── frontend/
|
||||||
|
├── database/
|
||||||
|
├── docker-compose.yml
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
## Principais Recursos
|
## Principais Recursos
|
||||||
|
|
||||||
- Login corporativo via LDAP/Active Directory.
|
- Login corporativo via LDAP/Active Directory.
|
||||||
@ -11,7 +34,7 @@ O projeto foi construído para validar e evoluir um MVP de atendimento corporati
|
|||||||
- JWT próprio da aplicação com perfis e especialidades.
|
- JWT próprio da aplicação com perfis e especialidades.
|
||||||
- Atendimento WhatsApp em tempo real via `whatsapp-web.js`.
|
- Atendimento WhatsApp em tempo real via `whatsapp-web.js`.
|
||||||
- Socket.IO para atualização de chats/mensagens.
|
- Socket.IO para atualização de chats/mensagens.
|
||||||
- Agente Virtual Sothis para triagem e roteamento.
|
- Agente Virtual para triagem e roteamento.
|
||||||
- Fila por especialidade.
|
- Fila por especialidade.
|
||||||
- Assumir, liberar, transferir e fechar atendimento.
|
- Assumir, liberar, transferir e fechar atendimento.
|
||||||
- Abertura ativa com templates aprovados.
|
- Abertura ativa com templates aprovados.
|
||||||
@ -31,10 +54,10 @@ O projeto foi construído para validar e evoluir um MVP de atendimento corporati
|
|||||||
- TypeScript `^6.0`.
|
- TypeScript `^6.0`.
|
||||||
- PostgreSQL via `pg`.
|
- PostgreSQL via `pg`.
|
||||||
- Socket.IO `^4.8`.
|
- Socket.IO `^4.8`.
|
||||||
- `whatsapp-web.js` `^1.34`.
|
- `whatsapp-web.js` `^1.34`, usando Puppeteer/Chrome para controlar o WhatsApp Web.
|
||||||
- LDAP via `ldapts`.
|
- LDAP via `ldapts`.
|
||||||
- JWT via `jsonwebtoken`.
|
- JWT via `jsonwebtoken`.
|
||||||
- Logs com `winston`.
|
- Logs com `NestJS`.
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
@ -74,7 +97,8 @@ omnichannel/
|
|||||||
|
|
||||||
1. Configure `.env.development` na raiz com os dados do banco externo.
|
1. Configure `.env.development` na raiz com os dados do banco externo.
|
||||||
2. Garanta que o PostgreSQL externo esteja acessível a partir do container backend.
|
2. Garanta que o PostgreSQL externo esteja acessível a partir do container backend.
|
||||||
3. Suba backend e frontend:
|
3. Garanta que o backend consiga iniciar Chrome/Chromium para o Puppeteer usado pelo `whatsapp-web.js`.
|
||||||
|
4. Suba backend e frontend:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
@ -91,6 +115,19 @@ Health:
|
|||||||
curl http://localhost:4001/health
|
curl http://localhost:4001/health
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deploy Automatizado
|
||||||
|
|
||||||
|
O deploy deve seguir pelo Gitea Actions. O antigo fluxo manual via arquivo `.bat` não faz mais parte do processo recomendado.
|
||||||
|
|
||||||
|
Responsabilidades esperadas da pipeline:
|
||||||
|
|
||||||
|
- baixar/clonar os repositórios necessários;
|
||||||
|
- configurar variáveis de ambiente do ambiente alvo;
|
||||||
|
- buildar backend e frontend;
|
||||||
|
- subir os serviços com Docker Compose;
|
||||||
|
- executar validações pós-deploy;
|
||||||
|
- não gerenciar o banco dentro do compose.
|
||||||
|
|
||||||
## Como Rodar em Desenvolvimento
|
## Como Rodar em Desenvolvimento
|
||||||
|
|
||||||
Backend:
|
Backend:
|
||||||
@ -130,7 +167,8 @@ Para ambiente novo, antes de subir backend para uso real:
|
|||||||
2. aplicar as migrations em ordem;
|
2. aplicar as migrations em ordem;
|
||||||
3. validar tabelas principais;
|
3. validar tabelas principais;
|
||||||
4. criar/atribuir usuário admin;
|
4. criar/atribuir usuário admin;
|
||||||
5. subir backend e frontend.
|
5. validar Chrome/Chromium e volume de sessao WhatsApp para Puppeteer;
|
||||||
|
6. subir backend e frontend.
|
||||||
|
|
||||||
Detalhes em:
|
Detalhes em:
|
||||||
|
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
-- Migration 000: Controle de migrations aplicadas
|
|
||||||
-- Este arquivo roda sempre (antes do loop principal) e e idempotente.
|
|
||||||
-- Cria a tabela de rastreamento e marca todas as migrations existentes
|
|
||||||
-- como ja aplicadas para que nao re-executem em ambientes ja provisionados.
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
||||||
filename VARCHAR(255) PRIMARY KEY,
|
|
||||||
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO schema_migrations (filename) VALUES
|
|
||||||
('001_auth.sql'),
|
|
||||||
('002_area.sql'),
|
|
||||||
('003_whatsapp.sql'),
|
|
||||||
('004_whatsapp_templates.sql'),
|
|
||||||
('005_whatsapp_assignment_queue.sql'),
|
|
||||||
('006_whatsapp_triage_state.sql'),
|
|
||||||
('007_agent_notes.sql'),
|
|
||||||
('008_mensagens.sql'),
|
|
||||||
('009_customer_contacts.sql'),
|
|
||||||
('010_agenda_contatos.sql'),
|
|
||||||
('011_whatsapp_awaiting_customer_reply.sql'),
|
|
||||||
('012_whatsapp_template_workflow.sql'),
|
|
||||||
('013_agent_presence_pause.sql'),
|
|
||||||
('014_hr_decision_tree_keywords.sql'),
|
|
||||||
('015_configurable_triage_flow.sql'),
|
|
||||||
('016_triage_resolution_step.sql'),
|
|
||||||
('017_bot_flow_builder.sql'),
|
|
||||||
('018_bot_flow_close_node.sql'),
|
|
||||||
('019_admin_audit_ai_contents.sql'),
|
|
||||||
('020_whatsapp_template_category.sql'),
|
|
||||||
('021_agenda_contact_channels.sql'),
|
|
||||||
('022_whatsapp_config.sql'),
|
|
||||||
('023_whatsapp_templates_hsm.sql'),
|
|
||||||
('024_mensagens_media_columns.sql'),
|
|
||||||
('025_mensagens_media_filename.sql'),
|
|
||||||
('026_remove_fake_templates.sql')
|
|
||||||
ON CONFLICT DO NOTHING;
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS whatsapp_templates (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL UNIQUE,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
14
database/migrations/005_templates.sql
Normal file
14
database/migrations/005_templates.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS whatsapp_templates (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO whatsapp_templates (name, content) VALUES
|
||||||
|
('aviso_fatura', 'Olá, {nome}. Estamos entrando em contato para lembrá-lo que a sua fatura está programada para {data}.'),
|
||||||
|
('boas_vindas', 'Olá, {nome}! Obrigado por entrar em contato conosco. Como podemos te ajudar hoje?'),
|
||||||
|
('lembrete_consulta', 'Olá, {nome}. Gostaríamos de confirmar o seu agendamento para {data}. Está confirmado?'),
|
||||||
|
('suporte_tecnico', 'Olá, {nome}. Sou o atendente e irei te auxiliar no seu suporte sob protocolo {protocolo}.')
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
@ -1,17 +0,0 @@
|
|||||||
-- Migration 008: Tabela de mensagens do WhatsApp
|
|
||||||
CREATE TABLE IF NOT EXISTS mensagens (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
wamid VARCHAR(255) NOT NULL,
|
|
||||||
chat_id VARCHAR(255) NOT NULL,
|
|
||||||
from_me BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
type VARCHAR(50) NOT NULL,
|
|
||||||
body TEXT,
|
|
||||||
sender_name VARCHAR(255),
|
|
||||||
status VARCHAR(50),
|
|
||||||
timestamp BIGINT NOT NULL,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE (wamid)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mensagens_chat_id ON mensagens (chat_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mensagens_timestamp ON mensagens (timestamp DESC);
|
|
||||||
19
database/migrations/011_whatsapp_opening_templates.sql
Normal file
19
database/migrations/011_whatsapp_opening_templates.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Migration 011: Templates de abertura ativa do WhatsApp
|
||||||
|
-- Tabela: whatsapp_templates
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
INSERT INTO whatsapp_templates (name, content) VALUES
|
||||||
|
('abertura_atendimento_padrao', 'Ola, {nome}. Tudo bem? Estamos entrando em contato pelo atendimento. Podemos seguir por aqui?'),
|
||||||
|
('abertura_retorno_contato', 'Ola, {nome}. Estamos retornando seu contato para dar continuidade ao seu atendimento.'),
|
||||||
|
('abertura_suporte', 'Ola, {nome}. Aqui e do suporte. Estamos entrando em contato para te ajudar com sua solicitacao.'),
|
||||||
|
('abertura_financeiro', 'Ola, {nome}. Aqui e do financeiro. Estamos entrando em contato para tratar de uma informacao importante sobre seu atendimento.'),
|
||||||
|
('abertura_comercial', 'Ola, {nome}. Aqui e do comercial. Estamos entrando em contato para conversar sobre sua solicitacao.'),
|
||||||
|
('abertura_confirmacao_dados', 'Ola, {nome}. Precisamos confirmar alguns dados para seguir com seu atendimento.'),
|
||||||
|
('abertura_contato_agendado', 'Ola, {nome}. Este contato foi combinado anteriormente e estamos disponiveis para seguir.'),
|
||||||
|
('abertura_pos_atendimento', 'Ola, {nome}. Estamos fazendo um acompanhamento sobre seu atendimento recente.'),
|
||||||
|
('abertura_aviso_importante', 'Ola, {nome}. Temos uma informacao importante para compartilhar com voce.'),
|
||||||
|
('abertura_contato_inicial', 'Ola, {nome}. Vamos iniciar seu atendimento por este canal.')
|
||||||
|
ON CONFLICT (name) DO UPDATE SET
|
||||||
|
content = EXCLUDED.content,
|
||||||
|
updated_at = CURRENT_TIMESTAMP;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Migration 015: Fluxo configuravel de triagem do Agente Virtual Sothis
|
-- Migration 017: Fluxo configuravel de triagem do Agente Virtual Sothis
|
||||||
-- Tabelas:
|
-- Tabelas:
|
||||||
-- bot_triage_flows
|
-- bot_triage_flows
|
||||||
-- bot_triage_audiences
|
-- bot_triage_audiences
|
||||||
@ -29,8 +29,7 @@ CREATE TABLE IF NOT EXISTS bot_triage_audiences (
|
|||||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
UNIQUE (flow_id, label)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
||||||
@ -42,8 +41,7 @@ CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
|||||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
UNIQUE (audience_id, label)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ALTER TABLE whatsapp_chat_atribuicoes
|
ALTER TABLE whatsapp_chat_atribuicoes
|
||||||
@ -116,7 +114,7 @@ JOIN (
|
|||||||
('Sou ex-colaborador', 'ex colaborador, ex-colaborador, rescisao, fgts, informe de rendimentos, desligamento', 2),
|
('Sou ex-colaborador', 'ex colaborador, ex-colaborador, rescisao, fgts, informe de rendimentos, desligamento', 2),
|
||||||
('Sou candidato a uma vaga', 'candidato, vaga, curriculo, processo seletivo, entrevista, candidatura', 3)
|
('Sou candidato a uma vaga', 'candidato, vaga, curriculo, processo seletivo, entrevista, candidatura', 3)
|
||||||
) AS seed(label, keywords, sort_order) ON TRUE
|
) AS seed(label, keywords, sort_order) ON TRUE
|
||||||
ON CONFLICT (flow_id, label) DO NOTHING;
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
WITH audiences AS (
|
WITH audiences AS (
|
||||||
SELECT bta.id, bta.label
|
SELECT bta.id, bta.label
|
||||||
@ -144,4 +142,4 @@ SELECT audiences.id, seed.label, areas.id, seed.keywords, seed.sort_order, TRUE,
|
|||||||
FROM seed
|
FROM seed
|
||||||
INNER JOIN audiences ON audiences.label = seed.audience_label
|
INNER JOIN audiences ON audiences.label = seed.audience_label
|
||||||
INNER JOIN areas ON areas.nome = seed.area_nome
|
INNER JOIN areas ON areas.nome = seed.area_nome
|
||||||
ON CONFLICT (audience_id, label) DO NOTHING;
|
ON CONFLICT DO NOTHING;
|
||||||
@ -1,18 +0,0 @@
|
|||||||
-- Migration 025: Configuração da integração WhatsApp (Meta API)
|
|
||||||
CREATE TABLE IF NOT EXISTS whatsapp_config (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
access_token TEXT,
|
|
||||||
phone_number_id VARCHAR(50),
|
|
||||||
api_version VARCHAR(20) NOT NULL DEFAULT 'v25.0',
|
|
||||||
webhook_token VARCHAR(255),
|
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
||||||
updated_by INTEGER REFERENCES usuarios(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Garante que exista no máximo uma linha de configuração
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS whatsapp_config_singleton ON whatsapp_config ((TRUE));
|
|
||||||
|
|
||||||
-- Linha inicial vazia para facilitar o UPDATE posterior
|
|
||||||
INSERT INTO whatsapp_config (access_token, phone_number_id, api_version, webhook_token)
|
|
||||||
VALUES (NULL, NULL, 'v25.0', NULL)
|
|
||||||
ON CONFLICT DO NOTHING;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
-- Migration 026: WhatsApp HSM Templates + WABA ID
|
|
||||||
-- Adiciona suporte a templates HSM da Meta com botões, header, footer e variáveis estruturadas.
|
|
||||||
|
|
||||||
-- WABA ID na config (necessário para gerenciar templates via API)
|
|
||||||
ALTER TABLE whatsapp_config
|
|
||||||
ADD COLUMN IF NOT EXISTS waba_id VARCHAR(100);
|
|
||||||
|
|
||||||
-- Colunas HSM na tabela de templates
|
|
||||||
ALTER TABLE whatsapp_templates
|
|
||||||
ADD COLUMN IF NOT EXISTS language VARCHAR(10) NOT NULL DEFAULT 'pt_BR',
|
|
||||||
ADD COLUMN IF NOT EXISTS meta_template_name VARCHAR(512),
|
|
||||||
ADD COLUMN IF NOT EXISTS meta_template_id VARCHAR(100),
|
|
||||||
ADD COLUMN IF NOT EXISTS meta_status VARCHAR(50),
|
|
||||||
ADD COLUMN IF NOT EXISTS header_type VARCHAR(20),
|
|
||||||
ADD COLUMN IF NOT EXISTS header_text TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS footer_text TEXT,
|
|
||||||
ADD COLUMN IF NOT EXISTS buttons JSONB,
|
|
||||||
ADD COLUMN IF NOT EXISTS body_variables JSONB;
|
|
||||||
|
|
||||||
-- Templates existentes sem meta_template_name ficam como rascunho
|
|
||||||
UPDATE whatsapp_templates
|
|
||||||
SET status = 'draft'
|
|
||||||
WHERE status IN ('approved', 'meta_review', 'admin_review')
|
|
||||||
AND meta_template_name IS NULL;
|
|
||||||
|
|
||||||
-- Índice único para meta_template_name (ignora NULLs)
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS whatsapp_templates_meta_name_idx
|
|
||||||
ON whatsapp_templates (meta_template_name)
|
|
||||||
WHERE meta_template_name IS NOT NULL;
|
|
||||||
16
database/migrations/024_mensagens.sql
Normal file
16
database/migrations/024_mensagens.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-- Migration 024: Persistência de mensagens WhatsApp (Meta API)
|
||||||
|
CREATE TABLE mensagens (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
wamid VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
chat_id VARCHAR(50) NOT NULL,
|
||||||
|
from_me BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
type VARCHAR(50) NOT NULL DEFAULT 'text',
|
||||||
|
body TEXT,
|
||||||
|
sender_name VARCHAR(255),
|
||||||
|
status VARCHAR(20),
|
||||||
|
timestamp BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_mensagens_chat_id ON mensagens(chat_id);
|
||||||
|
CREATE INDEX idx_mensagens_timestamp ON mensagens(timestamp);
|
||||||
@ -1,6 +0,0 @@
|
|||||||
-- Migration 027: Suporte a mídia nas mensagens
|
|
||||||
-- Adiciona colunas para armazenar caminho local e tipo MIME de mídias recebidas/enviadas.
|
|
||||||
|
|
||||||
ALTER TABLE mensagens
|
|
||||||
ADD COLUMN IF NOT EXISTS media_path VARCHAR(512),
|
|
||||||
ADD COLUMN IF NOT EXISTS media_mime_type VARCHAR(100);
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
-- Migration 028: Nome do arquivo de mídia nas mensagens
|
|
||||||
-- Permite exibir o nome original do arquivo (ex: relatorio.pdf) na interface.
|
|
||||||
|
|
||||||
ALTER TABLE mensagens
|
|
||||||
ADD COLUMN IF NOT EXISTS media_filename VARCHAR(512);
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
-- Migration 029: Remove templates de demonstração sem vinculo com a Meta
|
|
||||||
-- Apaga os seeds criados na migration 005 que não foram submetidos à Meta.
|
|
||||||
|
|
||||||
DELETE FROM whatsapp_templates
|
|
||||||
WHERE name IN ('aviso_fatura', 'boas_vindas', 'lembrete_consulta', 'suporte_tecnico')
|
|
||||||
AND meta_template_name IS NULL;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ssh desenvolvimento@10.0.120.75 -p 60000 "/home/desenvolvimento/scripts/deploy-omnichannel-dev.sh"
|
|
||||||
@ -3,6 +3,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "4000:3000"
|
- "4000:3000"
|
||||||
|
|
||||||
@ -10,9 +11,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "4001:3001"
|
- "4001:3001"
|
||||||
env_file:
|
env_file:
|
||||||
- .env.development
|
- .env.development
|
||||||
volumes:
|
|
||||||
- ./uploads:/app/uploads
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user