diff --git a/database/init/001.schema.sql b/database/init/001.schema.sql deleted file mode 100644 index e69de29..0000000 diff --git a/database/migrations/001_auth.sql b/database/migrations/001_auth.sql new file mode 100644 index 0000000..c0ea1cc --- /dev/null +++ b/database/migrations/001_auth.sql @@ -0,0 +1,112 @@ +-- ============================================================ +-- Migration 001: Módulo de Autenticação +-- Tabelas: usuários, provedores, perfis de acesso e auditoria +-- ============================================================ + + +-- ------------------------------------------------------------ +-- Tabela: usuarios +-- Representa qualquer pessoa que acessa o sistema, +-- independente de como ela se autenticou +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS usuarios ( + id SERIAL PRIMARY KEY, + nome VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE, -- pode ser nulo em contas só com LDAP sem email + ativo BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_usuarios_email ON usuarios (email); + + +-- ------------------------------------------------------------ +-- Tabela: usuarios_provedores +-- Vincula um usuário a um ou mais provedores de autenticação +-- Um mesmo usuário pode logar via LDAP e via Microsoft +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS usuarios_provedores ( + id SERIAL PRIMARY KEY, + -- FK para o usuário correspondente + usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE, + -- Provedor de autenticação: 'ldap' | 'microsoft' | 'google' | etc. + provedor VARCHAR(50) NOT NULL, + -- ID do usuário dentro do provedor (azure_id, username do AD, sub do Google...) + provedor_user_id VARCHAR(255) NOT NULL, + -- Evita duplicidade do mesmo provedor pro mesmo usuário + CONSTRAINT uq_provedor_user UNIQUE (provedor, provedor_user_id), + + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_usuarios_provedores_usuario ON usuarios_provedores (usuario_id); +CREATE INDEX IF NOT EXISTS idx_usuarios_provedores_lookup ON usuarios_provedores (provedor, provedor_user_id); + + +-- ------------------------------------------------------------ +-- Tabela: perfis_acesso +-- Define os papéis disponíveis no sistema +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS perfis_acesso ( + id SERIAL PRIMARY KEY, + nome VARCHAR(100) NOT NULL UNIQUE, + descricao TEXT, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + + +-- ------------------------------------------------------------ +-- Tabela: usuarios_perfis +-- Relacionamento entre usuários e perfis (muitos-para-muitos) +-- Um usuário pode ter mais de um perfil se necessário +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS usuarios_perfis ( + id SERIAL PRIMARY KEY, + usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE, + perfil_id INTEGER NOT NULL REFERENCES perfis_acesso (id) ON DELETE CASCADE, + + CONSTRAINT uq_usuario_perfil UNIQUE (usuario_id, perfil_id), + + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_usuarios_perfis_usuario ON usuarios_perfis (usuario_id); +CREATE INDEX IF NOT EXISTS idx_usuarios_perfis_perfil ON usuarios_perfis (perfil_id); + + +-- ------------------------------------------------------------ +-- Tabela: logs_auditoria +-- Registra ações relevantes feitas por usuários ou pelo sistema +-- usuario_id NULL = ação do sistema (ex: tentativa de login falha) +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS logs_auditoria ( + id SERIAL PRIMARY KEY, + usuario_id INTEGER REFERENCES usuarios (id) ON DELETE SET NULL, + + -- Ação realizada — ex: 'LOGIN_LDAP', 'LOGIN_MICROSOFT', 'LOGIN_FALHOU', 'USUARIO_CRIADO' + acao VARCHAR(100) NOT NULL, + + -- Dados extras livres — ex: { "ip": "...", "provedor": "microsoft", "motivo": "..." } + detalhes JSONB, + + ip_origem VARCHAR(45), + + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_logs_usuario ON logs_auditoria (usuario_id); +CREATE INDEX IF NOT EXISTS idx_logs_acao ON logs_auditoria (acao); +CREATE INDEX IF NOT EXISTS idx_logs_created_at ON logs_auditoria (created_at); + + +-- ------------------------------------------------------------ +-- Dados iniciais: perfis de acesso +-- ON CONFLICT garante que pode rodar mais de uma vez sem erro +-- ------------------------------------------------------------ +INSERT INTO perfis_acesso (nome, descricao) VALUES + ('Agente', 'Atendente responsável por responder e encaminhar chamados'), + ('Supervisor', 'Gestor com visibilidade de filas e relatórios'), + ('Admin', 'Administrador com acesso total ao sistema') +ON CONFLICT (nome) DO NOTHING; \ No newline at end of file diff --git a/database/migrations/002_area.sql b/database/migrations/002_area.sql new file mode 100644 index 0000000..1153a8b --- /dev/null +++ b/database/migrations/002_area.sql @@ -0,0 +1,63 @@ +-- ============================================================ +-- Migration 002: Modulo de Areas +-- Tabelas: areas e relacionamento usuarios_areas +-- ============================================================ + + +-- ------------------------------------------------------------ +-- Tabela: areas +-- Representa as areas operacionais do atendimento +-- Ex: Suporte, Financeiro, Comercial +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS areas ( + id SERIAL PRIMARY KEY, + nome VARCHAR(120) NOT NULL UNIQUE, + descricao TEXT, + responsavel_usuario_id INTEGER REFERENCES usuarios (id) ON DELETE SET NULL, + ativo BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_areas_nome ON areas (nome); +CREATE INDEX IF NOT EXISTS idx_areas_responsavel ON areas (responsavel_usuario_id); +CREATE INDEX IF NOT EXISTS idx_areas_ativo ON areas (ativo); + + +-- ------------------------------------------------------------ +-- Tabela: usuarios_areas +-- Relacionamento muitos-para-muitos entre usuarios e areas +-- Um usuario pode atuar em mais de uma area e uma area pode ter +-- varios usuarios. +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS usuarios_areas ( + id SERIAL PRIMARY KEY, + usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE, + area_id INTEGER NOT NULL REFERENCES areas (id) ON DELETE CASCADE, + funcao VARCHAR(80), + principal BOOLEAN NOT NULL DEFAULT FALSE, + ativo BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + + CONSTRAINT uq_usuario_area UNIQUE (usuario_id, area_id) +); + +CREATE INDEX IF NOT EXISTS idx_usuarios_areas_usuario ON usuarios_areas (usuario_id); +CREATE INDEX IF NOT EXISTS idx_usuarios_areas_area ON usuarios_areas (area_id); +CREATE INDEX IF NOT EXISTS idx_usuarios_areas_ativo ON usuarios_areas (ativo); + +-- Garante que cada usuario tenha no maximo uma area principal. +CREATE UNIQUE INDEX IF NOT EXISTS uq_usuario_area_principal + ON usuarios_areas (usuario_id) + WHERE principal = TRUE; + + +-- ------------------------------------------------------------ +-- Dados iniciais: areas padrao para o MVP +-- ------------------------------------------------------------ +INSERT INTO areas (nome, descricao) VALUES + ('Suporte', 'Atendimento operacional e resolucao de duvidas tecnicas'), + ('Financeiro', 'Atendimento relacionado a cobrancas, pagamentos e notas'), + ('Comercial', 'Atendimento de vendas, propostas e relacionamento comercial') +ON CONFLICT (nome) DO NOTHING; diff --git a/database/migrations/003_demo_access.sql b/database/migrations/003_demo_access.sql new file mode 100644 index 0000000..83e7164 --- /dev/null +++ b/database/migrations/003_demo_access.sql @@ -0,0 +1,62 @@ +-- ============================================================ +-- Migration 003: Usuarios de demonstracao e acessos iniciais +-- Perfis: Admin, Supervisor e Agente +-- Areas: Suporte, Financeiro e Comercial +-- ============================================================ + + +INSERT INTO usuarios (nome, email, ativo) VALUES + ('Admin Demo', 'admin@sothis.com.br', TRUE), + ('Supervisor Demo', 'supervisor@sothis.com.br', TRUE), + ('Atendente Demo', 'atendente@sothis.com.br', TRUE) +ON CONFLICT (email) DO UPDATE SET + nome = EXCLUDED.nome, + ativo = TRUE, + updated_at = NOW(); + + +INSERT INTO usuarios_provedores (usuario_id, provedor, provedor_user_id) +SELECT u.id, provider.provedor, provider.provedor_user_id +FROM usuarios u +JOIN ( + VALUES + ('admin@sothis.com.br', 'ldap', 'admin'), + ('admin@sothis.com.br', 'microsoft', 'admin@sothis.com.br'), + ('supervisor@sothis.com.br', 'ldap', 'supervisor'), + ('supervisor@sothis.com.br', 'microsoft', 'supervisor@sothis.com.br'), + ('atendente@sothis.com.br', 'ldap', 'atendente'), + ('atendente@sothis.com.br', 'microsoft', 'atendente@sothis.com.br') +) AS provider(email, provedor, provedor_user_id) ON provider.email = u.email +ON CONFLICT (provedor, provedor_user_id) +DO UPDATE SET usuario_id = EXCLUDED.usuario_id; + + +INSERT INTO usuarios_perfis (usuario_id, perfil_id) +SELECT u.id, p.id +FROM usuarios u +JOIN ( + VALUES + ('admin@sothis.com.br', 'Admin'), + ('supervisor@sothis.com.br', 'Supervisor'), + ('atendente@sothis.com.br', 'Agente') +) AS access(email, perfil) ON access.email = u.email +JOIN perfis_acesso p ON p.nome = access.perfil +ON CONFLICT (usuario_id, perfil_id) DO NOTHING; + + +INSERT INTO usuarios_areas (usuario_id, area_id, funcao, principal, ativo) +SELECT u.id, a.id, access.funcao, TRUE, TRUE +FROM usuarios u +JOIN ( + VALUES + ('admin@sothis.com.br', 'Suporte', 'Administrador'), + ('supervisor@sothis.com.br', 'Suporte', 'Supervisor'), + ('atendente@sothis.com.br', 'Suporte', 'Atendente') +) AS access(email, area, funcao) ON access.email = u.email +JOIN areas a ON a.nome = access.area +ON CONFLICT (usuario_id, area_id) +DO UPDATE SET + funcao = EXCLUDED.funcao, + principal = TRUE, + ativo = TRUE, + updated_at = NOW();