Compare commits
21 Commits
master
...
refactor/m
| Author | SHA1 | Date | |
|---|---|---|---|
| c5ebbd50b5 | |||
| dcb3a89e26 | |||
| 3796c89045 | |||
| 9bac5ca638 | |||
| eacf0f93e5 | |||
| 2c86a34963 | |||
| ae199a9bd0 | |||
| 4ab213f8bb | |||
| 347265f4e9 | |||
| bc74cb9fc6 | |||
| 41dec8ff45 | |||
| b2a0ff3a9c | |||
| 4f987e6146 | |||
| e2892d1df5 | |||
| aaebed359e | |||
| dff3c7fc9c | |||
| fef2065601 | |||
| 51eaaf0735 | |||
| 03d28495f0 | |||
| f8945bb92b | |||
| c211df60fb |
101
.gitea/workflows/deploy-dev.yml
Normal file
101
.gitea/workflows/deploy-dev.yml
Normal file
@ -0,0 +1,101 @@
|
||||
name: Deploy Dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy para VM Dev
|
||||
runs-on: omnichannel-uat
|
||||
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.DB_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${DB_PASSWORD:-}" ]; then
|
||||
echo "Secret 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:
|
||||
PGHOST: ${{ secrets.DB_HOST }}
|
||||
PGPORT: ${{ secrets.DB_PORT }}
|
||||
PGUSER: ${{ secrets.DB_USER }}
|
||||
PGDATABASE: ${{ secrets.DB_NAME }}
|
||||
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PGHOST="${PGHOST//[$'\t\r\n ']}"
|
||||
PGPORT="${PGPORT//[$'\t\r\n ']}"
|
||||
export PGHOST PGPORT
|
||||
|
||||
echo "Inicializando controle de migrations..."
|
||||
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: |
|
||||
set -euo pipefail
|
||||
PGHOST="${PGHOST//[$'\t\r\n ']}"
|
||||
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
|
||||
66
.gitea/workflows/deploy-prod.yml
Normal file
66
.gitea/workflows/deploy-prod.yml
Normal file
@ -0,0 +1,66 @@
|
||||
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
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@ -14,4 +14,11 @@ backend/test-api-out.json
|
||||
|
||||
# Frontend Specific Ignore
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
frontend/dist
|
||||
|
||||
/backend/
|
||||
/frontend/
|
||||
frontend/
|
||||
backend/
|
||||
|
||||
docs/
|
||||
1
backend
1
backend
@ -1 +0,0 @@
|
||||
Subproject commit 8790ce70d05d0256ded89ea8fb9335afad41bfa8
|
||||
38
database/migrations/000_schema_migrations.sql
Normal file
38
database/migrations/000_schema_migrations.sql
Normal file
@ -0,0 +1,38 @@
|
||||
-- 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;
|
||||
8
database/migrations/004_whatsapp_templates.sql
Normal file
8
database/migrations/004_whatsapp_templates.sql
Normal file
@ -0,0 +1,8 @@
|
||||
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
|
||||
);
|
||||
|
||||
@ -1,14 +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
|
||||
);
|
||||
|
||||
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;
|
||||
17
database/migrations/008_mensagens.sql
Normal file
17
database/migrations/008_mensagens.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- 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);
|
||||
@ -1,19 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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 017: Fluxo configuravel de triagem do Agente Virtual Sothis
|
||||
-- Migration 015: Fluxo configuravel de triagem do Agente Virtual Sothis
|
||||
-- Tabelas:
|
||||
-- bot_triage_flows
|
||||
-- bot_triage_audiences
|
||||
@ -29,7 +29,8 @@ CREATE TABLE IF NOT EXISTS bot_triage_audiences (
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
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 (
|
||||
@ -41,7 +42,8 @@ CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
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
|
||||
@ -114,7 +116,7 @@ JOIN (
|
||||
('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)
|
||||
) AS seed(label, keywords, sort_order) ON TRUE
|
||||
ON CONFLICT DO NOTHING;
|
||||
ON CONFLICT (flow_id, label) DO NOTHING;
|
||||
|
||||
WITH audiences AS (
|
||||
SELECT bta.id, bta.label
|
||||
@ -142,4 +144,4 @@ SELECT audiences.id, seed.label, areas.id, seed.keywords, seed.sort_order, TRUE,
|
||||
FROM seed
|
||||
INNER JOIN audiences ON audiences.label = seed.audience_label
|
||||
INNER JOIN areas ON areas.nome = seed.area_nome
|
||||
ON CONFLICT DO NOTHING;
|
||||
ON CONFLICT (audience_id, label) DO NOTHING;
|
||||
18
database/migrations/022_whatsapp_config.sql
Normal file
18
database/migrations/022_whatsapp_config.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- 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;
|
||||
29
database/migrations/023_whatsapp_templates_hsm.sql
Normal file
29
database/migrations/023_whatsapp_templates_hsm.sql
Normal file
@ -0,0 +1,29 @@
|
||||
-- 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;
|
||||
6
database/migrations/024_mensagens_media_columns.sql
Normal file
6
database/migrations/024_mensagens_media_columns.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- 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);
|
||||
5
database/migrations/025_mensagens_media_filename.sql
Normal file
5
database/migrations/025_mensagens_media_filename.sql
Normal file
@ -0,0 +1,5 @@
|
||||
-- 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);
|
||||
6
database/migrations/026_remove_fake_templates.sql
Normal file
6
database/migrations/026_remove_fake_templates.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- 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;
|
||||
@ -14,3 +14,5 @@ services:
|
||||
- "4001:3001"
|
||||
env_file:
|
||||
- .env.development
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
|
||||
1
frontend
1
frontend
@ -1 +0,0 @@
|
||||
Subproject commit 7dc07c2a806d6352d2a84c333f09974d997918b0
|
||||
Loading…
Reference in New Issue
Block a user