refactor: remove documentation components and related routes to streamline the application structure and improve maintainability

This commit is contained in:
2025-12-07 16:17:20 -03:00
parent 10a729baed
commit 12984997ce
16 changed files with 1 additions and 4555 deletions

View File

@@ -9,7 +9,6 @@
*/
import type * as acoes from "../acoes.js";
import type * as actions_documentacaoVarredura from "../actions/documentacaoVarredura.js";
import type * as actions_email from "../actions/email.js";
import type * as actions_linkPreview from "../actions/linkPreview.js";
import type * as actions_pushNotifications from "../actions/pushNotifications.js";
@@ -32,8 +31,6 @@ import type * as contratos from "../contratos.js";
import type * as crons from "../crons.js";
import type * as cursos from "../cursos.js";
import type * as dashboard from "../dashboard.js";
import type * as documentacao from "../documentacao.js";
import type * as documentacaoVarredura from "../documentacaoVarredura.js";
import type * as documentos from "../documentos.js";
import type * as email from "../email.js";
import type * as empresas from "../empresas.js";
@@ -67,7 +64,6 @@ import type * as tables_auth from "../tables/auth.js";
import type * as tables_chat from "../tables/chat.js";
import type * as tables_contratos from "../tables/contratos.js";
import type * as tables_cursos from "../tables/cursos.js";
import type * as tables_documentacao from "../tables/documentacao.js";
import type * as tables_empresas from "../tables/empresas.js";
import type * as tables_enderecos from "../tables/enderecos.js";
import type * as tables_ferias from "../tables/ferias.js";
@@ -101,7 +97,6 @@ import type {
declare const fullApi: ApiFromModules<{
acoes: typeof acoes;
"actions/documentacaoVarredura": typeof actions_documentacaoVarredura;
"actions/email": typeof actions_email;
"actions/linkPreview": typeof actions_linkPreview;
"actions/pushNotifications": typeof actions_pushNotifications;
@@ -124,8 +119,6 @@ declare const fullApi: ApiFromModules<{
crons: typeof crons;
cursos: typeof cursos;
dashboard: typeof dashboard;
documentacao: typeof documentacao;
documentacaoVarredura: typeof documentacaoVarredura;
documentos: typeof documentos;
email: typeof email;
empresas: typeof empresas;
@@ -159,7 +152,6 @@ declare const fullApi: ApiFromModules<{
"tables/chat": typeof tables_chat;
"tables/contratos": typeof tables_contratos;
"tables/cursos": typeof tables_cursos;
"tables/documentacao": typeof tables_documentacao;
"tables/empresas": typeof tables_empresas;
"tables/enderecos": typeof tables_enderecos;
"tables/ferias": typeof tables_ferias;

View File

@@ -1,137 +0,0 @@
'use node';
import { action } from '../_generated/server';
import { v } from 'convex/values';
import { internal } from '../_generated/api';
import * as fs from 'fs';
import * as path from 'path';
/**
* Action para ler arquivos do sistema de arquivos e analisar funções
*/
export const analisarArquivos = action({
args: {
varreduraId: v.id('documentacaoVarredura'),
arquivos: v.array(v.string()),
executadoPor: v.id('usuarios')
},
returns: v.object({
arquivosAnalisados: v.number(),
documentosNovos: v.number(),
documentosAtualizados: v.number(),
erros: v.array(v.string())
}),
handler: async (ctx, args) => {
'use node';
const erros: string[] = [];
let arquivosAnalisados = 0;
let documentosNovos = 0;
let documentosAtualizados = 0;
console.log('🔍 [analisarArquivos] Iniciando análise de arquivos...');
console.log(`📁 Total de arquivos: ${args.arquivos.length}`);
console.log(`📋 Arquivos: ${args.arquivos.join(', ')}`);
// Tentar diferentes caminhos possíveis
const caminhosPossiveis = [
path.join(process.cwd(), 'packages/backend/convex'),
path.join(process.cwd(), 'convex'),
path.resolve(__dirname, '../../convex'),
path.resolve(__dirname, '../../../convex')
];
let basePath: string | null = null;
for (const caminho of caminhosPossiveis) {
if (fs.existsSync(caminho)) {
basePath = caminho;
console.log(`✅ Caminho encontrado: ${basePath}`);
break;
}
}
if (!basePath) {
const erro = `Nenhum caminho válido encontrado. Tentados: ${caminhosPossiveis.join(', ')}`;
console.error(`${erro}`);
erros.push(erro);
await ctx.runMutation(internal.documentacaoVarredura.atualizarVarreduraComResultados, {
varreduraId: args.varreduraId,
documentosNovos: 0,
documentosAtualizados: 0,
arquivosAnalisados: 0,
erros: [erro]
});
return {
arquivosAnalisados: 0,
documentosNovos: 0,
documentosAtualizados: 0,
erros: [erro]
};
}
for (const nomeArquivo of args.arquivos) {
try {
const arquivoPath = path.join(basePath, nomeArquivo);
console.log(`📄 Processando: ${arquivoPath}`);
// Verificar se o arquivo existe
if (!fs.existsSync(arquivoPath)) {
const erro = `Arquivo não encontrado: ${nomeArquivo} (caminho: ${arquivoPath})`;
console.error(`${erro}`);
erros.push(erro);
continue;
}
// Ler conteúdo do arquivo
const conteudo = fs.readFileSync(arquivoPath, 'utf-8');
console.log(`✅ Arquivo lido: ${nomeArquivo} (${conteudo.length} caracteres)`);
// Analisar funções no arquivo usando a mutation interna
const resultado = await ctx.runMutation(
internal.documentacaoVarredura.processarArquivo,
{
varreduraId: args.varreduraId,
nomeArquivo,
conteudo,
executadoPor: args.executadoPor
}
);
console.log(
`✅ Processado ${nomeArquivo}: ${resultado.documentosNovos} novos, ${resultado.documentosAtualizados} atualizados`
);
arquivosAnalisados++;
documentosNovos += resultado.documentosNovos;
documentosAtualizados += resultado.documentosAtualizados;
} catch (error) {
const erroMsg = error instanceof Error ? error.message : 'Erro desconhecido';
const erroStack = error instanceof Error ? error.stack : undefined;
const erroCompleto = `Erro ao processar ${nomeArquivo}: ${erroMsg}${erroStack ? `\n${erroStack}` : ''}`;
console.error(`${erroCompleto}`);
erros.push(erroCompleto);
}
}
console.log(
`📊 Resumo: ${arquivosAnalisados} arquivos analisados, ${documentosNovos} novos, ${documentosAtualizados} atualizados`
);
// Atualizar varredura com os resultados
await ctx.runMutation(internal.documentacaoVarredura.atualizarVarreduraComResultados, {
varreduraId: args.varreduraId,
documentosNovos,
documentosAtualizados,
arquivosAnalisados,
erros
});
return {
arquivosAnalisados,
documentosNovos,
documentosAtualizados,
erros
};
}
});

View File

@@ -58,12 +58,4 @@ crons.interval(
{}
);
// Verificar e executar varredura de documentação a cada hora (verifica configuração internamente)
crons.interval(
'verificar-varredura-documentacao',
{ hours: 1 },
internal.documentacaoVarredura.verificarEExecutarVarreduraAgendada,
{}
);
export default crons;

View File

@@ -1,842 +0,0 @@
import { v } from 'convex/values';
import { mutation, query } from './_generated/server';
import { Doc, Id } from './_generated/dataModel';
import type { QueryCtx, MutationCtx } from './_generated/server';
import { getCurrentUserFunction } from './auth';
import { api } from './_generated/api';
// ========== HELPERS ==========
/**
* Normaliza texto para busca (remove acentos, converte para lowercase)
*/
function normalizarTextoParaBusca(texto: string): string {
return texto
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '') // Remove diacríticos
.trim();
}
/**
* Verifica se o usuário tem permissão de TI (nível 0 ou 1)
*/
async function verificarPermissaoTI(ctx: QueryCtx | MutationCtx): Promise<Doc<'usuarios'>> {
const usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) {
throw new Error('Não autenticado');
}
// Verificar se é TI (nível 0 ou 1)
const role = await ctx.db.get(usuarioAtual.roleId);
if (!role || (role.nivel > 1 && role.nome !== 'ti_master' && role.nome !== 'ti_usuario')) {
throw new Error('Acesso negado. Apenas usuários TI podem acessar a documentação.');
}
return usuarioAtual;
}
// ========== QUERIES ==========
/**
* Listar todos os documentos (com filtros opcionais)
*/
export const listarDocumentos = query({
args: {
categoriaId: v.optional(v.id('documentacaoCategorias')),
tipo: v.optional(
v.union(
v.literal('query'),
v.literal('mutation'),
v.literal('action'),
v.literal('component'),
v.literal('route'),
v.literal('modulo'),
v.literal('manual'),
v.literal('outro')
)
),
tags: v.optional(v.array(v.string())),
ativo: v.optional(v.boolean()),
busca: v.optional(v.string()),
limite: v.optional(v.number()),
offset: v.optional(v.number())
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
let documentos: Doc<'documentacao'>[] = [];
// Aplicar filtros usando índices quando possível
if (args.categoriaId) {
documentos = await ctx.db
.query('documentacao')
.withIndex('by_categoria', (q) => q.eq('categoriaId', args.categoriaId))
.collect();
} else if (args.tipo) {
documentos = await ctx.db
.query('documentacao')
.withIndex('by_tipo', (q) => q.eq('tipo', args.tipo!))
.collect();
} else if (args.ativo !== undefined) {
documentos = await ctx.db
.query('documentacao')
.withIndex('by_ativo', (q) => q.eq('ativo', args.ativo!))
.collect();
} else {
documentos = await ctx.db.query('documentacao').collect();
}
// Filtrar por tags (se fornecido)
if (args.tags && args.tags.length > 0) {
documentos = documentos.filter((doc) =>
args.tags!.some((tag) => doc.tags.includes(tag))
);
}
// Filtrar por busca (full-text)
if (args.busca && args.busca.trim().length > 0) {
const buscaNormalizada = normalizarTextoParaBusca(args.busca);
documentos = documentos.filter((doc) => {
const tituloBusca = normalizarTextoParaBusca(doc.titulo);
return (
doc.conteudoBusca.includes(buscaNormalizada) ||
tituloBusca.includes(buscaNormalizada)
);
});
}
// Ordenar por data de atualização (mais recentes primeiro)
documentos.sort((a, b) => b.atualizadoEm - a.atualizadoEm);
// Aplicar paginação
const offset = args.offset || 0;
const limite = args.limite || 50;
const documentosPaginados = documentos.slice(offset, offset + limite);
// Enriquecer com informações de categoria
const documentosEnriquecidos = await Promise.all(
documentosPaginados.map(async (doc) => {
let categoria = null;
if (doc.categoriaId) {
categoria = await ctx.db.get(doc.categoriaId);
}
return {
...doc,
categoria
};
})
);
return {
documentos: documentosEnriquecidos,
total: documentos.length,
offset,
limite
};
}
});
/**
* Obter um documento por ID
*/
export const obterDocumento = query({
args: {
documentoId: v.id('documentacao')
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
const documento = await ctx.db.get(args.documentoId);
if (!documento) {
throw new Error('Documento não encontrado');
}
// Nota: Não podemos fazer patch em uma query, então o contador de visualizações
// será atualizado apenas quando o documento for atualizado via mutation
// Enriquecer com informações
let categoria = null;
if (documento.categoriaId) {
categoria = await ctx.db.get(documento.categoriaId);
}
const criadoPor = await ctx.db.get(documento.criadoPor);
const atualizadoPor = documento.atualizadoPor
? await ctx.db.get(documento.atualizadoPor)
: null;
return {
...documento,
categoria,
criadoPorUsuario: criadoPor
? {
_id: criadoPor._id,
nome: criadoPor.nome,
email: criadoPor.email
}
: null,
atualizadoPorUsuario: atualizadoPor
? {
_id: atualizadoPor._id,
nome: atualizadoPor.nome,
email: atualizadoPor.email
}
: null
};
}
});
/**
* Buscar documentos (full-text search)
*/
export const buscarDocumentos = query({
args: {
query: v.string(),
categoriaId: v.optional(v.id('documentacaoCategorias')),
tipo: v.optional(
v.union(
v.literal('query'),
v.literal('mutation'),
v.literal('action'),
v.literal('component'),
v.literal('route'),
v.literal('modulo'),
v.literal('manual'),
v.literal('outro')
)
),
tags: v.optional(v.array(v.string())),
limite: v.optional(v.number())
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
const queryNormalizada = normalizarTextoParaBusca(args.query);
const limite = args.limite || 50;
// Buscar todos os documentos ativos
let documentos = await ctx.db
.query('documentacao')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.collect();
// Filtrar por busca
documentos = documentos.filter((doc) => {
const tituloBusca = normalizarTextoParaBusca(doc.titulo);
return (
doc.conteudoBusca.includes(queryNormalizada) ||
tituloBusca.includes(queryNormalizada) ||
doc.tags.some((tag) => normalizarTextoParaBusca(tag).includes(queryNormalizada))
);
});
// Aplicar filtros adicionais
if (args.categoriaId) {
documentos = documentos.filter((doc) => doc.categoriaId === args.categoriaId);
}
if (args.tipo) {
documentos = documentos.filter((doc) => doc.tipo === args.tipo);
}
if (args.tags && args.tags.length > 0) {
documentos = documentos.filter((doc) =>
args.tags!.some((tag) => doc.tags.includes(tag))
);
}
// Ordenar por relevância (simples: documentos com mais matches primeiro)
documentos.sort((a, b) => {
const aMatches =
(normalizarTextoParaBusca(a.titulo).includes(queryNormalizada) ? 2 : 0) +
(a.conteudoBusca.includes(queryNormalizada) ? 1 : 0);
const bMatches =
(normalizarTextoParaBusca(b.titulo).includes(queryNormalizada) ? 2 : 0) +
(b.conteudoBusca.includes(queryNormalizada) ? 1 : 0);
return bMatches - aMatches;
});
// Limitar resultados
documentos = documentos.slice(0, limite);
// Enriquecer com categoria
const documentosEnriquecidos = await Promise.all(
documentos.map(async (doc) => {
let categoria = null;
if (doc.categoriaId) {
categoria = await ctx.db.get(doc.categoriaId);
}
return {
...doc,
categoria
};
})
);
return documentosEnriquecidos;
}
});
/**
* Listar categorias
*/
export const listarCategorias = query({
args: {
parentId: v.optional(v.id('documentacaoCategorias')),
ativo: v.optional(v.boolean())
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
let categorias: Doc<'documentacaoCategorias'>[] = [];
if (args.parentId !== undefined) {
categorias = await ctx.db
.query('documentacaoCategorias')
.withIndex('by_parent', (q) => q.eq('parentId', args.parentId))
.collect();
} else if (args.ativo !== undefined) {
categorias = await ctx.db
.query('documentacaoCategorias')
.withIndex('by_ativo', (q) => q.eq('ativo', args.ativo!))
.collect();
} else {
categorias = await ctx.db.query('documentacaoCategorias').collect();
}
// Ordenar por ordem
categorias.sort((a, b) => a.ordem - b.ordem);
return categorias;
}
});
/**
* Obter múltiplos documentos por IDs
*/
export const obterDocumentosPorIds = query({
args: {
documentosIds: v.array(v.id('documentacao'))
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
const documentos = await Promise.all(
args.documentosIds.map(async (id) => {
const doc = await ctx.db.get(id);
if (!doc) return null;
let categoria = null;
if (doc.categoriaId) {
categoria = await ctx.db.get(doc.categoriaId);
}
return {
...doc,
categoria
};
})
);
return documentos.filter((doc): doc is NonNullable<typeof doc> => doc !== null);
}
});
/**
* Listar tags
*/
export const listarTags = query({
args: {
ativo: v.optional(v.boolean()),
limite: v.optional(v.number())
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
let tags: Doc<'documentacaoTags'>[] = [];
if (args.ativo !== undefined) {
tags = await ctx.db
.query('documentacaoTags')
.withIndex('by_ativo', (q) => q.eq('ativo', args.ativo!))
.collect();
} else {
tags = await ctx.db.query('documentacaoTags').collect();
}
// Ordenar por uso (mais usadas primeiro)
tags.sort((a, b) => b.usadoEm - a.usadoEm);
// Limitar se necessário
if (args.limite) {
tags = tags.slice(0, args.limite);
}
return tags;
}
});
// ========== MUTATIONS ==========
/**
* Criar novo documento
*/
export const criarDocumento = mutation({
args: {
titulo: v.string(),
conteudo: v.string(),
conteudoHtml: v.optional(v.string()),
categoriaId: v.optional(v.id('documentacaoCategorias')),
tags: v.array(v.string()),
tipo: v.union(
v.literal('query'),
v.literal('mutation'),
v.literal('action'),
v.literal('component'),
v.literal('route'),
v.literal('modulo'),
v.literal('manual'),
v.literal('outro')
),
versao: v.string(),
arquivoOrigem: v.optional(v.string()),
funcaoOrigem: v.optional(v.string()),
hashOrigem: v.optional(v.string()),
metadados: v.optional(
v.object({
parametros: v.optional(v.array(v.string())),
retorno: v.optional(v.string()),
dependencias: v.optional(v.array(v.string())),
exemplos: v.optional(v.array(v.string())),
algoritmo: v.optional(v.string())
})
),
geradoAutomaticamente: v.optional(v.boolean())
},
handler: async (ctx, args) => {
const usuarioAtual = await verificarPermissaoTI(ctx);
const agora = Date.now();
const conteudoBusca = normalizarTextoParaBusca(args.titulo + ' ' + args.conteudo);
const documentoId = await ctx.db.insert('documentacao', {
titulo: args.titulo,
conteudo: args.conteudo,
conteudoHtml: args.conteudoHtml,
conteudoBusca,
categoriaId: args.categoriaId,
tags: args.tags,
tipo: args.tipo,
versao: args.versao,
arquivoOrigem: args.arquivoOrigem,
funcaoOrigem: args.funcaoOrigem,
hashOrigem: args.hashOrigem,
metadados: args.metadados,
ativo: true,
criadoPor: usuarioAtual._id,
criadoEm: agora,
atualizadoEm: agora,
visualizacoes: 0,
geradoAutomaticamente: args.geradoAutomaticamente ?? false
});
// Atualizar contadores de uso das tags
for (const tagNome of args.tags) {
const tag = await ctx.db
.query('documentacaoTags')
.withIndex('by_nome', (q) => q.eq('nome', tagNome))
.first();
if (tag) {
await ctx.db.patch(tag._id, {
usadoEm: tag.usadoEm + 1
});
}
}
return documentoId;
}
});
/**
* Atualizar documento
*/
export const atualizarDocumento = mutation({
args: {
documentoId: v.id('documentacao'),
titulo: v.optional(v.string()),
conteudo: v.optional(v.string()),
conteudoHtml: v.optional(v.string()),
categoriaId: v.optional(v.id('documentacaoCategorias')),
tags: v.optional(v.array(v.string())),
tipo: v.optional(
v.union(
v.literal('query'),
v.literal('mutation'),
v.literal('action'),
v.literal('component'),
v.literal('route'),
v.literal('modulo'),
v.literal('manual'),
v.literal('outro')
)
),
versao: v.optional(v.string()),
metadados: v.optional(
v.object({
parametros: v.optional(v.array(v.string())),
retorno: v.optional(v.string()),
dependencias: v.optional(v.array(v.string())),
exemplos: v.optional(v.array(v.string())),
algoritmo: v.optional(v.string())
})
),
ativo: v.optional(v.boolean())
},
handler: async (ctx, args) => {
const usuarioAtual = await verificarPermissaoTI(ctx);
const documento = await ctx.db.get(args.documentoId);
if (!documento) {
throw new Error('Documento não encontrado');
}
const atualizacoes: Partial<Doc<'documentacao'>> = {
atualizadoPor: usuarioAtual._id,
atualizadoEm: Date.now()
};
if (args.titulo !== undefined) atualizacoes.titulo = args.titulo;
if (args.conteudo !== undefined) atualizacoes.conteudo = args.conteudo;
if (args.conteudoHtml !== undefined) atualizacoes.conteudoHtml = args.conteudoHtml;
if (args.categoriaId !== undefined) atualizacoes.categoriaId = args.categoriaId;
if (args.tags !== undefined) atualizacoes.tags = args.tags;
if (args.tipo !== undefined) atualizacoes.tipo = args.tipo;
if (args.versao !== undefined) atualizacoes.versao = args.versao;
if (args.metadados !== undefined) atualizacoes.metadados = args.metadados;
if (args.ativo !== undefined) atualizacoes.ativo = args.ativo;
// Atualizar conteúdo de busca se título ou conteúdo mudaram
if (args.titulo !== undefined || args.conteudo !== undefined) {
const novoTitulo = args.titulo ?? documento.titulo;
const novoConteudo = args.conteudo ?? documento.conteudo;
atualizacoes.conteudoBusca = normalizarTextoParaBusca(novoTitulo + ' ' + novoConteudo);
}
// Atualizar contadores de tags se tags mudaram
if (args.tags !== undefined) {
// Decrementar tags antigas
for (const tagNome of documento.tags) {
if (!args.tags.includes(tagNome)) {
const tag = await ctx.db
.query('documentacaoTags')
.withIndex('by_nome', (q) => q.eq('nome', tagNome))
.first();
if (tag && tag.usadoEm > 0) {
await ctx.db.patch(tag._id, {
usadoEm: tag.usadoEm - 1
});
}
}
}
// Incrementar tags novas
for (const tagNome of args.tags) {
if (!documento.tags.includes(tagNome)) {
const tag = await ctx.db
.query('documentacaoTags')
.withIndex('by_nome', (q) => q.eq('nome', tagNome))
.first();
if (tag) {
await ctx.db.patch(tag._id, {
usadoEm: tag.usadoEm + 1
});
}
}
}
}
await ctx.db.patch(args.documentoId, atualizacoes);
return { sucesso: true };
}
});
/**
* Deletar documento (soft delete)
*/
export const deletarDocumento = mutation({
args: {
documentoId: v.id('documentacao')
},
handler: async (ctx, args) => {
const usuarioAtual = await verificarPermissaoTI(ctx);
const documento = await ctx.db.get(args.documentoId);
if (!documento) {
throw new Error('Documento não encontrado');
}
// Soft delete: apenas marcar como inativo
await ctx.db.patch(args.documentoId, {
ativo: false,
atualizadoPor: usuarioAtual._id,
atualizadoEm: Date.now()
});
// Decrementar contadores de tags
for (const tagNome of documento.tags) {
const tag = await ctx.db
.query('documentacaoTags')
.withIndex('by_nome', (q) => q.eq('nome', tagNome))
.first();
if (tag && tag.usadoEm > 0) {
await ctx.db.patch(tag._id, {
usadoEm: tag.usadoEm - 1
});
}
}
return { sucesso: true };
}
});
/**
* Criar categoria
*/
export const criarCategoria = mutation({
args: {
nome: v.string(),
descricao: v.optional(v.string()),
icone: v.optional(v.string()),
cor: v.optional(
v.union(
v.literal('primary'),
v.literal('secondary'),
v.literal('accent'),
v.literal('success'),
v.literal('warning'),
v.literal('error'),
v.literal('info')
)
),
parentId: v.optional(v.id('documentacaoCategorias')),
ordem: v.number()
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
const usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) {
throw new Error('Não autenticado');
}
const agora = Date.now();
const categoriaId = await ctx.db.insert('documentacaoCategorias', {
nome: args.nome,
descricao: args.descricao,
icone: args.icone,
cor: args.cor,
parentId: args.parentId,
ordem: args.ordem,
ativo: true,
criadoPor: usuarioAtual._id,
criadoEm: agora,
atualizadoEm: agora
});
return categoriaId;
}
});
/**
* Criar tag
*/
export const criarTag = mutation({
args: {
nome: v.string(),
descricao: v.optional(v.string()),
cor: v.optional(
v.union(
v.literal('primary'),
v.literal('secondary'),
v.literal('accent'),
v.literal('success'),
v.literal('warning'),
v.literal('error'),
v.literal('info')
)
)
},
handler: async (ctx, args) => {
await verificarPermissaoTI(ctx);
// Verificar se tag já existe
const tagExistente = await ctx.db
.query('documentacaoTags')
.withIndex('by_nome', (q) => q.eq('nome', args.nome))
.first();
if (tagExistente) {
throw new Error('Tag já existe');
}
const usuarioAtual = await getCurrentUserFunction(ctx);
if (!usuarioAtual) {
throw new Error('Não autenticado');
}
const agora = Date.now();
const tagId = await ctx.db.insert('documentacaoTags', {
nome: args.nome,
descricao: args.descricao,
cor: args.cor,
usadoEm: 0,
ativo: true,
criadoPor: usuarioAtual._id,
criadoEm: agora
});
return tagId;
}
});
/**
* Obter configuração de agendamento de varredura
*/
export const obterConfigVarredura = query({
args: {},
handler: async (ctx) => {
await verificarPermissaoTI(ctx);
const config = await ctx.db
.query('documentacaoConfig')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.first();
return config;
}
});
/**
* Salvar configuração de agendamento de varredura
*/
export const salvarConfigVarredura = mutation({
args: {
ativo: v.boolean(),
diasSemana: v.array(
v.union(
v.literal('domingo'),
v.literal('segunda'),
v.literal('terca'),
v.literal('quarta'),
v.literal('quinta'),
v.literal('sexta'),
v.literal('sabado')
)
),
horario: v.string(), // Formato "HH:MM"
fusoHorario: v.optional(v.string())
},
handler: async (ctx, args) => {
const usuarioAtual = await verificarPermissaoTI(ctx);
// Buscar configuração existente
const configExistente = await ctx.db
.query('documentacaoConfig')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.first();
const agora = Date.now();
if (configExistente) {
// Atualizar configuração existente
await ctx.db.patch(configExistente._id, {
ativo: args.ativo,
diasSemana: args.diasSemana,
horario: args.horario,
fusoHorario: args.fusoHorario,
atualizadoPor: usuarioAtual._id,
atualizadoEm: agora
});
return configExistente._id;
} else {
// Criar nova configuração
const configId = await ctx.db.insert('documentacaoConfig', {
ativo: args.ativo,
diasSemana: args.diasSemana,
horario: args.horario,
fusoHorario: args.fusoHorario || 'America/Recife',
configuradoPor: usuarioAtual._id,
configuradoEm: agora,
atualizadoEm: agora
});
return configId;
}
}
});
/**
* Notificar TI_master sobre novos documentos criados
*/
export const notificarNovosDocumentos = mutation({
args: {
documentosIds: v.array(v.id('documentacao')),
quantidade: v.number()
},
handler: async (ctx, args) => {
// Buscar usuários TI_master (nível 0)
const roleTIMaster = await ctx.db
.query('roles')
.filter((q) => q.eq(q.field('nivel'), 0))
.first();
if (!roleTIMaster) {
return; // Não há TI_master configurado
}
const usuariosTIMaster = await ctx.db
.query('usuarios')
.filter((q) => q.eq(q.field('roleId'), roleTIMaster._id))
.collect();
// Criar notificações no chat
for (const usuario of usuariosTIMaster) {
await ctx.db.insert('notificacoes', {
usuarioId: usuario._id,
tipo: 'nova_mensagem',
titulo: '📚 Novos Documentos Criados',
descricao: `${args.quantidade} novo(s) documento(s) de documentação foram criados automaticamente pela varredura do sistema.`,
lida: false,
criadaEm: Date.now()
});
}
// Enviar emails para TI_master (usando scheduler para não bloquear)
for (const usuario of usuariosTIMaster) {
if (usuario.email) {
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
destinatario: usuario.email,
destinatarioId: usuario._id,
templateCodigo: 'documentacao_novos_documentos',
variaveis: {
destinatarioNome: usuario.nome,
quantidade: args.quantidade.toString(),
urlSistema: process.env.FRONTEND_URL || 'http://localhost:5173'
},
enviadoPor: usuario._id
});
}
}
return { sucesso: true };
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,6 @@ import { pontoTables } from './tables/ponto';
import { pedidosTables } from './tables/pedidos';
import { produtosTables } from './tables/produtos';
import { lgpdTables } from './tables/lgpdTables';
import { documentacaoTables } from './tables/documentacao';
export default defineSchema({
...setoresTables,
@@ -43,6 +42,5 @@ export default defineSchema({
...pontoTables,
...pedidosTables,
...produtosTables,
...lgpdTables,
...documentacaoTables
...lgpdTables
});

View File

@@ -1,152 +0,0 @@
import { defineTable } from 'convex/server';
import { v } from 'convex/values';
export const documentacaoTables = {
// Documentos principais
documentacao: defineTable({
titulo: v.string(),
conteudo: v.string(), // Conteúdo em Markdown
conteudoHtml: v.optional(v.string()), // Conteúdo renderizado em HTML
conteudoBusca: v.string(), // Versão normalizada para busca (lowercase, sem acentos)
categoriaId: v.optional(v.id('documentacaoCategorias')),
tags: v.array(v.string()),
tipo: v.union(
v.literal('query'),
v.literal('mutation'),
v.literal('action'),
v.literal('component'),
v.literal('route'),
v.literal('modulo'),
v.literal('manual'),
v.literal('outro')
),
versao: v.string(), // Versão do documento (ex: "1.0.0")
arquivoOrigem: v.optional(v.string()), // Caminho do arquivo que gerou este documento
funcaoOrigem: v.optional(v.string()), // Nome da função/componente que gerou este documento
hashOrigem: v.optional(v.string()), // Hash do arquivo/função para detectar mudanças
metadados: v.optional(
v.object({
parametros: v.optional(v.array(v.string())),
retorno: v.optional(v.string()),
dependencias: v.optional(v.array(v.string())),
exemplos: v.optional(v.array(v.string())),
algoritmo: v.optional(v.string())
})
),
ativo: v.boolean(),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoPor: v.optional(v.id('usuarios')),
atualizadoEm: v.number(),
visualizacoes: v.number(), // Contador de visualizações
geradoAutomaticamente: v.boolean() // Se foi gerado pela varredura automática
})
.index('by_categoria', ['categoriaId'])
.index('by_tipo', ['tipo'])
.index('by_busca', ['conteudoBusca'])
.index('by_ativo', ['ativo'])
.index('by_criado_em', ['criadoEm'])
.index('by_atualizado_em', ['atualizadoEm'])
.index('by_arquivo_origem', ['arquivoOrigem'])
.index('by_hash_origem', ['hashOrigem']),
// Categorias hierárquicas (Módulos, Seções, Funções)
documentacaoCategorias: defineTable({
nome: v.string(),
descricao: v.optional(v.string()),
icone: v.optional(v.string()), // Nome do ícone (lucide-svelte)
cor: v.optional(
v.union(
v.literal('primary'),
v.literal('secondary'),
v.literal('accent'),
v.literal('success'),
v.literal('warning'),
v.literal('error'),
v.literal('info')
)
),
parentId: v.optional(v.id('documentacaoCategorias')), // Para hierarquia
ordem: v.number(), // Ordem de exibição
ativo: v.boolean(),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoEm: v.number()
})
.index('by_parent', ['parentId'])
.index('by_ordem', ['ordem'])
.index('by_ativo', ['ativo']),
// Tags para busca e organização
documentacaoTags: defineTable({
nome: v.string(),
descricao: v.optional(v.string()),
cor: v.optional(
v.union(
v.literal('primary'),
v.literal('secondary'),
v.literal('accent'),
v.literal('success'),
v.literal('warning'),
v.literal('error'),
v.literal('info')
)
),
usadoEm: v.number(), // Contador de quantos documentos usam esta tag
ativo: v.boolean(),
criadoPor: v.id('usuarios'),
criadoEm: v.number()
})
.index('by_nome', ['nome'])
.index('by_ativo', ['ativo']),
// Histórico de varreduras realizadas
documentacaoVarredura: defineTable({
tipo: v.union(v.literal('automatica'), v.literal('manual')),
status: v.union(
v.literal('em_andamento'),
v.literal('concluida'),
v.literal('erro'),
v.literal('cancelada')
),
documentosEncontrados: v.number(), // Quantidade de documentos encontrados
documentosNovos: v.number(), // Quantidade de novos documentos criados
documentosAtualizados: v.number(), // Quantidade de documentos atualizados
arquivosAnalisados: v.number(), // Quantidade de arquivos analisados
erros: v.optional(v.array(v.string())), // Lista de erros encontrados
duracaoMs: v.optional(v.number()), // Duração em milissegundos
executadoPor: v.id('usuarios'),
iniciadoEm: v.number(),
concluidoEm: v.optional(v.number())
})
.index('by_status', ['status'])
.index('by_tipo', ['tipo'])
.index('by_executado_por', ['executadoPor'])
.index('by_iniciado_em', ['iniciadoEm']),
// Configurações de agendamento de varredura
documentacaoConfig: defineTable({
ativo: v.boolean(),
diasSemana: v.array(
v.union(
v.literal('domingo'),
v.literal('segunda'),
v.literal('terca'),
v.literal('quarta'),
v.literal('quinta'),
v.literal('sexta'),
v.literal('sabado')
)
), // Dias da semana para executar varredura
horario: v.string(), // Horário no formato "HH:MM" (ex: "08:00")
fusoHorario: v.optional(v.string()), // Fuso horário (padrão: "America/Recife")
ultimaExecucao: v.optional(v.number()), // Timestamp da última execução
proximaExecucao: v.optional(v.number()), // Timestamp da próxima execução agendada
configuradoPor: v.id('usuarios'),
configuradoEm: v.number(),
atualizadoPor: v.optional(v.id('usuarios')),
atualizadoEm: v.number()
})
.index('by_ativo', ['ativo'])
};