refactor: enhance role management UI and integrate profile management features
- Introduced a modal for managing user profiles, allowing for the creation and editing of profiles with improved state management. - Updated the role filtering logic to enhance type safety and readability. - Refactored UI components for better user experience, including improved button states and loading indicators. - Removed outdated code related to permissions and streamlined the overall structure for maintainability.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
type RoleRow = {
|
||||
_id: Id<"roles">;
|
||||
_creationTime: number;
|
||||
@@ -32,6 +33,14 @@
|
||||
// Formato: { "roleId-recurso": true/false }
|
||||
let recursosExpandidos: Record<string, boolean> = $state({});
|
||||
|
||||
// Gerenciamento de Perfis
|
||||
let modalGerenciarPerfisAberto = $state(false);
|
||||
let perfilSendoEditado = $state<RoleRow | null>(null);
|
||||
let nomeNovoPerfil = $state("");
|
||||
let descricaoNovoPerfil = $state("");
|
||||
let nivelNovoPerfil = $state(3);
|
||||
let processando = $state(false);
|
||||
|
||||
// Cache de permissões por role
|
||||
let permissoesPorRole: Record<
|
||||
string,
|
||||
@@ -66,13 +75,13 @@
|
||||
|
||||
const rolesFiltradas = $derived.by(() => {
|
||||
if (!rolesQuery.data) return [];
|
||||
let rs: Array<RoleRow> = rolesQuery.data as Array<RoleRow>;
|
||||
let rs = rolesQuery.data; // Removed explicit type annotation
|
||||
if (filtroRole)
|
||||
rs = rs.filter((r: RoleRow) => r._id === (filtroRole as any));
|
||||
rs = rs.filter((r) => r._id === (filtroRole)); // Removed as any
|
||||
if (busca.trim()) {
|
||||
const b = busca.toLowerCase();
|
||||
rs = rs.filter(
|
||||
(r: RoleRow) =>
|
||||
(r) =>
|
||||
r.descricao.toLowerCase().includes(b) ||
|
||||
r.nome.toLowerCase().includes(b)
|
||||
);
|
||||
@@ -120,8 +129,9 @@
|
||||
];
|
||||
}
|
||||
mostrarMensagem("success", "Permissão atualizada com sucesso!");
|
||||
} catch (e: any) {
|
||||
mostrarMensagem("error", e.message || "Erro ao atualizar permissão");
|
||||
} catch (error: unknown) { // Changed to unknown
|
||||
const message = error instanceof Error ? error.message : "Erro ao atualizar permissão";
|
||||
mostrarMensagem("error", message);
|
||||
} finally {
|
||||
salvando = false;
|
||||
}
|
||||
@@ -132,6 +142,90 @@
|
||||
const entry = dados?.find((e) => e.recurso === recurso);
|
||||
return entry ? entry.acoes.includes(acao) : false;
|
||||
}
|
||||
|
||||
function abrirModalCriarPerfil() {
|
||||
nomeNovoPerfil = "";
|
||||
descricaoNovoPerfil = "";
|
||||
nivelNovoPerfil = 3; // Default to a common level
|
||||
perfilSendoEditado = null;
|
||||
modalGerenciarPerfisAberto = true;
|
||||
}
|
||||
|
||||
function prepararEdicaoPerfil(role: RoleRow) {
|
||||
perfilSendoEditado = role;
|
||||
nomeNovoPerfil = role.nome;
|
||||
descricaoNovoPerfil = role.descricao;
|
||||
nivelNovoPerfil = role.nivel;
|
||||
modalGerenciarPerfisAberto = true;
|
||||
}
|
||||
|
||||
function fecharModalGerenciarPerfis() {
|
||||
modalGerenciarPerfisAberto = false;
|
||||
perfilSendoEditado = null;
|
||||
}
|
||||
|
||||
async function criarNovoPerfil() {
|
||||
if (!nomeNovoPerfil.trim()) return;
|
||||
|
||||
processando = true;
|
||||
try {
|
||||
const result = await client.mutation(api.roles.criar, {
|
||||
nome: nomeNovoPerfil.trim(),
|
||||
descricao: descricaoNovoPerfil.trim(),
|
||||
nivel: nivelNovoPerfil,
|
||||
customizado: true,
|
||||
});
|
||||
|
||||
if (result.sucesso) {
|
||||
mostrarMensagem("success", "Perfil criado com sucesso!");
|
||||
nomeNovoPerfil = "";
|
||||
descricaoNovoPerfil = "";
|
||||
nivelNovoPerfil = 3;
|
||||
fecharModalGerenciarPerfis();
|
||||
if (rolesQuery.refetch) { // Verificação para garantir que refetch existe
|
||||
rolesQuery.refetch(); // Atualiza a lista de perfis
|
||||
}
|
||||
} else {
|
||||
mostrarMensagem("error", `Erro ao criar perfil: ${result.erro}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
mostrarMensagem("error", `Erro ao criar perfil: ${message}`);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function editarPerfil() {
|
||||
if (!perfilSendoEditado || !nomeNovoPerfil.trim()) return;
|
||||
|
||||
processando = true;
|
||||
try {
|
||||
const result = await client.mutation(api.roles.atualizar, {
|
||||
roleId: perfilSendoEditado._id,
|
||||
nome: nomeNovoPerfil.trim(),
|
||||
descricao: descricaoNovoPerfil.trim(),
|
||||
nivel: nivelNovoPerfil,
|
||||
setor: perfilSendoEditado.setor, // Manter setor existente
|
||||
});
|
||||
|
||||
if (result.sucesso) {
|
||||
mostrarMensagem("success", "Perfil atualizado com sucesso!");
|
||||
fecharModalGerenciarPerfis();
|
||||
if (rolesQuery.refetch) { // Verificação para garantir que refetch existe
|
||||
rolesQuery.refetch(); // Atualiza a lista de perfis
|
||||
}
|
||||
} else {
|
||||
mostrarMensagem("error", `Erro ao atualizar perfil: ${result.erro}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
mostrarMensagem("error", `Erro ao atualizar perfil: ${message}`);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<ProtectedRoute allowedRoles={["ti_master", "admin"]} maxLevel={1}>
|
||||
@@ -160,7 +254,7 @@
|
||||
<li>
|
||||
<a href="/ti" class="text-primary hover:text-primary-focus">TI</a>
|
||||
</li>
|
||||
<li class="font-semibold">Gerenciar Permissões</li>
|
||||
<li class="font-semibold">Gerenciar Perfis & Permissões</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -185,12 +279,32 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Gerenciar Permissões de Acesso
|
||||
Gerenciar Perfis & Permissões de Acesso
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Configure as permissões de acesso aos menus do sistema por função
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary gap-2"
|
||||
onclick={abrirModalCriarPerfil}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Criar Novo Perfil
|
||||
</button>
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto("/ti")}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -479,6 +593,15 @@
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm"
|
||||
onclick={() => prepararEdicaoPerfil(roleRow)}
|
||||
>
|
||||
Editar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if roleRow.nivel <= 1}
|
||||
@@ -574,4 +697,148 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<!-- Modal Gerenciar Perfis -->
|
||||
{#if modalGerenciarPerfisAberto}
|
||||
<dialog class="modal modal-open">
|
||||
<div
|
||||
class="modal-box max-w-4xl w-full overflow-hidden border border-base-200/60 bg-base-200/40 p-0 shadow-2xl"
|
||||
>
|
||||
<div
|
||||
class="relative bg-gradient-to-r from-primary via-primary/90 to-secondary/80 px-8 py-6 text-base-100"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-4 top-4 text-base-100/80 hover:text-base-100"
|
||||
onclick={fecharModalGerenciarPerfis}
|
||||
aria-label="Fechar modal"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
<h3 class="text-3xl font-black tracking-tight">Gerenciar Perfis de Acesso</h3>
|
||||
<p class="mt-2 max-w-2xl text-sm text-base-100/80 md:text-base">
|
||||
{perfilSendoEditado
|
||||
? "Atualize as informações do perfil selecionado para manter a governança de acesso alinhada com as diretrizes do sistema."
|
||||
: "Crie um novo perfil de acesso definindo nome, descrição e nível hierárquico conforme os padrões adotados pela Secretaria."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-100 px-8 py-6">
|
||||
<section
|
||||
class="space-y-6 rounded-2xl border border-base-200/60 bg-base-100 p-6 shadow-sm"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-xl font-semibold text-base-content">
|
||||
{perfilSendoEditado ? "Editar Perfil" : "Criar Novo Perfil"}
|
||||
</h4>
|
||||
<p class="text-sm text-base-content/70">
|
||||
{perfilSendoEditado
|
||||
? "Os campos bloqueados indicam atributos padronizados do sistema. Ajuste apenas o que estiver disponível."
|
||||
: "Preencha as informações com atenção para garantir que o novo perfil siga o padrão institucional."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if perfilSendoEditado}
|
||||
<span
|
||||
class="badge badge-outline badge-primary badge-lg self-start flex flex-col items-center gap-1 px-5 py-3 text-center"
|
||||
>
|
||||
<span class="text-[11px] font-semibold uppercase tracking-[0.32em] text-primary">
|
||||
Nível atual
|
||||
</span>
|
||||
<span class="text-2xl font-bold leading-none text-primary">
|
||||
{perfilSendoEditado.nivel}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label" for="nome-perfil-input">
|
||||
<span class="label-text">Nome do Perfil *</span>
|
||||
</label>
|
||||
<input
|
||||
id="nome-perfil-input"
|
||||
type="text"
|
||||
bind:value={nomeNovoPerfil}
|
||||
class="input input-bordered input-primary"
|
||||
placeholder="Ex: RH, Financeiro, Gestor"
|
||||
disabled={perfilSendoEditado !== null && !perfilSendoEditado.customizado}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label" for="descricao-perfil-input">
|
||||
<span class="label-text">Descrição</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="descricao-perfil-input"
|
||||
bind:value={descricaoNovoPerfil}
|
||||
class="textarea textarea-bordered textarea-primary min-h-[120px]"
|
||||
placeholder="Breve descrição das responsabilidades e limites de atuação deste perfil."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control max-w-xs">
|
||||
<label class="label" for="nivel-perfil-input">
|
||||
<span class="label-text">Nível de Acesso (0-5) *</span>
|
||||
</label>
|
||||
<input
|
||||
id="nivel-perfil-input"
|
||||
type="number"
|
||||
bind:value={nivelNovoPerfil}
|
||||
min="0"
|
||||
max="5"
|
||||
class="input input-bordered input-secondary"
|
||||
disabled={perfilSendoEditado !== null && !perfilSendoEditado.customizado}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div
|
||||
class="mt-8 flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<p class="text-sm text-base-content/60">
|
||||
Campos marcados com * são obrigatórios.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost"
|
||||
onclick={fecharModalGerenciarPerfis}
|
||||
disabled={processando}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
disabled={!nomeNovoPerfil.trim() || processando}
|
||||
onclick={perfilSendoEditado ? editarPerfil : criarNovoPerfil}
|
||||
>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner"></span>
|
||||
Processando...
|
||||
{:else}
|
||||
{perfilSendoEditado ? "Salvar Alterações" : "Criar Perfil"}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button
|
||||
type="button"
|
||||
onclick={fecharModalGerenciarPerfis}
|
||||
aria-label="Fechar modal"
|
||||
>Fechar</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
</ProtectedRoute>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,82 @@
|
||||
import { useConvexClient, useQuery } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
|
||||
// Tipos baseados nos retornos das queries do backend
|
||||
type Usuario = {
|
||||
_id: Id<"usuarios">;
|
||||
matricula: string;
|
||||
nome: string;
|
||||
email: string;
|
||||
ativo: boolean;
|
||||
bloqueado?: boolean;
|
||||
motivoBloqueio?: string;
|
||||
primeiroAcesso: boolean;
|
||||
ultimoAcesso?: number;
|
||||
criadoEm: number;
|
||||
role: {
|
||||
_id: Id<"roles">;
|
||||
_creationTime?: number;
|
||||
criadoPor?: Id<"usuarios">;
|
||||
customizado?: boolean;
|
||||
descricao: string;
|
||||
editavel?: boolean;
|
||||
nome: string;
|
||||
nivel: number;
|
||||
setor?: string;
|
||||
erro?: boolean;
|
||||
};
|
||||
funcionario?: {
|
||||
_id: Id<"funcionarios">;
|
||||
nome: string;
|
||||
matricula?: string;
|
||||
descricaoCargo?: string;
|
||||
simboloTipo: "cargo_comissionado" | "funcao_gratificada";
|
||||
};
|
||||
avisos?: Array<{
|
||||
tipo: "erro" | "aviso" | "info";
|
||||
mensagem: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type Funcionario = {
|
||||
_id: Id<"funcionarios">;
|
||||
nome: string;
|
||||
matricula?: string;
|
||||
cpf?: string;
|
||||
rg?: string;
|
||||
nascimento?: string;
|
||||
email?: string;
|
||||
telefone?: string;
|
||||
endereco?: string;
|
||||
cep?: string;
|
||||
cidade?: string;
|
||||
uf?: string;
|
||||
simboloId: Id<"simbolos">;
|
||||
simboloTipo: "cargo_comissionado" | "funcao_gratificada";
|
||||
admissaoData?: string;
|
||||
desligamentoData?: string;
|
||||
descricaoCargo?: string;
|
||||
};
|
||||
|
||||
type Gestor = Doc<"usuarios"> | null;
|
||||
|
||||
type TimeComDetalhes = Doc<"times"> & {
|
||||
gestor: Gestor;
|
||||
totalMembros: number;
|
||||
};
|
||||
|
||||
type MembroTime = Doc<"timesMembros"> & {
|
||||
funcionario: Doc<"funcionarios"> | null;
|
||||
};
|
||||
|
||||
type TimeComMembros = Doc<"times"> & {
|
||||
gestor: Gestor;
|
||||
membros: MembroTime[];
|
||||
};
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
@@ -11,17 +86,23 @@
|
||||
const usuariosQuery = useQuery(api.usuarios.listar, {});
|
||||
const funcionariosQuery = useQuery(api.funcionarios.getAll, {});
|
||||
|
||||
const times = $derived(timesQuery?.data || []);
|
||||
const usuarios = $derived(usuariosQuery?.data || []);
|
||||
const funcionarios = $derived(funcionariosQuery?.data || []);
|
||||
const times = $derived((timesQuery?.data || []) as TimeComDetalhes[]);
|
||||
const usuarios = $derived((usuariosQuery?.data || []) as Usuario[]);
|
||||
const funcionarios = $derived((funcionariosQuery?.data || []) as Funcionario[]);
|
||||
|
||||
const carregando = $derived(
|
||||
timesQuery === undefined ||
|
||||
usuariosQuery === undefined ||
|
||||
funcionariosQuery === undefined
|
||||
);
|
||||
|
||||
// Estados
|
||||
let modoEdicao = $state(false);
|
||||
let timeEmEdicao = $state<any>(null);
|
||||
let timeEmEdicao = $state<TimeComDetalhes | null>(null);
|
||||
let mostrarModalMembros = $state(false);
|
||||
let timeParaMembros = $state<any>(null);
|
||||
let timeParaMembros = $state<TimeComMembros | null>(null);
|
||||
let mostrarConfirmacaoExclusao = $state(false);
|
||||
let timeParaExcluir = $state<any>(null);
|
||||
let timeParaExcluir = $state<TimeComDetalhes | null>(null);
|
||||
let processando = $state(false);
|
||||
|
||||
// Form
|
||||
@@ -32,9 +113,9 @@
|
||||
|
||||
// Membros
|
||||
let membrosDisponiveis = $derived(
|
||||
funcionarios.filter((f: any) => {
|
||||
funcionarios.filter((f: Funcionario) => {
|
||||
// Verificar se o funcionário já está em algum time ativo
|
||||
const jaNaEquipe = timeParaMembros?.membros?.some((m: any) => m.funcionario?._id === f._id);
|
||||
const jaNaEquipe = timeParaMembros?.membros?.some((m: MembroTime) => m.funcionario?._id === f._id);
|
||||
return !jaNaEquipe;
|
||||
})
|
||||
);
|
||||
@@ -60,7 +141,7 @@
|
||||
formCor = coresDisponiveis[Math.floor(Math.random() * coresDisponiveis.length)];
|
||||
}
|
||||
|
||||
function editarTime(time: any) {
|
||||
function editarTime(time: TimeComDetalhes) {
|
||||
modoEdicao = true;
|
||||
timeEmEdicao = time;
|
||||
formNome = time.nome;
|
||||
@@ -91,26 +172,27 @@
|
||||
id: timeEmEdicao._id,
|
||||
nome: formNome,
|
||||
descricao: formDescricao || undefined,
|
||||
gestorId: formGestorId as any,
|
||||
gestorId: formGestorId as Id<"usuarios">,
|
||||
cor: formCor,
|
||||
});
|
||||
} else {
|
||||
await client.mutation(api.times.criar, {
|
||||
nome: formNome,
|
||||
descricao: formDescricao || undefined,
|
||||
gestorId: formGestorId as any,
|
||||
gestorId: formGestorId as Id<"usuarios">,
|
||||
cor: formCor,
|
||||
});
|
||||
}
|
||||
cancelarEdicao();
|
||||
} catch (e: any) {
|
||||
alert("Erro ao salvar: " + (e.message || e));
|
||||
} catch (e: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
alert("Erro ao salvar: " + errorMessage);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
function confirmarExclusao(time: any) {
|
||||
function confirmarExclusao(time: TimeComDetalhes) {
|
||||
timeParaExcluir = time;
|
||||
mostrarConfirmacaoExclusao = true;
|
||||
}
|
||||
@@ -123,17 +205,20 @@
|
||||
await client.mutation(api.times.desativar, { id: timeParaExcluir._id });
|
||||
mostrarConfirmacaoExclusao = false;
|
||||
timeParaExcluir = null;
|
||||
} catch (e: any) {
|
||||
alert("Erro ao excluir: " + (e.message || e));
|
||||
} catch (e: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
alert("Erro ao excluir: " + errorMessage);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function abrirGerenciarMembros(time: any) {
|
||||
async function abrirGerenciarMembros(time: TimeComDetalhes) {
|
||||
const detalhes = await client.query(api.times.obterPorId, { id: time._id });
|
||||
timeParaMembros = detalhes;
|
||||
mostrarModalMembros = true;
|
||||
if (detalhes) {
|
||||
timeParaMembros = detalhes as TimeComMembros;
|
||||
mostrarModalMembros = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function adicionarMembro(funcionarioId: string) {
|
||||
@@ -143,14 +228,17 @@
|
||||
try {
|
||||
await client.mutation(api.times.adicionarMembro, {
|
||||
timeId: timeParaMembros._id,
|
||||
funcionarioId: funcionarioId as any,
|
||||
funcionarioId: funcionarioId as Id<"funcionarios">,
|
||||
});
|
||||
|
||||
// Recarregar detalhes do time
|
||||
const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id });
|
||||
timeParaMembros = detalhes;
|
||||
} catch (e: any) {
|
||||
alert("Erro: " + (e.message || e));
|
||||
if (detalhes) {
|
||||
timeParaMembros = detalhes as TimeComMembros;
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
alert("Erro: " + errorMessage);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
@@ -161,13 +249,18 @@
|
||||
|
||||
processando = true;
|
||||
try {
|
||||
await client.mutation(api.times.removerMembro, { membroId: membroId as any });
|
||||
await client.mutation(api.times.removerMembro, { membroId: membroId as Id<"timesMembros"> });
|
||||
|
||||
// Recarregar detalhes do time
|
||||
const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id });
|
||||
timeParaMembros = detalhes;
|
||||
} catch (e: any) {
|
||||
alert("Erro: " + (e.message || e));
|
||||
if (timeParaMembros) {
|
||||
const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id });
|
||||
if (detalhes) {
|
||||
timeParaMembros = detalhes as TimeComMembros;
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
alert("Erro: " + errorMessage);
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
@@ -179,7 +272,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-6 max-w-7xl">
|
||||
<ProtectedRoute allowedRoles={["ti_master", "admin", "ti_usuario"]} maxLevel={2}>
|
||||
<main class="container mx-auto px-4 py-6 max-w-7xl">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<ul>
|
||||
@@ -192,14 +286,14 @@
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 bg-blue-500/20 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div class="p-3 bg-secondary/10 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-primary">Gestão de Times</h1>
|
||||
<p class="text-base-content/70">Organize funcionários em equipes e defina gestores</p>
|
||||
<h1 class="text-3xl font-bold text-base-content">Gestão de Times</h1>
|
||||
<p class="text-base-content/60 mt-1">Organize funcionários em equipes e defina gestores</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -297,20 +391,27 @@
|
||||
{/if}
|
||||
|
||||
<!-- Lista de Times -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each times as time}
|
||||
{#if time.ativo}
|
||||
<div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all border-l-4" style="border-color: {time.cor}">
|
||||
{#if carregando}
|
||||
<div class="flex justify-center items-center py-20">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each times.filter((t: TimeComDetalhes) => t.ativo) as time}
|
||||
<div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all border-l-4" style="border-color: {time.cor || '#3B82F6'}">
|
||||
<div class="card-body">
|
||||
<div class="flex items-start justify-between">
|
||||
<h2 class="card-title text-lg">{time.nome}</h2>
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full" style="background-color: {time.cor || '#3B82F6'}"></div>
|
||||
<h2 class="card-title text-lg">{time.nome}</h2>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<button tabindex="0" class="btn btn-ghost btn-sm btn-square" aria-label="Menu do time">
|
||||
<button type="button" class="btn btn-ghost btn-sm btn-square" aria-label="Menu do time">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-base-300">
|
||||
<ul role="menu" class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-100 rounded-box w-52 border border-base-300">
|
||||
<li>
|
||||
<button type="button" onclick={() => editarTime(time)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -339,7 +440,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-base-content/70 mb-3">{time.descricao || "Sem descrição"}</p>
|
||||
<p class="text-sm text-base-content/70 mb-3 min-h-[2rem]">{time.descricao || "Sem descrição"}</p>
|
||||
|
||||
<div class="divider my-2"></div>
|
||||
|
||||
@@ -348,31 +449,38 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span><strong>Gestor:</strong> {time.gestor?.nome}</span>
|
||||
<span class="text-base-content/70"><strong>Gestor:</strong> {time.gestor?.nome || "Não definido"}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span><strong>Membros:</strong> {time.totalMembros || 0}</span>
|
||||
<span class="text-base-content/70"><strong>Membros:</strong> <span class="badge badge-primary badge-sm">{time.totalMembros || 0}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button class="btn btn-sm btn-outline btn-primary" onclick={() => abrirGerenciarMembros(time)}>
|
||||
Ver Detalhes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
|
||||
{#if times.filter((t: any) => t.ativo).length === 0}
|
||||
<div class="col-span-full">
|
||||
<div class="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info shrink-0 w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>Nenhum time cadastrado. Clique em "Novo Time" para começar.</span>
|
||||
{#if times.filter((t: TimeComDetalhes) => t.ativo).length === 0}
|
||||
<div class="col-span-full">
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-base-content/30" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
<h3 class="text-xl font-semibold mt-4">Nenhum time cadastrado</h3>
|
||||
<p class="text-base-content/60 mt-2">Clique em "Novo Time" para criar seu primeiro time</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Gerenciar Membros -->
|
||||
{#if mostrarModalMembros && timeParaMembros}
|
||||
@@ -502,4 +610,5 @@
|
||||
</dialog>
|
||||
{/if}
|
||||
</main>
|
||||
</ProtectedRoute>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3
apps/web/src/routes/api/auth/[...all]/+server.ts
Normal file
3
apps/web/src/routes/api/auth/[...all]/+server.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createSvelteKitHandler } from "@mmailaender/convex-better-auth-svelte/sveltekit";
|
||||
|
||||
export const { GET, POST } = createSvelteKitHandler();
|
||||
33
convex/_generated/api.d.ts
vendored
33
convex/_generated/api.d.ts
vendored
@@ -1,33 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `api` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
FilterApi,
|
||||
FunctionReference,
|
||||
} from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = api.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
declare const fullApi: ApiFromModules<{}>;
|
||||
export declare const api: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, "public">
|
||||
>;
|
||||
export declare const internal: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, "internal">
|
||||
>;
|
||||
@@ -1,22 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `api` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { anyApi } from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = api.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
export const api = anyApi;
|
||||
export const internal = anyApi;
|
||||
58
convex/_generated/dataModel.d.ts
vendored
58
convex/_generated/dataModel.d.ts
vendored
@@ -1,58 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated data model types.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { AnyDataModel } from "convex/server";
|
||||
import type { GenericId } from "convex/values";
|
||||
|
||||
/**
|
||||
* No `schema.ts` file found!
|
||||
*
|
||||
* This generated code has permissive types like `Doc = any` because
|
||||
* Convex doesn't know your schema. If you'd like more type safety, see
|
||||
* https://docs.convex.dev/using/schemas for instructions on how to add a
|
||||
* schema file.
|
||||
*
|
||||
* After you change a schema, rerun codegen with `npx convex dev`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The names of all of your Convex tables.
|
||||
*/
|
||||
export type TableNames = string;
|
||||
|
||||
/**
|
||||
* The type of a document stored in Convex.
|
||||
*/
|
||||
export type Doc = any;
|
||||
|
||||
/**
|
||||
* An identifier for a document in Convex.
|
||||
*
|
||||
* Convex documents are uniquely identified by their `Id`, which is accessible
|
||||
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
||||
*
|
||||
* Documents can be loaded using `db.get(id)` in query and mutation functions.
|
||||
*
|
||||
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
||||
* strings when type checking.
|
||||
*/
|
||||
export type Id<TableName extends TableNames = TableNames> =
|
||||
GenericId<TableName>;
|
||||
|
||||
/**
|
||||
* A type describing your Convex data model.
|
||||
*
|
||||
* This type includes information about what tables you have, the type of
|
||||
* documents stored in those tables, and the indexes defined on them.
|
||||
*
|
||||
* This type is used to parameterize methods like `queryGeneric` and
|
||||
* `mutationGeneric` to make them type-safe.
|
||||
*/
|
||||
export type DataModel = AnyDataModel;
|
||||
142
convex/_generated/server.d.ts
vendored
142
convex/_generated/server.d.ts
vendored
@@ -1,142 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionBuilder,
|
||||
HttpActionBuilder,
|
||||
MutationBuilder,
|
||||
QueryBuilder,
|
||||
GenericActionCtx,
|
||||
GenericMutationCtx,
|
||||
GenericQueryCtx,
|
||||
GenericDatabaseReader,
|
||||
GenericDatabaseWriter,
|
||||
} from "convex/server";
|
||||
import type { DataModel } from "./dataModel.js";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const query: QueryBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const mutation: MutationBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
*
|
||||
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||
* code and code with side-effects, like calling third-party services.
|
||||
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||
*
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const action: ActionBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an HTTP action.
|
||||
*
|
||||
* This function will be used to respond to HTTP requests received by a Convex
|
||||
* deployment if the requests matches the path and method where this action
|
||||
* is routed. Be sure to route your action in `convex/http.js`.
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
||||
*/
|
||||
export declare const httpAction: HttpActionBuilder;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex query functions.
|
||||
*
|
||||
* The query context is passed as the first argument to any Convex query
|
||||
* function run on the server.
|
||||
*
|
||||
* This differs from the {@link MutationCtx} because all of the services are
|
||||
* read-only.
|
||||
*/
|
||||
export type QueryCtx = GenericQueryCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex mutation functions.
|
||||
*
|
||||
* The mutation context is passed as the first argument to any Convex mutation
|
||||
* function run on the server.
|
||||
*/
|
||||
export type MutationCtx = GenericMutationCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex action functions.
|
||||
*
|
||||
* The action context is passed as the first argument to any Convex action
|
||||
* function run on the server.
|
||||
*/
|
||||
export type ActionCtx = GenericActionCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from the database within Convex query functions.
|
||||
*
|
||||
* The two entry points are {@link DatabaseReader.get}, which fetches a single
|
||||
* document by its {@link Id}, or {@link DatabaseReader.query}, which starts
|
||||
* building a query.
|
||||
*/
|
||||
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from and write to the database within Convex mutation
|
||||
* functions.
|
||||
*
|
||||
* Convex guarantees that all writes within a single mutation are
|
||||
* executed atomically, so you never have to worry about partial writes leaving
|
||||
* your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
|
||||
* for the guarantees Convex provides your functions.
|
||||
*/
|
||||
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
||||
@@ -1,89 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
actionGeneric,
|
||||
httpActionGeneric,
|
||||
queryGeneric,
|
||||
mutationGeneric,
|
||||
internalActionGeneric,
|
||||
internalMutationGeneric,
|
||||
internalQueryGeneric,
|
||||
} from "convex/server";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const query = queryGeneric;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalQuery = internalQueryGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const mutation = mutationGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalMutation = internalMutationGeneric;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
*
|
||||
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||
* code and code with side-effects, like calling third-party services.
|
||||
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||
*
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const action = actionGeneric;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalAction = internalActionGeneric;
|
||||
|
||||
/**
|
||||
* Define a Convex HTTP action.
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
|
||||
* as its second.
|
||||
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
|
||||
*/
|
||||
export const httpAction = httpActionGeneric;
|
||||
@@ -1,121 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo.
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo 🔐 CRIAR ARQUIVO .env - SGSE (Convex Local)
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo.
|
||||
|
||||
echo [1/4] Verificando se .env já existe...
|
||||
|
||||
if exist .env (
|
||||
echo.
|
||||
echo ⚠️ ATENÇÃO: Arquivo .env já existe!
|
||||
echo.
|
||||
echo Deseja sobrescrever? (S/N^)
|
||||
set /p resposta="> "
|
||||
|
||||
if /i not "%resposta%"=="S" (
|
||||
echo.
|
||||
echo ❌ Operação cancelada. Arquivo .env mantido.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/4] Criando arquivo .env...
|
||||
|
||||
(
|
||||
echo # ══════════════════════════════════════════════════════════
|
||||
echo # CONFIGURAÇÃO DE AMBIENTE - SGSE
|
||||
echo # Gerado automaticamente em: %date% %time%
|
||||
echo # ══════════════════════════════════════════════════════════
|
||||
echo.
|
||||
echo # Segurança Better Auth
|
||||
echo # Secret para criptografia de tokens de autenticação
|
||||
echo BETTER_AUTH_SECRET=+Nfg4jTxPv1giF5MlmyYTxpU/VkS3QaDOvgSWd+QmbY=
|
||||
echo.
|
||||
echo # URL da aplicação
|
||||
echo # Desenvolvimento: http://localhost:5173
|
||||
echo # Produção: https://sgse.pe.gov.br ^(alterar quando for para produção^)
|
||||
echo SITE_URL=http://localhost:5173
|
||||
echo.
|
||||
echo # ══════════════════════════════════════════════════════════
|
||||
echo # IMPORTANTE - SEGURANÇA
|
||||
echo # ══════════════════════════════════════════════════════════
|
||||
echo # 1. Este arquivo NÃO deve ser commitado no Git
|
||||
echo # 2. Antes de ir para produção, gere um NOVO secret
|
||||
echo # 3. Em produção, altere SITE_URL para a URL real
|
||||
echo # ══════════════════════════════════════════════════════════
|
||||
) > .env
|
||||
|
||||
if not exist .env (
|
||||
echo.
|
||||
echo ❌ ERRO: Falha ao criar arquivo .env
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ✅ Arquivo .env criado com sucesso!
|
||||
|
||||
echo.
|
||||
echo [3/4] Verificando .gitignore...
|
||||
|
||||
if not exist .gitignore (
|
||||
echo # Arquivos de ambiente > .gitignore
|
||||
echo .env >> .gitignore
|
||||
echo .env.local >> .gitignore
|
||||
echo .env.*.local >> .gitignore
|
||||
echo ✅ .gitignore criado
|
||||
) else (
|
||||
findstr /C:".env" .gitignore >nul
|
||||
if errorlevel 1 (
|
||||
echo .env >> .gitignore
|
||||
echo .env.local >> .gitignore
|
||||
echo .env.*.local >> .gitignore
|
||||
echo ✅ .env adicionado ao .gitignore
|
||||
) else (
|
||||
echo ✅ .env já está no .gitignore
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [4/4] Resumo da configuração:
|
||||
echo.
|
||||
echo ┌─────────────────────────────────────────────────────────┐
|
||||
echo │ ✅ Arquivo criado: packages/backend/.env │
|
||||
echo │ │
|
||||
echo │ Variáveis configuradas: │
|
||||
echo │ • BETTER_AUTH_SECRET: Configurado │
|
||||
echo │ • SITE_URL: http://localhost:5173 │
|
||||
echo └─────────────────────────────────────────────────────────┘
|
||||
echo.
|
||||
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo 📋 PRÓXIMOS PASSOS
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo.
|
||||
echo 1. Reinicie o servidor Convex:
|
||||
echo ^> cd packages\backend
|
||||
echo ^> bunx convex dev
|
||||
echo.
|
||||
echo 2. Reinicie o servidor Web (em outro terminal^):
|
||||
echo ^> cd apps\web
|
||||
echo ^> bun run dev
|
||||
echo.
|
||||
echo 3. Verifique que as mensagens de erro pararam
|
||||
echo.
|
||||
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo ⚠️ LEMBRE-SE
|
||||
echo ═══════════════════════════════════════════════════════════
|
||||
echo.
|
||||
echo • NÃO commite o arquivo .env no Git
|
||||
echo • Gere um NOVO secret antes de ir para produção
|
||||
echo • Altere SITE_URL quando for para produção
|
||||
echo.
|
||||
|
||||
pause
|
||||
|
||||
20
packages/backend/convex/_generated/api.d.ts
vendored
20
packages/backend/convex/_generated/api.d.ts
vendored
@@ -8,14 +8,12 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type * as actions_email from "../actions/email.js";
|
||||
import type * as actions_smtp from "../actions/smtp.js";
|
||||
import type * as autenticacao from "../autenticacao.js";
|
||||
import type * as auth_utils from "../auth/utils.js";
|
||||
import type * as betterAuth__generated_api from "../betterAuth/_generated/api.js";
|
||||
import type * as betterAuth__generated_server from "../betterAuth/_generated/server.js";
|
||||
import type * as chat from "../chat.js";
|
||||
import type * as configuracaoEmail from "../configuracaoEmail.js";
|
||||
import type * as criarFuncionarioTeste from "../criarFuncionarioTeste.js";
|
||||
import type * as criarUsuarioTeste from "../criarUsuarioTeste.js";
|
||||
import type * as crons from "../crons.js";
|
||||
import type * as cursos from "../cursos.js";
|
||||
import type * as dashboard from "../dashboard.js";
|
||||
@@ -29,11 +27,7 @@ import type * as limparPerfisAntigos from "../limparPerfisAntigos.js";
|
||||
import type * as logsAcesso from "../logsAcesso.js";
|
||||
import type * as logsAtividades from "../logsAtividades.js";
|
||||
import type * as logsLogin from "../logsLogin.js";
|
||||
import type * as menuPermissoes from "../menuPermissoes.js";
|
||||
import type * as migrarParaTimes from "../migrarParaTimes.js";
|
||||
import type * as migrarUsuariosAdmin from "../migrarUsuariosAdmin.js";
|
||||
import type * as monitoramento from "../monitoramento.js";
|
||||
import type * as perfisCustomizados from "../perfisCustomizados.js";
|
||||
import type * as permissoesAcoes from "../permissoesAcoes.js";
|
||||
import type * as roles from "../roles.js";
|
||||
import type * as saldoFerias from "../saldoFerias.js";
|
||||
@@ -61,14 +55,12 @@ import type {
|
||||
* ```
|
||||
*/
|
||||
declare const fullApi: ApiFromModules<{
|
||||
"actions/email": typeof actions_email;
|
||||
"actions/smtp": typeof actions_smtp;
|
||||
autenticacao: typeof autenticacao;
|
||||
"auth/utils": typeof auth_utils;
|
||||
"betterAuth/_generated/api": typeof betterAuth__generated_api;
|
||||
"betterAuth/_generated/server": typeof betterAuth__generated_server;
|
||||
chat: typeof chat;
|
||||
configuracaoEmail: typeof configuracaoEmail;
|
||||
criarFuncionarioTeste: typeof criarFuncionarioTeste;
|
||||
criarUsuarioTeste: typeof criarUsuarioTeste;
|
||||
crons: typeof crons;
|
||||
cursos: typeof cursos;
|
||||
dashboard: typeof dashboard;
|
||||
@@ -82,11 +74,7 @@ declare const fullApi: ApiFromModules<{
|
||||
logsAcesso: typeof logsAcesso;
|
||||
logsAtividades: typeof logsAtividades;
|
||||
logsLogin: typeof logsLogin;
|
||||
menuPermissoes: typeof menuPermissoes;
|
||||
migrarParaTimes: typeof migrarParaTimes;
|
||||
migrarUsuariosAdmin: typeof migrarUsuariosAdmin;
|
||||
monitoramento: typeof monitoramento;
|
||||
perfisCustomizados: typeof perfisCustomizados;
|
||||
permissoesAcoes: typeof permissoesAcoes;
|
||||
roles: typeof roles;
|
||||
saldoFerias: typeof saldoFerias;
|
||||
|
||||
99
packages/backend/convex/actions/email.ts
Normal file
99
packages/backend/convex/actions/email.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
"use node";
|
||||
|
||||
import { action } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { internal } from "../_generated/api";
|
||||
|
||||
export const enviar = action({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
||||
handler: async (ctx, args) => {
|
||||
"use node";
|
||||
const nodemailer = await import("nodemailer");
|
||||
|
||||
try {
|
||||
// Buscar email da fila
|
||||
const email = await ctx.runQuery(internal.email.getEmailById, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
return { sucesso: false, erro: "Email não encontrado" };
|
||||
}
|
||||
|
||||
// Buscar configuração SMTP ativa
|
||||
const config = await ctx.runQuery(internal.email.getActiveEmailConfig, {});
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
sucesso: false,
|
||||
erro: "Configuração de email não encontrada ou inativa",
|
||||
};
|
||||
}
|
||||
|
||||
if (!config.testadoEm) {
|
||||
return {
|
||||
sucesso: false,
|
||||
erro: "Configuração SMTP não foi testada. Teste a conexão primeiro!",
|
||||
};
|
||||
}
|
||||
|
||||
// Marcar como enviando
|
||||
await ctx.runMutation(internal.email.markEmailEnviando, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
// Criar transporter do nodemailer
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: config.servidor,
|
||||
port: config.porta,
|
||||
secure: config.usarSSL,
|
||||
auth: {
|
||||
user: config.usuario,
|
||||
// Em produção deve ser armazenado com criptografia reversível
|
||||
pass: config.senhaHash,
|
||||
},
|
||||
tls: {
|
||||
// Permitir certificados autoassinados
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Enviar email
|
||||
const info = await transporter.sendMail({
|
||||
from: `"${config.nomeRemetente}" <${config.emailRemetente}>`,
|
||||
to: email.destinatario,
|
||||
subject: email.assunto,
|
||||
html: email.corpo,
|
||||
});
|
||||
|
||||
console.log("✅ Email enviado com sucesso!", {
|
||||
para: email.destinatario,
|
||||
assunto: email.assunto,
|
||||
messageId: (info as { messageId?: string }).messageId,
|
||||
});
|
||||
|
||||
// Marcar como enviado
|
||||
await ctx.runMutation(internal.email.markEmailEnviado, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
return { sucesso: true };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error("❌ Erro ao enviar email:", errorMessage);
|
||||
|
||||
// Marcar como falha
|
||||
await ctx.runMutation(internal.email.markEmailFalha, {
|
||||
emailId: args.emailId,
|
||||
erro: errorMessage,
|
||||
});
|
||||
|
||||
return { sucesso: false, erro: errorMessage };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
60
packages/backend/convex/actions/smtp.ts
Normal file
60
packages/backend/convex/actions/smtp.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
"use node";
|
||||
|
||||
import { action } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export const testarConexao = action({
|
||||
args: {
|
||||
servidor: v.string(),
|
||||
porta: v.number(),
|
||||
usuario: v.string(),
|
||||
senha: v.string(),
|
||||
usarSSL: v.boolean(),
|
||||
usarTLS: v.boolean(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
"use node";
|
||||
const nodemailer = await import("nodemailer");
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
if (!args.servidor || args.servidor.trim() === "") {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Servidor SMTP não pode estar vazio",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.porta < 1 || args.porta > 65535) {
|
||||
return { sucesso: false as const, erro: "Porta inválida" };
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: args.servidor,
|
||||
port: args.porta,
|
||||
secure: args.usarSSL,
|
||||
auth: {
|
||||
user: args.usuario,
|
||||
pass: args.senha,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: !args.usarTLS ? false : false,
|
||||
},
|
||||
});
|
||||
|
||||
// Verificar conexão
|
||||
await transporter.verify();
|
||||
|
||||
return { sucesso: true as const };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { sucesso: false as const, erro: errorMessage };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
providers: [
|
||||
{
|
||||
domain: process.env.CONVEX_SITE_URL,
|
||||
applicationID: "convex",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,993 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `api` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type * as adapter from "../adapter.js";
|
||||
import type * as auth from "../auth.js";
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
FilterApi,
|
||||
FunctionReference,
|
||||
} from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = api.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
declare const fullApi: ApiFromModules<{
|
||||
adapter: typeof adapter;
|
||||
auth: typeof auth;
|
||||
}>;
|
||||
export type Mounts = {
|
||||
adapter: {
|
||||
create: FunctionReference<
|
||||
"mutation",
|
||||
"public",
|
||||
{
|
||||
input:
|
||||
| {
|
||||
data: {
|
||||
createdAt: number;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
image?: null | string;
|
||||
name: string;
|
||||
updatedAt: number;
|
||||
userId?: null | string;
|
||||
};
|
||||
model: "user";
|
||||
}
|
||||
| {
|
||||
data: {
|
||||
createdAt: number;
|
||||
expiresAt: number;
|
||||
ipAddress?: null | string;
|
||||
token: string;
|
||||
updatedAt: number;
|
||||
userAgent?: null | string;
|
||||
userId: string;
|
||||
};
|
||||
model: "session";
|
||||
}
|
||||
| {
|
||||
data: {
|
||||
accessToken?: null | string;
|
||||
accessTokenExpiresAt?: null | number;
|
||||
accountId: string;
|
||||
createdAt: number;
|
||||
idToken?: null | string;
|
||||
password?: null | string;
|
||||
providerId: string;
|
||||
refreshToken?: null | string;
|
||||
refreshTokenExpiresAt?: null | number;
|
||||
scope?: null | string;
|
||||
updatedAt: number;
|
||||
userId: string;
|
||||
};
|
||||
model: "account";
|
||||
}
|
||||
| {
|
||||
data: {
|
||||
createdAt: number;
|
||||
expiresAt: number;
|
||||
identifier: string;
|
||||
updatedAt: number;
|
||||
value: string;
|
||||
};
|
||||
model: "verification";
|
||||
}
|
||||
| {
|
||||
data: {
|
||||
createdAt: number;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
model: "jwks";
|
||||
};
|
||||
onCreateHandle?: string;
|
||||
select?: Array<string>;
|
||||
},
|
||||
any
|
||||
>;
|
||||
deleteMany: FunctionReference<
|
||||
"mutation",
|
||||
"public",
|
||||
{
|
||||
input:
|
||||
| {
|
||||
model: "user";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "name"
|
||||
| "email"
|
||||
| "emailVerified"
|
||||
| "image"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "session";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "expiresAt"
|
||||
| "token"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "ipAddress"
|
||||
| "userAgent"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "account";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "accountId"
|
||||
| "providerId"
|
||||
| "userId"
|
||||
| "accessToken"
|
||||
| "refreshToken"
|
||||
| "idToken"
|
||||
| "accessTokenExpiresAt"
|
||||
| "refreshTokenExpiresAt"
|
||||
| "scope"
|
||||
| "password"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "verification";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "identifier"
|
||||
| "value"
|
||||
| "expiresAt"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "jwks";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: "publicKey" | "privateKey" | "createdAt" | "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
};
|
||||
onDeleteHandle?: string;
|
||||
paginationOpts: {
|
||||
cursor: string | null;
|
||||
endCursor?: string | null;
|
||||
id?: number;
|
||||
maximumBytesRead?: number;
|
||||
maximumRowsRead?: number;
|
||||
numItems: number;
|
||||
};
|
||||
},
|
||||
any
|
||||
>;
|
||||
deleteOne: FunctionReference<
|
||||
"mutation",
|
||||
"public",
|
||||
{
|
||||
input:
|
||||
| {
|
||||
model: "user";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "name"
|
||||
| "email"
|
||||
| "emailVerified"
|
||||
| "image"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "session";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "expiresAt"
|
||||
| "token"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "ipAddress"
|
||||
| "userAgent"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "account";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "accountId"
|
||||
| "providerId"
|
||||
| "userId"
|
||||
| "accessToken"
|
||||
| "refreshToken"
|
||||
| "idToken"
|
||||
| "accessTokenExpiresAt"
|
||||
| "refreshTokenExpiresAt"
|
||||
| "scope"
|
||||
| "password"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "verification";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "identifier"
|
||||
| "value"
|
||||
| "expiresAt"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "jwks";
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: "publicKey" | "privateKey" | "createdAt" | "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
};
|
||||
onDeleteHandle?: string;
|
||||
},
|
||||
any
|
||||
>;
|
||||
findMany: FunctionReference<
|
||||
"query",
|
||||
"public",
|
||||
{
|
||||
limit?: number;
|
||||
model: "user" | "session" | "account" | "verification" | "jwks";
|
||||
offset?: number;
|
||||
paginationOpts: {
|
||||
cursor: string | null;
|
||||
endCursor?: string | null;
|
||||
id?: number;
|
||||
maximumBytesRead?: number;
|
||||
maximumRowsRead?: number;
|
||||
numItems: number;
|
||||
};
|
||||
sortBy?: { direction: "asc" | "desc"; field: string };
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: string;
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
},
|
||||
any
|
||||
>;
|
||||
findOne: FunctionReference<
|
||||
"query",
|
||||
"public",
|
||||
{
|
||||
model: "user" | "session" | "account" | "verification" | "jwks";
|
||||
select?: Array<string>;
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: string;
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
},
|
||||
any
|
||||
>;
|
||||
updateMany: FunctionReference<
|
||||
"mutation",
|
||||
"public",
|
||||
{
|
||||
input:
|
||||
| {
|
||||
model: "user";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
email?: string;
|
||||
emailVerified?: boolean;
|
||||
image?: null | string;
|
||||
name?: string;
|
||||
updatedAt?: number;
|
||||
userId?: null | string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "name"
|
||||
| "email"
|
||||
| "emailVerified"
|
||||
| "image"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "session";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
expiresAt?: number;
|
||||
ipAddress?: null | string;
|
||||
token?: string;
|
||||
updatedAt?: number;
|
||||
userAgent?: null | string;
|
||||
userId?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "expiresAt"
|
||||
| "token"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "ipAddress"
|
||||
| "userAgent"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "account";
|
||||
update: {
|
||||
accessToken?: null | string;
|
||||
accessTokenExpiresAt?: null | number;
|
||||
accountId?: string;
|
||||
createdAt?: number;
|
||||
idToken?: null | string;
|
||||
password?: null | string;
|
||||
providerId?: string;
|
||||
refreshToken?: null | string;
|
||||
refreshTokenExpiresAt?: null | number;
|
||||
scope?: null | string;
|
||||
updatedAt?: number;
|
||||
userId?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "accountId"
|
||||
| "providerId"
|
||||
| "userId"
|
||||
| "accessToken"
|
||||
| "refreshToken"
|
||||
| "idToken"
|
||||
| "accessTokenExpiresAt"
|
||||
| "refreshTokenExpiresAt"
|
||||
| "scope"
|
||||
| "password"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "verification";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
expiresAt?: number;
|
||||
identifier?: string;
|
||||
updatedAt?: number;
|
||||
value?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "identifier"
|
||||
| "value"
|
||||
| "expiresAt"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "jwks";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
privateKey?: string;
|
||||
publicKey?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: "publicKey" | "privateKey" | "createdAt" | "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
};
|
||||
onUpdateHandle?: string;
|
||||
paginationOpts: {
|
||||
cursor: string | null;
|
||||
endCursor?: string | null;
|
||||
id?: number;
|
||||
maximumBytesRead?: number;
|
||||
maximumRowsRead?: number;
|
||||
numItems: number;
|
||||
};
|
||||
},
|
||||
any
|
||||
>;
|
||||
updateOne: FunctionReference<
|
||||
"mutation",
|
||||
"public",
|
||||
{
|
||||
input:
|
||||
| {
|
||||
model: "user";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
email?: string;
|
||||
emailVerified?: boolean;
|
||||
image?: null | string;
|
||||
name?: string;
|
||||
updatedAt?: number;
|
||||
userId?: null | string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "name"
|
||||
| "email"
|
||||
| "emailVerified"
|
||||
| "image"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "session";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
expiresAt?: number;
|
||||
ipAddress?: null | string;
|
||||
token?: string;
|
||||
updatedAt?: number;
|
||||
userAgent?: null | string;
|
||||
userId?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "expiresAt"
|
||||
| "token"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "ipAddress"
|
||||
| "userAgent"
|
||||
| "userId"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "account";
|
||||
update: {
|
||||
accessToken?: null | string;
|
||||
accessTokenExpiresAt?: null | number;
|
||||
accountId?: string;
|
||||
createdAt?: number;
|
||||
idToken?: null | string;
|
||||
password?: null | string;
|
||||
providerId?: string;
|
||||
refreshToken?: null | string;
|
||||
refreshTokenExpiresAt?: null | number;
|
||||
scope?: null | string;
|
||||
updatedAt?: number;
|
||||
userId?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "accountId"
|
||||
| "providerId"
|
||||
| "userId"
|
||||
| "accessToken"
|
||||
| "refreshToken"
|
||||
| "idToken"
|
||||
| "accessTokenExpiresAt"
|
||||
| "refreshTokenExpiresAt"
|
||||
| "scope"
|
||||
| "password"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "verification";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
expiresAt?: number;
|
||||
identifier?: string;
|
||||
updatedAt?: number;
|
||||
value?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field:
|
||||
| "identifier"
|
||||
| "value"
|
||||
| "expiresAt"
|
||||
| "createdAt"
|
||||
| "updatedAt"
|
||||
| "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
model: "jwks";
|
||||
update: {
|
||||
createdAt?: number;
|
||||
privateKey?: string;
|
||||
publicKey?: string;
|
||||
};
|
||||
where?: Array<{
|
||||
connector?: "AND" | "OR";
|
||||
field: "publicKey" | "privateKey" | "createdAt" | "_id";
|
||||
operator?:
|
||||
| "lt"
|
||||
| "lte"
|
||||
| "gt"
|
||||
| "gte"
|
||||
| "eq"
|
||||
| "in"
|
||||
| "not_in"
|
||||
| "ne"
|
||||
| "contains"
|
||||
| "starts_with"
|
||||
| "ends_with";
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<string>
|
||||
| Array<number>
|
||||
| null;
|
||||
}>;
|
||||
};
|
||||
onUpdateHandle?: string;
|
||||
},
|
||||
any
|
||||
>;
|
||||
};
|
||||
};
|
||||
// For now fullApiWithMounts is only fullApi which provides
|
||||
// jump-to-definition in component client code.
|
||||
// Use Mounts for the same type without the inference.
|
||||
declare const fullApiWithMounts: typeof fullApi;
|
||||
|
||||
export declare const api: FilterApi<
|
||||
typeof fullApiWithMounts,
|
||||
FunctionReference<any, "public">
|
||||
>;
|
||||
export declare const internal: FilterApi<
|
||||
typeof fullApiWithMounts,
|
||||
FunctionReference<any, "internal">
|
||||
>;
|
||||
|
||||
export declare const components: {};
|
||||
@@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `api` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { anyApi, componentsGeneric } from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = api.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
export const api = anyApi;
|
||||
export const internal = anyApi;
|
||||
export const components = componentsGeneric();
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated data model types.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type {
|
||||
DataModelFromSchemaDefinition,
|
||||
DocumentByName,
|
||||
TableNamesInDataModel,
|
||||
SystemTableNames,
|
||||
} from "convex/server";
|
||||
import type { GenericId } from "convex/values";
|
||||
import schema from "../schema.js";
|
||||
|
||||
/**
|
||||
* The names of all of your Convex tables.
|
||||
*/
|
||||
export type TableNames = TableNamesInDataModel<DataModel>;
|
||||
|
||||
/**
|
||||
* The type of a document stored in Convex.
|
||||
*
|
||||
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||
*/
|
||||
export type Doc<TableName extends TableNames> = DocumentByName<
|
||||
DataModel,
|
||||
TableName
|
||||
>;
|
||||
|
||||
/**
|
||||
* An identifier for a document in Convex.
|
||||
*
|
||||
* Convex documents are uniquely identified by their `Id`, which is accessible
|
||||
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
||||
*
|
||||
* Documents can be loaded using `db.get(id)` in query and mutation functions.
|
||||
*
|
||||
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
||||
* strings when type checking.
|
||||
*
|
||||
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||
*/
|
||||
export type Id<TableName extends TableNames | SystemTableNames> =
|
||||
GenericId<TableName>;
|
||||
|
||||
/**
|
||||
* A type describing your Convex data model.
|
||||
*
|
||||
* This type includes information about what tables you have, the type of
|
||||
* documents stored in those tables, and the indexes defined on them.
|
||||
*
|
||||
* This type is used to parameterize methods like `queryGeneric` and
|
||||
* `mutationGeneric` to make them type-safe.
|
||||
*/
|
||||
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
||||
@@ -1,149 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
ActionBuilder,
|
||||
AnyComponents,
|
||||
HttpActionBuilder,
|
||||
MutationBuilder,
|
||||
QueryBuilder,
|
||||
GenericActionCtx,
|
||||
GenericMutationCtx,
|
||||
GenericQueryCtx,
|
||||
GenericDatabaseReader,
|
||||
GenericDatabaseWriter,
|
||||
FunctionReference,
|
||||
} from "convex/server";
|
||||
import type { DataModel } from "./dataModel.js";
|
||||
|
||||
type GenericCtx =
|
||||
| GenericActionCtx<DataModel>
|
||||
| GenericMutationCtx<DataModel>
|
||||
| GenericQueryCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const query: QueryBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const mutation: MutationBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
*
|
||||
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||
* code and code with side-effects, like calling third-party services.
|
||||
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||
*
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const action: ActionBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an HTTP action.
|
||||
*
|
||||
* This function will be used to respond to HTTP requests received by a Convex
|
||||
* deployment if the requests matches the path and method where this action
|
||||
* is routed. Be sure to route your action in `convex/http.js`.
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
||||
*/
|
||||
export declare const httpAction: HttpActionBuilder;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex query functions.
|
||||
*
|
||||
* The query context is passed as the first argument to any Convex query
|
||||
* function run on the server.
|
||||
*
|
||||
* This differs from the {@link MutationCtx} because all of the services are
|
||||
* read-only.
|
||||
*/
|
||||
export type QueryCtx = GenericQueryCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex mutation functions.
|
||||
*
|
||||
* The mutation context is passed as the first argument to any Convex mutation
|
||||
* function run on the server.
|
||||
*/
|
||||
export type MutationCtx = GenericMutationCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex action functions.
|
||||
*
|
||||
* The action context is passed as the first argument to any Convex action
|
||||
* function run on the server.
|
||||
*/
|
||||
export type ActionCtx = GenericActionCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from the database within Convex query functions.
|
||||
*
|
||||
* The two entry points are {@link DatabaseReader.get}, which fetches a single
|
||||
* document by its {@link Id}, or {@link DatabaseReader.query}, which starts
|
||||
* building a query.
|
||||
*/
|
||||
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from and write to the database within Convex mutation
|
||||
* functions.
|
||||
*
|
||||
* Convex guarantees that all writes within a single mutation are
|
||||
* executed atomically, so you never have to worry about partial writes leaving
|
||||
* your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
|
||||
* for the guarantees Convex provides your functions.
|
||||
*/
|
||||
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
||||
@@ -1,90 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
actionGeneric,
|
||||
httpActionGeneric,
|
||||
queryGeneric,
|
||||
mutationGeneric,
|
||||
internalActionGeneric,
|
||||
internalMutationGeneric,
|
||||
internalQueryGeneric,
|
||||
componentsGeneric,
|
||||
} from "convex/server";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const query = queryGeneric;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalQuery = internalQueryGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const mutation = mutationGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalMutation = internalMutationGeneric;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
*
|
||||
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||
* code and code with side-effects, like calling third-party services.
|
||||
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||
*
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const action = actionGeneric;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalAction = internalActionGeneric;
|
||||
|
||||
/**
|
||||
* Define a Convex HTTP action.
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
|
||||
* as its second.
|
||||
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
|
||||
*/
|
||||
export const httpAction = httpActionGeneric;
|
||||
@@ -110,7 +110,7 @@ export const salvarConfigEmail = mutation({
|
||||
* NOTA: Esta action será implementada quando instalarmos nodemailer.
|
||||
* Por enquanto, retorna sucesso simulado para não bloquear o desenvolvimento.
|
||||
*/
|
||||
export const testarConexaoSMTP = action({
|
||||
export const testarConexaoSMTP = mutation({
|
||||
args: {
|
||||
servidor: v.string(),
|
||||
porta: v.number(),
|
||||
@@ -119,43 +119,10 @@ export const testarConexaoSMTP = action({
|
||||
usarSSL: v.boolean(),
|
||||
usarTLS: v.boolean(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// TODO: Implementar teste real com nodemailer
|
||||
// Por enquanto, simula sucesso
|
||||
// Delegar para a action de Node em arquivo separado
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
if (!args.servidor || args.servidor.trim() === "") {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Servidor SMTP não pode estar vazio",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.porta < 1 || args.porta > 65535) {
|
||||
return { sucesso: false as const, erro: "Porta inválida" };
|
||||
}
|
||||
|
||||
// Simular delay de teste
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// Retornar sucesso simulado
|
||||
console.log(
|
||||
"⚠️ AVISO: Teste de conexão SMTP simulado (nodemailer não instalado ainda)"
|
||||
);
|
||||
return { sucesso: true as const };
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: errorMessage || "Erro ao testar conexão",
|
||||
};
|
||||
}
|
||||
return { sucesso: true };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation } from "./_generated/server";
|
||||
|
||||
/**
|
||||
* Mutation de teste para criar um funcionário e associar ao usuário TI Master
|
||||
* Isso permite testar o sistema de férias completo
|
||||
*/
|
||||
export const criarFuncionarioParaTIMaster = mutation({
|
||||
args: {
|
||||
usuarioEmail: v.string(), // Email do usuário TI Master
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true), funcionarioId: v.id("funcionarios") }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar usuário
|
||||
const usuario = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", args.usuarioEmail))
|
||||
.first();
|
||||
|
||||
if (!usuario) {
|
||||
return { sucesso: false as const, erro: "Usuário não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se já tem funcionário associado
|
||||
if (usuario.funcionarioId) {
|
||||
return { sucesso: false as const, erro: "Usuário já tem funcionário associado" };
|
||||
}
|
||||
|
||||
// Buscar um símbolo qualquer (pegamos o primeiro)
|
||||
const simbolo = await ctx.db.query("simbolos").first();
|
||||
|
||||
if (!simbolo) {
|
||||
return { sucesso: false as const, erro: "Nenhum símbolo encontrado no sistema" };
|
||||
}
|
||||
|
||||
// Criar funcionário de teste
|
||||
const funcionarioId = await ctx.db.insert("funcionarios", {
|
||||
nome: usuario.nome,
|
||||
cpf: "000.000.000-00", // CPF de teste
|
||||
rg: "0000000",
|
||||
endereco: "Endereço de Teste",
|
||||
bairro: "Centro",
|
||||
cidade: "Recife",
|
||||
uf: "PE",
|
||||
telefone: "(81) 99999-9999",
|
||||
email: usuario.email,
|
||||
matricula: usuario.matricula,
|
||||
admissaoData: "2023-01-01", // Data de admissão: 1 ano atrás
|
||||
simboloId: simbolo._id,
|
||||
simboloTipo: simbolo.tipo,
|
||||
statusFerias: "ativo",
|
||||
|
||||
// IMPORTANTE: Definir regime de trabalho
|
||||
// Altere aqui para testar diferentes regimes:
|
||||
// - "clt" = CLT (máx 3 períodos, mín 5 dias)
|
||||
// - "estatutario_pe" = Servidor Público PE (máx 2 períodos, mín 10 dias)
|
||||
regimeTrabalho: "clt",
|
||||
|
||||
// Dados opcionais
|
||||
descricaoCargo: "Gestor de TI - Cargo de Teste",
|
||||
nomePai: "Pai de Teste",
|
||||
nomeMae: "Mãe de Teste",
|
||||
naturalidade: "Recife",
|
||||
naturalidadeUF: "PE",
|
||||
sexo: "masculino",
|
||||
estadoCivil: "solteiro",
|
||||
nacionalidade: "Brasileira",
|
||||
grauInstrucao: "superior_completo",
|
||||
tipoSanguineo: "O+",
|
||||
});
|
||||
|
||||
// Associar funcionário ao usuário
|
||||
await ctx.db.patch(usuario._id, {
|
||||
funcionarioId,
|
||||
});
|
||||
|
||||
return { sucesso: true as const, funcionarioId };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Mutation para alterar o regime de trabalho de um funcionário
|
||||
* Útil para testar diferentes regras (CLT vs Servidor PE)
|
||||
*/
|
||||
export const alterarRegimeTrabalho = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
novoRegime: v.union(
|
||||
v.literal("clt"),
|
||||
v.literal("estatutario_pe"),
|
||||
v.literal("estatutario_federal"),
|
||||
v.literal("estatutario_municipal")
|
||||
),
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean() }),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.funcionarioId, {
|
||||
regimeTrabalho: args.novoRegime,
|
||||
});
|
||||
|
||||
return { sucesso: true };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Mutation para alterar data de admissão
|
||||
* Útil para testar diferentes períodos aquisitivos
|
||||
*/
|
||||
export const alterarDataAdmissao = mutation({
|
||||
args: {
|
||||
funcionarioId: v.id("funcionarios"),
|
||||
novaData: v.string(), // Formato: "YYYY-MM-DD"
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean() }),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.funcionarioId, {
|
||||
admissaoData: args.novaData,
|
||||
});
|
||||
|
||||
return { sucesso: true };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation } from "./_generated/server";
|
||||
import { hashPassword } from "./auth/utils";
|
||||
|
||||
/**
|
||||
* Cria um usuário de teste com funcionário associado
|
||||
* para testar o sistema de férias
|
||||
*/
|
||||
export const criarUsuarioParaTesteFerias = mutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
sucesso: v.boolean(),
|
||||
login: v.string(),
|
||||
senha: v.string(),
|
||||
mensagem: v.string(),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
const loginTeste = "teste.ferias";
|
||||
const senhaTeste = "Teste@2025";
|
||||
const emailTeste = "teste.ferias@sgse.pe.gov.br";
|
||||
const nomeTeste = "João Silva (Teste)";
|
||||
|
||||
// Verificar se já existe
|
||||
const usuarioExistente = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_matricula", (q) => q.eq("matricula", loginTeste))
|
||||
.first();
|
||||
|
||||
if (usuarioExistente) {
|
||||
return {
|
||||
sucesso: true,
|
||||
login: loginTeste,
|
||||
senha: senhaTeste,
|
||||
mensagem: "Usuário de teste já existe! Use as credenciais abaixo.",
|
||||
};
|
||||
}
|
||||
|
||||
// Buscar role padrão (usuário comum)
|
||||
const roleUsuario = await ctx.db
|
||||
.query("roles")
|
||||
.filter((q) => q.eq(q.field("nome"), "usuario"))
|
||||
.first();
|
||||
|
||||
if (!roleUsuario) {
|
||||
return {
|
||||
sucesso: false,
|
||||
login: "",
|
||||
senha: "",
|
||||
mensagem: "Erro: Role 'usuario' não encontrada",
|
||||
};
|
||||
}
|
||||
|
||||
// Buscar um símbolo qualquer
|
||||
const simbolo = await ctx.db.query("simbolos").first();
|
||||
|
||||
if (!simbolo) {
|
||||
return {
|
||||
sucesso: false,
|
||||
login: "",
|
||||
senha: "",
|
||||
mensagem: "Erro: Nenhum símbolo encontrado. Crie um símbolo primeiro.",
|
||||
};
|
||||
}
|
||||
|
||||
// Criar funcionário
|
||||
const funcionarioId = await ctx.db.insert("funcionarios", {
|
||||
nome: nomeTeste,
|
||||
cpf: "111.222.333-44",
|
||||
rg: "1234567",
|
||||
nascimento: "1990-05-15",
|
||||
endereco: "Rua de Teste, 123",
|
||||
bairro: "Centro",
|
||||
cidade: "Recife",
|
||||
uf: "PE",
|
||||
cep: "50000-000",
|
||||
telefone: "(81) 98765-4321",
|
||||
email: emailTeste,
|
||||
matricula: loginTeste,
|
||||
admissaoData: "2023-01-15", // Admitido em jan/2023 (quase 2 anos)
|
||||
simboloId: simbolo._id,
|
||||
simboloTipo: simbolo.tipo,
|
||||
statusFerias: "ativo",
|
||||
regimeTrabalho: "clt", // CLT para testar
|
||||
descricaoCargo: "Analista Administrativo",
|
||||
nomePai: "José Silva",
|
||||
nomeMae: "Maria Silva",
|
||||
naturalidade: "Recife",
|
||||
naturalidadeUF: "PE",
|
||||
sexo: "masculino",
|
||||
estadoCivil: "solteiro",
|
||||
nacionalidade: "Brasileira",
|
||||
grauInstrucao: "superior",
|
||||
});
|
||||
|
||||
// Criar usuário
|
||||
const senhaHash = await hashPassword(senhaTeste);
|
||||
const usuarioId = await ctx.db.insert("usuarios", {
|
||||
matricula: loginTeste,
|
||||
senhaHash,
|
||||
nome: nomeTeste,
|
||||
email: emailTeste,
|
||||
funcionarioId,
|
||||
roleId: roleUsuario._id,
|
||||
ativo: true,
|
||||
primeiroAcesso: false, // Já consideramos que fez primeiro acesso
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
return {
|
||||
sucesso: true,
|
||||
login: loginTeste,
|
||||
senha: senhaTeste,
|
||||
mensagem: "Usuário de teste criado com sucesso!",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
// Mutation para fazer upload de arquivo e obter o storage ID
|
||||
export const generateUploadUrl = mutation({
|
||||
@@ -72,9 +73,70 @@ export const getDocumentosUrls = query({
|
||||
throw new Error("Funcionário não encontrado");
|
||||
}
|
||||
|
||||
// Gerar URLs para todos os documentos
|
||||
const urls: Record<string, string | null> = {};
|
||||
const campos = [
|
||||
// Tipo exato do retorno para alinhar com o validator
|
||||
type DocumentUrls = {
|
||||
certidaoAntecedentesPF: string | null;
|
||||
certidaoAntecedentesJFPE: string | null;
|
||||
certidaoAntecedentesSDS: string | null;
|
||||
certidaoAntecedentesTJPE: string | null;
|
||||
certidaoImprobidade: string | null;
|
||||
rgFrente: string | null;
|
||||
rgVerso: string | null;
|
||||
cpfFrente: string | null;
|
||||
cpfVerso: string | null;
|
||||
situacaoCadastralCPF: string | null;
|
||||
tituloEleitorFrente: string | null;
|
||||
tituloEleitorVerso: string | null;
|
||||
comprovanteVotacao: string | null;
|
||||
carteiraProfissionalFrente: string | null;
|
||||
carteiraProfissionalVerso: string | null;
|
||||
comprovantePIS: string | null;
|
||||
certidaoRegistroCivil: string | null;
|
||||
certidaoNascimentoDependentes: string | null;
|
||||
cpfDependentes: string | null;
|
||||
reservistaDoc: string | null;
|
||||
comprovanteEscolaridade: string | null;
|
||||
comprovanteResidencia: string | null;
|
||||
comprovanteContaBradesco: string | null;
|
||||
declaracaoAcumulacaoCargo: string | null;
|
||||
declaracaoDependentesIR: string | null;
|
||||
declaracaoIdoneidade: string | null;
|
||||
termoNepotismo: string | null;
|
||||
termoOpcaoRemuneracao: string | null;
|
||||
};
|
||||
|
||||
const urls: DocumentUrls = {
|
||||
certidaoAntecedentesPF: null,
|
||||
certidaoAntecedentesJFPE: null,
|
||||
certidaoAntecedentesSDS: null,
|
||||
certidaoAntecedentesTJPE: null,
|
||||
certidaoImprobidade: null,
|
||||
rgFrente: null,
|
||||
rgVerso: null,
|
||||
cpfFrente: null,
|
||||
cpfVerso: null,
|
||||
situacaoCadastralCPF: null,
|
||||
tituloEleitorFrente: null,
|
||||
tituloEleitorVerso: null,
|
||||
comprovanteVotacao: null,
|
||||
carteiraProfissionalFrente: null,
|
||||
carteiraProfissionalVerso: null,
|
||||
comprovantePIS: null,
|
||||
certidaoRegistroCivil: null,
|
||||
certidaoNascimentoDependentes: null,
|
||||
cpfDependentes: null,
|
||||
reservistaDoc: null,
|
||||
comprovanteEscolaridade: null,
|
||||
comprovanteResidencia: null,
|
||||
comprovanteContaBradesco: null,
|
||||
declaracaoAcumulacaoCargo: null,
|
||||
declaracaoDependentesIR: null,
|
||||
declaracaoIdoneidade: null,
|
||||
termoNepotismo: null,
|
||||
termoOpcaoRemuneracao: null,
|
||||
};
|
||||
|
||||
const campos: Array<keyof DocumentUrls> = [
|
||||
"certidaoAntecedentesPF",
|
||||
"certidaoAntecedentesJFPE",
|
||||
"certidaoAntecedentesSDS",
|
||||
@@ -106,7 +168,9 @@ export const getDocumentosUrls = query({
|
||||
];
|
||||
|
||||
for (const campo of campos) {
|
||||
const storageId = funcionario[campo as keyof typeof funcionario] as Id<"_storage"> | undefined;
|
||||
const storageId = (funcionario as Record<string, unknown>)[campo as string] as
|
||||
| Id<"_storage">
|
||||
| undefined;
|
||||
if (storageId) {
|
||||
urls[campo] = await ctx.storage.getUrl(storageId);
|
||||
} else {
|
||||
|
||||
@@ -46,6 +46,11 @@ export const enfileirarEmail = mutation({
|
||||
criadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Agendar envio imediato via action
|
||||
await ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId,
|
||||
});
|
||||
|
||||
return { sucesso: true, emailId };
|
||||
},
|
||||
});
|
||||
@@ -94,6 +99,11 @@ export const enviarEmailComTemplate = mutation({
|
||||
criadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Agendar envio imediato via action
|
||||
await ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId,
|
||||
});
|
||||
|
||||
return { sucesso: true, emailId };
|
||||
},
|
||||
});
|
||||
@@ -225,99 +235,7 @@ export const markEmailFalha = internalMutation({
|
||||
},
|
||||
});
|
||||
|
||||
export const enviarEmailAction = action({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
||||
handler: async (ctx, args) => {
|
||||
"use node";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
try {
|
||||
// Buscar email da fila
|
||||
const email = await ctx.runQuery(internal.email.getEmailById, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
return { sucesso: false, erro: "Email não encontrado" };
|
||||
}
|
||||
|
||||
// Buscar configuração SMTP
|
||||
const config = await ctx.runQuery(
|
||||
internal.email.getActiveEmailConfig,
|
||||
{}
|
||||
);
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
sucesso: false,
|
||||
erro: "Configuração de email não encontrada ou inativa",
|
||||
};
|
||||
}
|
||||
|
||||
if (!config.testadoEm) {
|
||||
return {
|
||||
sucesso: false,
|
||||
erro: "Configuração SMTP não foi testada. Teste a conexão primeiro!",
|
||||
};
|
||||
}
|
||||
|
||||
// Marcar como enviando
|
||||
await ctx.runMutation(internal.email.markEmailEnviando, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
// Criar transporter do nodemailer
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: config.servidor,
|
||||
port: config.porta,
|
||||
secure: config.usarSSL,
|
||||
auth: {
|
||||
user: config.usuario,
|
||||
pass: config.senhaHash, // Note: em produção deve ser descriptografado
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Enviar email REAL
|
||||
const info = await transporter.sendMail({
|
||||
from: `"${config.nomeRemetente}" <${config.emailRemetente}>`,
|
||||
to: email.destinatario,
|
||||
subject: email.assunto,
|
||||
html: email.corpo,
|
||||
});
|
||||
|
||||
console.log("✅ Email enviado com sucesso!");
|
||||
console.log(" Para:", email.destinatario);
|
||||
console.log(" Assunto:", email.assunto);
|
||||
console.log(" Message ID:", info.messageId);
|
||||
|
||||
// Marcar como enviado
|
||||
await ctx.runMutation(internal.email.markEmailEnviado, {
|
||||
emailId: args.emailId,
|
||||
});
|
||||
|
||||
return { sucesso: true };
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
console.error("❌ Erro ao enviar email:", errorMessage);
|
||||
|
||||
// Marcar como falha
|
||||
await ctx.runMutation(internal.email.markEmailFalha, {
|
||||
emailId: args.emailId,
|
||||
erro: errorMessage,
|
||||
});
|
||||
|
||||
return { sucesso: false, erro: errorMessage };
|
||||
}
|
||||
},
|
||||
});
|
||||
// Action de envio foi movida para `actions/email.ts`
|
||||
|
||||
/**
|
||||
* Processar fila de emails (cron job - processa emails pendentes)
|
||||
@@ -345,9 +263,7 @@ export const processarFilaEmails = internalMutation({
|
||||
}
|
||||
|
||||
// Agendar envio via action
|
||||
// IMPORTANTE: Não podemos chamar action diretamente de mutation
|
||||
// Por isso, usaremos o scheduler com string path
|
||||
await ctx.scheduler.runAfter(0, "email:enviarEmailAction" as any, {
|
||||
await ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: email._id,
|
||||
});
|
||||
|
||||
|
||||
@@ -54,13 +54,42 @@ export const listarMinhasSolicitacoes = query({
|
||||
args: { funcionarioId: v.id("funcionarios") },
|
||||
// returns não especificado - TypeScript inferirá automaticamente o tipo correto
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
const solicitacoes = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.withIndex("by_funcionario", (q) =>
|
||||
q.eq("funcionarioId", args.funcionarioId)
|
||||
)
|
||||
.order("desc")
|
||||
.collect();
|
||||
|
||||
// Enriquecer com dados do funcionário e time
|
||||
const solicitacoesComDetalhes = await Promise.all(
|
||||
solicitacoes.map(async (s) => {
|
||||
const funcionario = await ctx.db.get(s.funcionarioId);
|
||||
|
||||
// Buscar time do funcionário
|
||||
const membroTime = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) =>
|
||||
q.eq("funcionarioId", s.funcionarioId)
|
||||
)
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
let time = null;
|
||||
if (membroTime) {
|
||||
time = await ctx.db.get(membroTime.timeId);
|
||||
}
|
||||
|
||||
return {
|
||||
...s,
|
||||
funcionario,
|
||||
time,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return solicitacoesComDetalhes;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const getAll = query({
|
||||
// Retornar apenas os campos necessários para listagem
|
||||
return funcionarios.map((f) => ({
|
||||
_id: f._id,
|
||||
_creationTime: f._creationTime,
|
||||
nome: f.nome,
|
||||
matricula: f.matricula,
|
||||
cpf: f.cpf,
|
||||
|
||||
@@ -190,15 +190,6 @@ export const limparPerfisAntigos = internalMutation({
|
||||
await ctx.db.delete(perm._id);
|
||||
}
|
||||
|
||||
// Remover menu permissões associadas
|
||||
const menuPerms = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", role._id))
|
||||
.collect();
|
||||
for (const menuPerm of menuPerms) {
|
||||
await ctx.db.delete(menuPerm._id);
|
||||
}
|
||||
|
||||
// Remover o role
|
||||
await ctx.db.delete(role._id);
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import { internalMutation } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
/**
|
||||
* Migração: Converte estrutura antiga de gestores individuais para times
|
||||
*
|
||||
* Esta função cria automaticamente times baseados nos gestores existentes
|
||||
* e adiciona os funcionários subordinados aos respectivos times.
|
||||
*
|
||||
* Execute uma vez via dashboard do Convex:
|
||||
* Settings > Functions > Internal > migrarParaTimes > executar
|
||||
*/
|
||||
export const executar = internalMutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
timesCreated: v.number(),
|
||||
funcionariosAtribuidos: v.number(),
|
||||
erros: v.array(v.string()),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const erros: string[] = [];
|
||||
let timesCreated = 0;
|
||||
let funcionariosAtribuidos = 0;
|
||||
|
||||
try {
|
||||
// 1. Buscar todos os funcionários que têm gestor definido
|
||||
const funcionariosComGestor = await ctx.db
|
||||
.query("funcionarios")
|
||||
.filter((q) => q.neq(q.field("gestorId"), undefined))
|
||||
.collect();
|
||||
|
||||
if (funcionariosComGestor.length === 0) {
|
||||
return {
|
||||
timesCreated: 0,
|
||||
funcionariosAtribuidos: 0,
|
||||
erros: ["Nenhum funcionário com gestor configurado encontrado"],
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Agrupar funcionários por gestor
|
||||
const gestoresMap = new Map<string, Doc<"funcionarios">[]>();
|
||||
|
||||
for (const funcionario of funcionariosComGestor) {
|
||||
if (!funcionario.gestorId) continue;
|
||||
|
||||
const gestorId = funcionario.gestorId;
|
||||
if (!gestoresMap.has(gestorId)) {
|
||||
gestoresMap.set(gestorId, []);
|
||||
}
|
||||
gestoresMap.get(gestorId)!.push(funcionario);
|
||||
}
|
||||
|
||||
// 3. Para cada gestor, criar um time
|
||||
for (const [gestorId, subordinados] of gestoresMap.entries()) {
|
||||
try {
|
||||
const gestor = await ctx.db.get(gestorId);
|
||||
|
||||
if (!gestor) {
|
||||
erros.push(`Gestor ${gestorId} não encontrado`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verificar se já existe time para este gestor
|
||||
const timeExistente = await ctx.db
|
||||
.query("times")
|
||||
.withIndex("by_gestor", (q) => q.eq("gestorId", gestorId))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
let timeId;
|
||||
|
||||
if (timeExistente) {
|
||||
timeId = timeExistente._id;
|
||||
} else {
|
||||
// Criar novo time
|
||||
timeId = await ctx.db.insert("times", {
|
||||
nome: `Equipe ${gestor.nome}`,
|
||||
descricao: `Time gerenciado por ${gestor.nome} (migração automática)`,
|
||||
gestorId: gestorId,
|
||||
ativo: true,
|
||||
cor: "#3B82F6",
|
||||
});
|
||||
timesCreated++;
|
||||
}
|
||||
|
||||
// Adicionar membros ao time
|
||||
for (const funcionario of subordinados) {
|
||||
try {
|
||||
// Verificar se já está em algum time
|
||||
const membroExistente = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", funcionario._id))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (!membroExistente) {
|
||||
await ctx.db.insert("timesMembros", {
|
||||
timeId: timeId,
|
||||
funcionarioId: funcionario._id,
|
||||
dataEntrada: Date.now(),
|
||||
ativo: true,
|
||||
});
|
||||
funcionariosAtribuidos++;
|
||||
}
|
||||
} catch (e) {
|
||||
erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
erros.push(`Erro ao processar gestor ${gestorId}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timesCreated,
|
||||
funcionariosAtribuidos,
|
||||
erros,
|
||||
};
|
||||
} catch (e: any) {
|
||||
erros.push(`Erro geral na migração: ${e.message}`);
|
||||
return {
|
||||
timesCreated,
|
||||
funcionariosAtribuidos,
|
||||
erros,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Função auxiliar para limpar times inativos antigos
|
||||
*/
|
||||
export const limparTimesInativos = internalMutation({
|
||||
args: {
|
||||
diasInativos: v.optional(v.number()),
|
||||
},
|
||||
returns: v.number(),
|
||||
handler: async (ctx, args) => {
|
||||
const diasLimite = args.diasInativos || 30;
|
||||
const dataLimite = Date.now() - (diasLimite * 24 * 60 * 60 * 1000);
|
||||
|
||||
const timesInativos = await ctx.db
|
||||
.query("times")
|
||||
.filter((q) => q.eq(q.field("ativo"), false))
|
||||
.collect();
|
||||
|
||||
let removidos = 0;
|
||||
|
||||
for (const time of timesInativos) {
|
||||
if (time._creationTime < dataLimite) {
|
||||
// Remover membros inativos do time
|
||||
const membrosInativos = await ctx.db
|
||||
.query("timesMembros")
|
||||
.withIndex("by_time", (q) => q.eq("timeId", time._id))
|
||||
.filter((q) => q.eq(q.field("ativo"), false))
|
||||
.collect();
|
||||
|
||||
for (const membro of membrosInativos) {
|
||||
await ctx.db.delete(membro._id);
|
||||
}
|
||||
|
||||
// Remover o time
|
||||
await ctx.db.delete(time._id);
|
||||
removidos++;
|
||||
}
|
||||
}
|
||||
|
||||
return removidos;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
import { internalMutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
/**
|
||||
* Listar usuários usando o perfil "admin" antigo (nível 0)
|
||||
*/
|
||||
export const listarUsuariosAdminAntigo = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
roleId: v.id("roles"),
|
||||
roleNome: v.string(),
|
||||
roleNivel: v.number(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
// Buscar todos os perfis "admin"
|
||||
const allAdmins = await ctx.db
|
||||
.query("roles")
|
||||
.filter((q) => q.eq(q.field("nome"), "admin"))
|
||||
.collect();
|
||||
|
||||
console.log("Perfis 'admin' encontrados:", allAdmins.length);
|
||||
|
||||
// Identificar o admin antigo (nível 0)
|
||||
const adminAntigo = allAdmins.find((r) => r.nivel === 0);
|
||||
|
||||
if (!adminAntigo) {
|
||||
console.log("Nenhum admin antigo (nível 0) encontrado");
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log("Admin antigo encontrado:", adminAntigo);
|
||||
|
||||
// Buscar usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", adminAntigo._id))
|
||||
.collect();
|
||||
|
||||
console.log("Usuários usando admin antigo:", usuarios.length);
|
||||
|
||||
return usuarios.map((u) => ({
|
||||
_id: u._id,
|
||||
matricula: u.matricula,
|
||||
nome: u.nome,
|
||||
email: u.email || "",
|
||||
roleId: u.roleId,
|
||||
roleNome: adminAntigo.nome,
|
||||
roleNivel: adminAntigo.nivel,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Migrar usuários do perfil "admin" antigo (nível 0) para o novo (nível 2)
|
||||
*/
|
||||
export const migrarUsuariosParaAdminNovo = internalMutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
migrados: v.number(),
|
||||
usuariosMigrados: v.array(
|
||||
v.object({
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
roleAntigo: v.string(),
|
||||
roleNovo: v.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
// Buscar todos os perfis "admin"
|
||||
const allAdmins = await ctx.db
|
||||
.query("roles")
|
||||
.filter((q) => q.eq(q.field("nome"), "admin"))
|
||||
.collect();
|
||||
|
||||
// Identificar admin antigo (nível 0) e admin novo (nível 2)
|
||||
const adminAntigo = allAdmins.find((r) => r.nivel === 0);
|
||||
const adminNovo = allAdmins.find((r) => r.nivel === 2);
|
||||
|
||||
if (!adminAntigo) {
|
||||
console.log("❌ Admin antigo (nível 0) não encontrado");
|
||||
return { migrados: 0, usuariosMigrados: [] };
|
||||
}
|
||||
|
||||
if (!adminNovo) {
|
||||
console.log("❌ Admin novo (nível 2) não encontrado");
|
||||
return { migrados: 0, usuariosMigrados: [] };
|
||||
}
|
||||
|
||||
console.log("✅ Admin antigo ID:", adminAntigo._id, "- Nível:", adminAntigo.nivel);
|
||||
console.log("✅ Admin novo ID:", adminNovo._id, "- Nível:", adminNovo.nivel);
|
||||
|
||||
// Buscar usuários usando o admin antigo
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", adminAntigo._id))
|
||||
.collect();
|
||||
|
||||
console.log(`📊 Encontrados ${usuarios.length} usuário(s) para migrar`);
|
||||
|
||||
const usuariosMigrados: Array<{
|
||||
matricula: string;
|
||||
nome: string;
|
||||
roleAntigo: string;
|
||||
roleNovo: string;
|
||||
}> = [];
|
||||
|
||||
// Migrar cada usuário
|
||||
for (const usuario of usuarios) {
|
||||
await ctx.db.patch(usuario._id, {
|
||||
roleId: adminNovo._id,
|
||||
});
|
||||
|
||||
usuariosMigrados.push({
|
||||
matricula: usuario.matricula,
|
||||
nome: usuario.nome,
|
||||
roleAntigo: `admin (nível 0) - ${adminAntigo._id}`,
|
||||
roleNovo: `admin (nível 2) - ${adminNovo._id}`,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`✅ MIGRADO: ${usuario.nome} (${usuario.matricula}) → admin nível 2`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
migrados: usuarios.length,
|
||||
usuariosMigrados,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Remover perfil "admin" antigo (nível 0) após migração
|
||||
*/
|
||||
export const removerAdminAntigo = internalMutation({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
sucesso: v.boolean(),
|
||||
mensagem: v.string(),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
// Buscar todos os perfis "admin"
|
||||
const allAdmins = await ctx.db
|
||||
.query("roles")
|
||||
.filter((q) => q.eq(q.field("nome"), "admin"))
|
||||
.collect();
|
||||
|
||||
// Identificar admin antigo (nível 0)
|
||||
const adminAntigo = allAdmins.find((r) => r.nivel === 0);
|
||||
|
||||
if (!adminAntigo) {
|
||||
return {
|
||||
sucesso: false,
|
||||
mensagem: "Admin antigo (nível 0) não encontrado",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se ainda há usuários usando
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", adminAntigo._id))
|
||||
.collect();
|
||||
|
||||
if (usuarios.length > 0) {
|
||||
return {
|
||||
sucesso: false,
|
||||
mensagem: `Ainda há ${usuarios.length} usuário(s) usando este perfil. Execute migrarUsuariosParaAdminNovo primeiro.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Remover permissões associadas
|
||||
const permissoes = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", adminAntigo._id))
|
||||
.collect();
|
||||
for (const perm of permissoes) {
|
||||
await ctx.db.delete(perm._id);
|
||||
}
|
||||
|
||||
// Remover menu permissões associadas
|
||||
const menuPerms = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", adminAntigo._id))
|
||||
.collect();
|
||||
for (const menuPerm of menuPerms) {
|
||||
await ctx.db.delete(menuPerm._id);
|
||||
}
|
||||
|
||||
// Remover o perfil
|
||||
await ctx.db.delete(adminAntigo._id);
|
||||
|
||||
console.log(
|
||||
`🗑️ REMOVIDO: Admin antigo (nível 0) - ${adminAntigo._id}`
|
||||
);
|
||||
|
||||
return {
|
||||
sucesso: true,
|
||||
mensagem: "Admin antigo removido com sucesso",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -191,23 +191,36 @@ export const obterMetricas = query({
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
let query = ctx.db.query("systemMetrics");
|
||||
|
||||
// Filtrar por data se fornecido
|
||||
if (args.dataInicio !== undefined || args.dataFim !== undefined) {
|
||||
query = query.withIndex("by_timestamp", (q) => {
|
||||
if (args.dataInicio !== undefined && args.dataFim !== undefined) {
|
||||
return q.gte("timestamp", args.dataInicio).lte("timestamp", args.dataFim);
|
||||
} else if (args.dataInicio !== undefined) {
|
||||
return q.gte("timestamp", args.dataInicio);
|
||||
} else {
|
||||
return q.lte("timestamp", args.dataFim!);
|
||||
}
|
||||
});
|
||||
// Construir consulta respeitando tipos sem reatribuições
|
||||
let metricas;
|
||||
if (args.dataInicio !== undefined && args.dataFim !== undefined) {
|
||||
const inicio: number = args.dataInicio as number;
|
||||
const fim: number = args.dataFim as number;
|
||||
metricas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) =>
|
||||
q.gte("timestamp", inicio).lte("timestamp", fim)
|
||||
)
|
||||
.order("desc")
|
||||
.collect();
|
||||
} else if (args.dataInicio !== undefined) {
|
||||
const inicio: number = args.dataInicio as number;
|
||||
metricas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) => q.gte("timestamp", inicio))
|
||||
.order("desc")
|
||||
.collect();
|
||||
} else if (args.dataFim !== undefined) {
|
||||
const fim: number = args.dataFim as number;
|
||||
metricas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) => q.lte("timestamp", fim))
|
||||
.order("desc")
|
||||
.collect();
|
||||
} else {
|
||||
metricas = await ctx.db.query("systemMetrics").order("desc").collect();
|
||||
}
|
||||
|
||||
let metricas = await query.order("desc").collect();
|
||||
|
||||
// Limitar resultados
|
||||
if (args.limit !== undefined && args.limit > 0) {
|
||||
metricas = metricas.slice(0, args.limit);
|
||||
@@ -298,10 +311,10 @@ export const verificarAlertasInternal = internalMutation({
|
||||
.collect();
|
||||
|
||||
for (const alerta of alertasAtivos) {
|
||||
// Obter valor da métrica correspondente
|
||||
const metricValue = (metrica as Record<string, number>)[alerta.metricName];
|
||||
|
||||
if (metricValue === undefined) continue;
|
||||
// Obter valor da métrica correspondente, validando tipo número
|
||||
const rawValue = (metrica as Record<string, unknown>)[alerta.metricName];
|
||||
if (typeof rawValue !== "number") continue;
|
||||
const metricValue = rawValue;
|
||||
|
||||
// Verificar se o alerta deve ser disparado
|
||||
let shouldTrigger = false;
|
||||
@@ -353,11 +366,16 @@ export const verificarAlertasInternal = internalMutation({
|
||||
|
||||
// Criar notificação no chat se configurado
|
||||
if (alerta.notifyByChat) {
|
||||
// Buscar usuários TI para notificar
|
||||
// Buscar roles administrativas (nível <= 1) e filtrar usuários por roleId
|
||||
const rolesAdminOuTi = await ctx.db
|
||||
.query("roles")
|
||||
.filter((q) => q.lte(q.field("nivel"), 1))
|
||||
.collect();
|
||||
|
||||
const rolesPermitidas = new Set(rolesAdminOuTi.map((r) => r._id));
|
||||
|
||||
const usuarios = await ctx.db.query("usuarios").collect();
|
||||
const usuariosTI = usuarios.filter(
|
||||
(u) => u.role?.nome === "ti" || u.role?.nivel === 0
|
||||
);
|
||||
const usuariosTI = usuarios.filter((u) => rolesPermitidas.has(u.roleId));
|
||||
|
||||
for (const usuario of usuariosTI) {
|
||||
await ctx.db.insert("notificacoes", {
|
||||
|
||||
@@ -1,436 +1 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { registrarAtividade } from "./logsAtividades";
|
||||
import { api } from "./_generated/api";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
/**
|
||||
* Listar todos os perfis customizados
|
||||
*/
|
||||
export const listarPerfisCustomizados = query({
|
||||
args: {},
|
||||
// Tipo inferido automaticamente pelo Convex
|
||||
handler: async (ctx) => {
|
||||
const perfis = await ctx.db.query("perfisCustomizados").collect();
|
||||
|
||||
// Buscar role correspondente para cada perfil
|
||||
const perfisComDetalhes = await Promise.all(
|
||||
perfis.map(async (perfil) => {
|
||||
const role = await ctx.db.get(perfil.roleId);
|
||||
const criador = await ctx.db.get(perfil.criadoPor);
|
||||
|
||||
// Contar usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
...perfil,
|
||||
roleNome: role?.nome || "Desconhecido",
|
||||
criadorNome: criador?.nome || "Desconhecido",
|
||||
numeroUsuarios: usuarios.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return perfisComDetalhes;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter perfil com permissões detalhadas
|
||||
*/
|
||||
export const obterPerfilComPermissoes = query({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
perfil: v.any(), // Doc<"perfisCustomizados"> não pode ser validado diretamente
|
||||
role: v.any(), // Doc<"roles"> não pode ser validado diretamente
|
||||
permissoes: v.array(v.any()), // Doc<"permissoes">[] não pode ser validado diretamente
|
||||
menuPermissoes: v.array(v.any()), // Doc<"menuPermissoes">[] não pode ser validado diretamente
|
||||
usuarios: v.array(v.any()), // Doc<"usuarios">[] não pode ser validado diretamente
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const role = await ctx.db.get(perfil.roleId);
|
||||
if (!role) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Buscar permissões do role
|
||||
const rolePermissoes = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
const permissoes = await Promise.all(
|
||||
rolePermissoes.map(async (rp) => {
|
||||
return await ctx.db.get(rp.permissaoId);
|
||||
})
|
||||
);
|
||||
|
||||
// Buscar permissões de menu
|
||||
const menuPermissoes = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
// Buscar usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
return {
|
||||
perfil,
|
||||
role,
|
||||
permissoes: permissoes.filter((p) => p !== null),
|
||||
menuPermissoes,
|
||||
usuarios,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Criar perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const criarPerfilCustomizado = mutation({
|
||||
args: {
|
||||
nome: v.string(),
|
||||
descricao: v.string(),
|
||||
nivel: v.number(), // >= 3
|
||||
clonarDeRoleId: v.optional(v.id("roles")), // role para copiar permissões
|
||||
criadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
}),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
// Validar nível (deve ser >= 3)
|
||||
if (args.nivel < 3) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Perfis customizados devem ter nível >= 3",
|
||||
};
|
||||
}
|
||||
|
||||
// Verificar se nome já existe
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
const nomeExiste = roles.some(
|
||||
(r) => r.nome.toLowerCase() === args.nome.toLowerCase()
|
||||
);
|
||||
if (nomeExiste) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Já existe um perfil com este nome",
|
||||
};
|
||||
}
|
||||
|
||||
// Criar role correspondente
|
||||
const roleId = await ctx.db.insert("roles", {
|
||||
nome: args.nome.toLowerCase().replace(/\s+/g, "_"),
|
||||
descricao: args.descricao,
|
||||
nivel: args.nivel,
|
||||
customizado: true,
|
||||
criadoPor: args.criadoPorId,
|
||||
editavel: true,
|
||||
});
|
||||
|
||||
// Copiar permissões se especificado
|
||||
if (args.clonarDeRoleId) {
|
||||
// Copiar permissões gerais
|
||||
const permissoesClonar = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!))
|
||||
.collect();
|
||||
|
||||
for (const perm of permissoesClonar) {
|
||||
await ctx.db.insert("rolePermissoes", {
|
||||
roleId,
|
||||
permissaoId: perm.permissaoId,
|
||||
});
|
||||
}
|
||||
|
||||
// Copiar permissões de menu
|
||||
const menuPermsClonar = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", args.clonarDeRoleId!))
|
||||
.collect();
|
||||
|
||||
for (const menuPerm of menuPermsClonar) {
|
||||
await ctx.db.insert("menuPermissoes", {
|
||||
roleId,
|
||||
menuPath: menuPerm.menuPath,
|
||||
podeAcessar: menuPerm.podeAcessar,
|
||||
podeConsultar: menuPerm.podeConsultar,
|
||||
podeGravar: menuPerm.podeGravar,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Criar perfil customizado
|
||||
const perfilId = await ctx.db.insert("perfisCustomizados", {
|
||||
nome: args.nome,
|
||||
descricao: args.descricao,
|
||||
nivel: args.nivel,
|
||||
roleId,
|
||||
criadoPor: args.criadoPorId,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.criadoPorId,
|
||||
"criar",
|
||||
"perfis",
|
||||
JSON.stringify({ perfilId, nome: args.nome, nivel: args.nivel }),
|
||||
perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const, perfilId };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Editar perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const editarPerfilCustomizado = mutation({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
nome: v.optional(v.string()),
|
||||
descricao: v.optional(v.string()),
|
||||
editadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return { sucesso: false as const, erro: "Perfil não encontrado" };
|
||||
}
|
||||
|
||||
// Atualizar perfil
|
||||
const updates: Partial<Doc<"perfisCustomizados">> & { atualizadoEm: number } = {
|
||||
atualizadoEm: Date.now(),
|
||||
};
|
||||
|
||||
if (args.nome !== undefined) updates.nome = args.nome;
|
||||
if (args.descricao !== undefined) updates.descricao = args.descricao;
|
||||
|
||||
await ctx.db.patch(args.perfilId, updates);
|
||||
|
||||
// Atualizar role correspondente se nome mudou
|
||||
if (args.nome !== undefined) {
|
||||
await ctx.db.patch(perfil.roleId, {
|
||||
nome: args.nome.toLowerCase().replace(/\s+/g, "_"),
|
||||
});
|
||||
}
|
||||
|
||||
if (args.descricao !== undefined) {
|
||||
await ctx.db.patch(perfil.roleId, {
|
||||
descricao: args.descricao,
|
||||
});
|
||||
}
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.editadoPorId,
|
||||
"editar",
|
||||
"perfis",
|
||||
JSON.stringify(updates),
|
||||
args.perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Excluir perfil customizado (apenas TI_MASTER)
|
||||
*/
|
||||
export const excluirPerfilCustomizado = mutation({
|
||||
args: {
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
excluidoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfil = await ctx.db.get(args.perfilId);
|
||||
if (!perfil) {
|
||||
return { sucesso: false as const, erro: "Perfil não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se existem usuários usando este perfil
|
||||
const usuarios = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
if (usuarios.length > 0) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: `Não é possível excluir. ${usuarios.length} usuário(s) ainda usa(m) este perfil.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Remover permissões associadas ao role
|
||||
const rolePermissoes = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
for (const rp of rolePermissoes) {
|
||||
await ctx.db.delete(rp._id);
|
||||
}
|
||||
|
||||
// Remover permissões de menu
|
||||
const menuPermissoes = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfil.roleId))
|
||||
.collect();
|
||||
|
||||
for (const mp of menuPermissoes) {
|
||||
await ctx.db.delete(mp._id);
|
||||
}
|
||||
|
||||
// Excluir role
|
||||
await ctx.db.delete(perfil.roleId);
|
||||
|
||||
// Excluir perfil
|
||||
await ctx.db.delete(args.perfilId);
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.excluidoPorId,
|
||||
"excluir",
|
||||
"perfis",
|
||||
JSON.stringify({ perfilId: args.perfilId, nome: perfil.nome }),
|
||||
args.perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Clonar perfil existente
|
||||
*/
|
||||
export const clonarPerfil = mutation({
|
||||
args: {
|
||||
perfilOrigemId: v.id("perfisCustomizados"),
|
||||
novoNome: v.string(),
|
||||
novaDescricao: v.string(),
|
||||
criadoPorId: v.id("usuarios"),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
sucesso: v.literal(true),
|
||||
perfilId: v.id("perfisCustomizados"),
|
||||
}),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const perfilOrigem = await ctx.db.get(args.perfilOrigemId);
|
||||
if (!perfilOrigem) {
|
||||
return { sucesso: false as const, erro: "Perfil origem não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se nome já existe
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
const nomeExiste = roles.some(
|
||||
(r) => r.nome.toLowerCase() === args.novoNome.toLowerCase()
|
||||
);
|
||||
if (nomeExiste) {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Já existe um perfil com este nome",
|
||||
};
|
||||
}
|
||||
|
||||
// Criar role correspondente
|
||||
const roleId = await ctx.db.insert("roles", {
|
||||
nome: args.novoNome.toLowerCase().replace(/\s+/g, "_"),
|
||||
descricao: args.novaDescricao,
|
||||
nivel: perfilOrigem.nivel,
|
||||
customizado: true,
|
||||
criadoPor: args.criadoPorId,
|
||||
editavel: true,
|
||||
});
|
||||
|
||||
// Copiar permissões gerais do perfil de origem
|
||||
const permissoesClonar = await ctx.db
|
||||
.query("rolePermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId))
|
||||
.collect();
|
||||
for (const perm of permissoesClonar) {
|
||||
await ctx.db.insert("rolePermissoes", {
|
||||
roleId,
|
||||
permissaoId: perm.permissaoId,
|
||||
});
|
||||
}
|
||||
|
||||
// Copiar permissões de menu
|
||||
const menuPermsClonar = await ctx.db
|
||||
.query("menuPermissoes")
|
||||
.withIndex("by_role", (q) => q.eq("roleId", perfilOrigem.roleId))
|
||||
.collect();
|
||||
for (const menuPerm of menuPermsClonar) {
|
||||
await ctx.db.insert("menuPermissoes", {
|
||||
roleId,
|
||||
menuPath: menuPerm.menuPath,
|
||||
podeAcessar: menuPerm.podeAcessar,
|
||||
podeConsultar: menuPerm.podeConsultar,
|
||||
podeGravar: menuPerm.podeGravar,
|
||||
});
|
||||
}
|
||||
|
||||
// Criar perfil customizado
|
||||
const perfilId = await ctx.db.insert("perfisCustomizados", {
|
||||
nome: args.novoNome,
|
||||
descricao: args.novaDescricao,
|
||||
nivel: perfilOrigem.nivel,
|
||||
roleId,
|
||||
criadoPor: args.criadoPorId,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
|
||||
// Log de atividade
|
||||
await registrarAtividade(
|
||||
ctx,
|
||||
args.criadoPorId,
|
||||
"criar",
|
||||
"perfis",
|
||||
JSON.stringify({
|
||||
perfilId,
|
||||
nome: args.novoNome,
|
||||
nivel: perfilOrigem.nivel,
|
||||
}),
|
||||
perfilId
|
||||
);
|
||||
|
||||
return { sucesso: true as const, perfilId };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { query, mutation, internalQuery } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import type { Doc } from "./_generated/dataModel";
|
||||
|
||||
// Catálogo base de recursos e ações
|
||||
// Ajuste/expanda conforme os módulos disponíveis no sistema
|
||||
|
||||
@@ -14,9 +14,6 @@ export const listar = query({
|
||||
descricao: v.string(),
|
||||
nivel: v.number(),
|
||||
setor: v.optional(v.string()),
|
||||
customizado: v.optional(v.boolean()),
|
||||
editavel: v.optional(v.boolean()),
|
||||
criadoPor: v.optional(v.id("usuarios")),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
@@ -45,3 +42,4 @@ export const buscarPorId = query({
|
||||
return await ctx.db.get(args.roleId);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v } from "convex/values";
|
||||
import { query, mutation, internalMutation } from "./_generated/server";
|
||||
import { internal } from "./_generated/api";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
import type { QueryCtx } from "./_generated/server";
|
||||
|
||||
/**
|
||||
* SISTEMA DE CÁLCULO DE SALDO DE FÉRIAS
|
||||
@@ -126,7 +127,7 @@ export const obterSaldo = query({
|
||||
.first();
|
||||
|
||||
if (!periodo) {
|
||||
// Se não existe, criar automaticamente
|
||||
// Se não existe, calcular e retornar dados previstos sem mutar o banco
|
||||
const funcionario = await ctx.db.get(args.funcionarioId);
|
||||
if (!funcionario || !funcionario.admissaoData) return null;
|
||||
|
||||
@@ -139,23 +140,14 @@ export const obterSaldo = query({
|
||||
|
||||
if (anosDesdeAdmissao < 1) return null; // Ainda não tem direito
|
||||
|
||||
const dataInicio = calcularDataFimPeriodo(funcionario.admissaoData, anosDesdeAdmissao - 1);
|
||||
const dataFim = calcularDataFimPeriodo(funcionario.admissaoData, anosDesdeAdmissao);
|
||||
|
||||
// Criar período aquisitivo
|
||||
await ctx.db.insert("periodosAquisitivos", {
|
||||
funcionarioId: args.funcionarioId,
|
||||
anoReferencia: args.anoReferencia,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
diasDireito: 30,
|
||||
diasUsados: 0,
|
||||
diasPendentes: 0,
|
||||
diasDisponiveis: 30,
|
||||
abonoPermitido: config.abonoPermitido,
|
||||
diasAbono: 0,
|
||||
status: "ativo",
|
||||
});
|
||||
const dataInicio = calcularDataFimPeriodo(
|
||||
funcionario.admissaoData,
|
||||
anosDesdeAdmissao - 1
|
||||
);
|
||||
const dataFim = calcularDataFimPeriodo(
|
||||
funcionario.admissaoData,
|
||||
anosDesdeAdmissao
|
||||
);
|
||||
|
||||
return {
|
||||
anoReferencia: args.anoReferencia,
|
||||
|
||||
@@ -366,32 +366,6 @@ export default defineSchema({
|
||||
.index("by_role", ["roleId"])
|
||||
.index("by_permissao", ["permissaoId"]),
|
||||
|
||||
// Permissões de Menu (granulares por role)
|
||||
menuPermissoes: defineTable({
|
||||
roleId: v.id("roles"),
|
||||
menuPath: v.string(), // "/recursos-humanos", "/financeiro", etc.
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(), // Pode apenas visualizar
|
||||
podeGravar: v.boolean(), // Pode criar/editar/excluir
|
||||
})
|
||||
.index("by_role", ["roleId"])
|
||||
.index("by_menu", ["menuPath"])
|
||||
.index("by_role_and_menu", ["roleId", "menuPath"]),
|
||||
|
||||
// Permissões de Menu Personalizadas (por matrícula)
|
||||
menuPermissoesPersonalizadas: defineTable({
|
||||
usuarioId: v.id("usuarios"),
|
||||
matricula: v.string(), // Para facilitar busca
|
||||
menuPath: v.string(),
|
||||
podeAcessar: v.boolean(),
|
||||
podeConsultar: v.boolean(),
|
||||
podeGravar: v.boolean(),
|
||||
})
|
||||
.index("by_usuario", ["usuarioId"])
|
||||
.index("by_matricula", ["matricula"])
|
||||
.index("by_usuario_and_menu", ["usuarioId", "menuPath"])
|
||||
.index("by_matricula_and_menu", ["matricula", "menuPath"]),
|
||||
|
||||
sessoes: defineTable({
|
||||
usuarioId: v.id("usuarios"),
|
||||
token: v.string(),
|
||||
@@ -473,20 +447,7 @@ export default defineSchema({
|
||||
.index("by_data_inicio", ["dataInicio"]),
|
||||
|
||||
// Perfis Customizados
|
||||
perfisCustomizados: defineTable({
|
||||
nome: v.string(),
|
||||
descricao: v.string(),
|
||||
nivel: v.number(), // >= 3
|
||||
roleId: v.id("roles"), // role correspondente criada
|
||||
criadoPor: v.id("usuarios"), // TI_MASTER que criou
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number(),
|
||||
})
|
||||
.index("by_nome", ["nome"])
|
||||
.index("by_nivel", ["nivel"])
|
||||
.index("by_criado_por", ["criadoPor"])
|
||||
.index("by_role", ["roleId"]),
|
||||
|
||||
|
||||
// Templates de Mensagens
|
||||
templatesMensagens: defineTable({
|
||||
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { internalMutation } from "./_generated/server";
|
||||
import { internalMutation, mutation, query } from "./_generated/server";
|
||||
import { internal } from "./_generated/api";
|
||||
import { v } from "convex/values";
|
||||
import { hashPassword } from "./auth/utils";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
// Dados exportados do Convex Cloud
|
||||
const simbolosData = [
|
||||
@@ -246,203 +248,41 @@ export const seedDatabase = internalMutation({
|
||||
});
|
||||
console.log(" ✅ Role criada: financeiro");
|
||||
|
||||
const roleControladoria = await ctx.db.insert("roles", {
|
||||
nome: "controladoria",
|
||||
descricao: "Controladoria",
|
||||
nivel: 2,
|
||||
setor: "controladoria",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: controladoria");
|
||||
|
||||
const roleLicitacoes = await ctx.db.insert("roles", {
|
||||
nome: "licitacoes",
|
||||
descricao: "Licitações",
|
||||
nivel: 2,
|
||||
setor: "licitacoes",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: licitacoes");
|
||||
|
||||
const roleCompras = await ctx.db.insert("roles", {
|
||||
nome: "compras",
|
||||
descricao: "Compras",
|
||||
nivel: 2,
|
||||
setor: "compras",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: compras");
|
||||
|
||||
const roleJuridico = await ctx.db.insert("roles", {
|
||||
nome: "juridico",
|
||||
descricao: "Jurídico",
|
||||
nivel: 2,
|
||||
setor: "juridico",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: juridico");
|
||||
|
||||
const roleComunicacao = await ctx.db.insert("roles", {
|
||||
nome: "comunicacao",
|
||||
descricao: "Comunicação",
|
||||
nivel: 2,
|
||||
setor: "comunicacao",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: comunicacao");
|
||||
|
||||
const roleProgramasEsportivos = await ctx.db.insert("roles", {
|
||||
nome: "programas_esportivos",
|
||||
descricao: "Programas Esportivos",
|
||||
nivel: 2,
|
||||
setor: "programas_esportivos",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: programas_esportivos");
|
||||
|
||||
const roleSecretariaExecutiva = await ctx.db.insert("roles", {
|
||||
nome: "secretaria_executiva",
|
||||
descricao: "Secretaria Executiva",
|
||||
nivel: 2,
|
||||
setor: "secretaria_executiva",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: secretaria_executiva");
|
||||
|
||||
const roleGestaoPessoas = await ctx.db.insert("roles", {
|
||||
nome: "gestao_pessoas",
|
||||
descricao: "Gestão de Pessoas",
|
||||
nivel: 2,
|
||||
setor: "gestao_pessoas",
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: gestao_pessoas");
|
||||
|
||||
const roleUsuario = await ctx.db.insert("roles", {
|
||||
nome: "usuario",
|
||||
descricao: "Usuário Comum",
|
||||
nivel: 10,
|
||||
descricao: "Usuário Padrão",
|
||||
nivel: 3,
|
||||
setor: undefined,
|
||||
customizado: false,
|
||||
editavel: false,
|
||||
});
|
||||
console.log(" ✅ Role criada: usuario");
|
||||
console.log(" ✅ Role criada: usuario (Nível 3 - Padrão)");
|
||||
|
||||
// 2. Criar usuários iniciais
|
||||
console.log("👤 Criando usuários iniciais...");
|
||||
|
||||
// TI Master
|
||||
const senhaTIMaster = await hashPassword("TI@123");
|
||||
const usuarioTIMasterId = await ctx.db.insert("usuarios", {
|
||||
matricula: "1000",
|
||||
senhaHash: senhaTIMaster,
|
||||
nome: "Gestor TI Master",
|
||||
email: "ti.master@sgse.pe.gov.br",
|
||||
setor: "ti",
|
||||
roleId: roleTIMaster as any,
|
||||
ativo: true,
|
||||
primeiroAcesso: false,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
console.log(" ✅ TI Master criado (matrícula: 1000, senha: TI@123)");
|
||||
|
||||
// Admin (permissões configuráveis)
|
||||
const senhaAdmin = await hashPassword("Admin@123");
|
||||
const adminId = await ctx.db.insert("usuarios", {
|
||||
matricula: "0000",
|
||||
senhaHash: senhaAdmin,
|
||||
nome: "Administrador Geral",
|
||||
email: "admin@sgse.pe.gov.br",
|
||||
setor: "administrativo",
|
||||
roleId: roleAdmin as any,
|
||||
ativo: true,
|
||||
primeiroAcesso: false,
|
||||
criadoEm: Date.now(),
|
||||
atualizadoEm: Date.now(),
|
||||
});
|
||||
console.log(" ✅ Admin criado (matrícula: 0000, senha: Admin@123)");
|
||||
|
||||
// 2.1 Criar catálogo de permissões por ação e conceder a Admin/TI
|
||||
console.log("🔐 Criando permissões por ação...");
|
||||
const CATALOGO_RECURSOS = [
|
||||
{ recurso: "dashboard", acoes: ["ver"] },
|
||||
{
|
||||
recurso: "funcionarios",
|
||||
acoes: ["ver", "listar", "criar", "editar", "excluir"],
|
||||
},
|
||||
{
|
||||
recurso: "simbolos",
|
||||
acoes: ["ver", "listar", "criar", "editar", "excluir"],
|
||||
},
|
||||
{
|
||||
recurso: "usuarios",
|
||||
acoes: ["ver", "listar", "criar", "editar", "excluir"],
|
||||
},
|
||||
{
|
||||
recurso: "perfis",
|
||||
acoes: ["ver", "listar", "criar", "editar", "excluir"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
const permissaoKeyToId = new Map<string, string>();
|
||||
for (const item of CATALOGO_RECURSOS) {
|
||||
for (const acao of item.acoes) {
|
||||
const nome = `${item.recurso}.${acao}`;
|
||||
const id = await ctx.db.insert("permissoes", {
|
||||
nome,
|
||||
descricao: `Permite ${acao} em ${item.recurso}`,
|
||||
recurso: item.recurso,
|
||||
acao,
|
||||
});
|
||||
permissaoKeyToId.set(nome, id);
|
||||
}
|
||||
}
|
||||
console.log(` ✅ ${permissaoKeyToId.size} permissões criadas`);
|
||||
|
||||
// Conceder todas permissões a Admin e TI
|
||||
const rolesParaConceder = [roleAdmin, roleTIUsuario, roleTIMaster];
|
||||
for (const roleId of rolesParaConceder) {
|
||||
for (const [, permId] of permissaoKeyToId) {
|
||||
await ctx.db.insert("rolePermissoes", {
|
||||
roleId: roleId as any,
|
||||
permissaoId: permId as any,
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log(" ✅ Todas as permissões concedidas a Admin e TI");
|
||||
|
||||
// 3. Inserir símbolos
|
||||
console.log("📝 Inserindo símbolos...");
|
||||
const simbolosMap = new Map<string, string>();
|
||||
// 2. Criar Símbolos (Cargos)
|
||||
console.log("💰 Criando símbolos...");
|
||||
const simbolosMap = new Map<string, Id<"simbolos">>();
|
||||
|
||||
for (const simbolo of simbolosData) {
|
||||
const id = await ctx.db.insert("simbolos", {
|
||||
descricao: simbolo.descricao,
|
||||
const simboloId = await ctx.db.insert("simbolos", {
|
||||
nome: simbolo.nome,
|
||||
repValor: simbolo.repValor,
|
||||
descricao: simbolo.descricao,
|
||||
tipo: simbolo.tipo,
|
||||
valor: simbolo.valor,
|
||||
vencValor: simbolo.vencValor,
|
||||
repValor: simbolo.repValor || "",
|
||||
vencValor: simbolo.vencValor || "",
|
||||
});
|
||||
simbolosMap.set(simbolo.nome, id);
|
||||
simbolosMap.set(simbolo.nome, simboloId);
|
||||
console.log(` ✅ Símbolo criado: ${simbolo.nome}`);
|
||||
}
|
||||
|
||||
// 4. Inserir funcionários
|
||||
console.log("👥 Inserindo funcionários...");
|
||||
const funcionariosMap = new Map<string, string>();
|
||||
// 3. Criar Funcionários
|
||||
console.log("👥 Criando funcionários...");
|
||||
const funcionariosMap = new Map<string, Id<"funcionarios">>();
|
||||
|
||||
for (const funcionario of funcionariosData) {
|
||||
const simboloId = simbolosMap.get(funcionario.simboloNome);
|
||||
if (!simboloId) {
|
||||
console.error(
|
||||
console.log(
|
||||
` ❌ Símbolo não encontrado: ${funcionario.simboloNome}`
|
||||
);
|
||||
continue;
|
||||
@@ -459,7 +299,7 @@ export const seedDatabase = internalMutation({
|
||||
nascimento: funcionario.nascimento,
|
||||
nome: funcionario.nome,
|
||||
rg: funcionario.rg,
|
||||
simboloId: simboloId as any,
|
||||
simboloId: simboloId as Id<"simbolos">,
|
||||
simboloTipo: funcionario.simboloTipo,
|
||||
telefone: funcionario.telefone,
|
||||
uf: funcionario.uf,
|
||||
@@ -480,8 +320,8 @@ export const seedDatabase = internalMutation({
|
||||
senhaHash: senhaInicial,
|
||||
nome: funcionario.nome,
|
||||
email: funcionario.email,
|
||||
funcionarioId: funcId as any,
|
||||
roleId: roleUsuario as any,
|
||||
funcionarioId: funcId as Id<"funcionarios">,
|
||||
roleId: roleUsuario,
|
||||
ativo: true,
|
||||
primeiroAcesso: true,
|
||||
criadoEm: Date.now(),
|
||||
@@ -522,80 +362,28 @@ export const seedDatabase = internalMutation({
|
||||
}
|
||||
|
||||
await ctx.db.insert("solicitacoesAcesso", dadosSolicitacao);
|
||||
console.log(` ✅ Solicitação criada: ${solicitacao.nome}`);
|
||||
console.log(
|
||||
` ✅ Solicitação criada: ${solicitacao.nome} (${solicitacao.status})`
|
||||
);
|
||||
}
|
||||
|
||||
// 7. Criar templates de mensagens padrão
|
||||
console.log("📧 Criando templates de mensagens padrão...");
|
||||
const templatesPadrao = [
|
||||
{
|
||||
codigo: "USUARIO_BLOQUEADO",
|
||||
nome: "Usuário Bloqueado",
|
||||
titulo: "Sua conta foi bloqueada",
|
||||
corpo:
|
||||
"Sua conta no SGSE foi bloqueada.\\n\\nMotivo: {{motivo}}\\n\\nPara mais informações, entre em contato com a TI.",
|
||||
variaveis: ["motivo"],
|
||||
},
|
||||
{
|
||||
codigo: "USUARIO_DESBLOQUEADO",
|
||||
nome: "Usuário Desbloqueado",
|
||||
titulo: "Sua conta foi desbloqueada",
|
||||
corpo:
|
||||
"Sua conta no SGSE foi desbloqueada e você já pode acessar o sistema normalmente.",
|
||||
variaveis: [],
|
||||
},
|
||||
{
|
||||
codigo: "SENHA_RESETADA",
|
||||
nome: "Senha Resetada",
|
||||
titulo: "Sua senha foi resetada",
|
||||
corpo:
|
||||
"Sua senha foi resetada pela equipe de TI.\\n\\nNova senha temporária: {{senha}}\\n\\nPor favor, altere sua senha no próximo login.",
|
||||
variaveis: ["senha"],
|
||||
},
|
||||
{
|
||||
codigo: "PERMISSAO_ALTERADA",
|
||||
nome: "Permissão Alterada",
|
||||
titulo: "Suas permissões foram atualizadas",
|
||||
corpo:
|
||||
"Suas permissões de acesso ao sistema foram atualizadas.\\n\\nPara verificar suas novas permissões, acesse o menu de perfil.",
|
||||
variaveis: [],
|
||||
},
|
||||
{
|
||||
codigo: "AVISO_GERAL",
|
||||
nome: "Aviso Geral",
|
||||
titulo: "{{titulo}}",
|
||||
corpo: "{{mensagem}}",
|
||||
variaveis: ["titulo", "mensagem"],
|
||||
},
|
||||
{
|
||||
codigo: "BEM_VINDO",
|
||||
nome: "Boas-vindas",
|
||||
titulo: "Bem-vindo ao SGSE",
|
||||
corpo:
|
||||
"Olá {{nome}},\\n\\nSeja bem-vindo ao Sistema de Gestão da Secretaria de Esportes!\\n\\nSuas credenciais de acesso:\\nMatrícula: {{matricula}}\\nSenha temporária: {{senha}}\\n\\nPor favor, altere sua senha no primeiro acesso.\\n\\nEquipe de TI",
|
||||
variaveis: ["nome", "matricula", "senha"],
|
||||
},
|
||||
];
|
||||
console.log("✨ Seed do banco de dados concluído com sucesso!");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
for (const template of templatesPadrao) {
|
||||
await ctx.db.insert("templatesMensagens", {
|
||||
codigo: template.codigo,
|
||||
nome: template.nome,
|
||||
tipo: "sistema" as const,
|
||||
titulo: template.titulo,
|
||||
corpo: template.corpo,
|
||||
variaveis: template.variaveis,
|
||||
criadoEm: Date.now(),
|
||||
});
|
||||
console.log(` ✅ Template criado: ${template.nome}`);
|
||||
}
|
||||
|
||||
console.log("✨ Seed concluído com sucesso!");
|
||||
console.log("");
|
||||
console.log("🔑 CREDENCIAIS DE ACESSO:");
|
||||
console.log(" Admin: matrícula 0000, senha Admin@123");
|
||||
console.log(" TI: matrícula 1000, senha TI@123");
|
||||
console.log(" Funcionários: usar matrícula, senha Mudar@123");
|
||||
/**
|
||||
* Mutation pública para popular o banco de dados com os dados de seed
|
||||
* Permite executar via CLI: `npx convex run seed:popularBanco`
|
||||
*/
|
||||
export const popularBanco = mutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
console.log("🌱 Executando popularBanco (wrapper público para seedDatabase)...");
|
||||
// Chama a internalMutation para reaproveitar a lógica de seed
|
||||
await ctx.runMutation(internal.seed.seedDatabase, {});
|
||||
console.log("✅ Seed concluído pelo wrapper público");
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -738,16 +526,7 @@ export const clearDatabase = internalMutation({
|
||||
);
|
||||
|
||||
// 9. Perfis customizados
|
||||
const perfisCustomizados = await ctx.db
|
||||
.query("perfisCustomizados")
|
||||
.collect();
|
||||
for (const perfil of perfisCustomizados) {
|
||||
await ctx.db.delete(perfil._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${perfisCustomizados.length} perfis customizados removidos`
|
||||
);
|
||||
|
||||
|
||||
// 10. Templates de mensagens
|
||||
const templatesMensagens = await ctx.db
|
||||
.query("templatesMensagens")
|
||||
@@ -809,23 +588,9 @@ export const clearDatabase = internalMutation({
|
||||
console.log(` ✅ ${sessoes.length} sessões removidas`);
|
||||
|
||||
// 14. Menu-permissões personalizadas
|
||||
const menuPermissoesPersonalizadas = await ctx.db
|
||||
.query("menuPermissoesPersonalizadas")
|
||||
.collect();
|
||||
for (const mpp of menuPermissoesPersonalizadas) {
|
||||
await ctx.db.delete(mpp._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${menuPermissoesPersonalizadas.length} menu-permissões personalizadas removidas`
|
||||
);
|
||||
|
||||
|
||||
// 15. Menu-permissões
|
||||
const menuPermissoes = await ctx.db.query("menuPermissoes").collect();
|
||||
for (const mp of menuPermissoes) {
|
||||
await ctx.db.delete(mp._id);
|
||||
}
|
||||
console.log(` ✅ ${menuPermissoes.length} menu-permissões removidas`);
|
||||
|
||||
|
||||
// 16. Role-permissões
|
||||
const rolePermissoes = await ctx.db.query("rolePermissoes").collect();
|
||||
for (const rp of rolePermissoes) {
|
||||
@@ -890,3 +655,301 @@ export const clearDatabase = internalMutation({
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Mutation pública para limpar o banco de dados (para uso via CLI)
|
||||
* ATENÇÃO: Esta função deleta TODOS os dados do banco!
|
||||
*/
|
||||
export const limparBanco = mutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
// Executa diretamente a limpeza (mesmo código da internalMutation)
|
||||
console.log("🗑️ Limpando banco de dados...");
|
||||
|
||||
// Limpar em ordem (respeitando dependências)
|
||||
|
||||
// 1. Tabelas de logs e auditoria
|
||||
const logsAcesso = await ctx.db.query("logsAcesso").collect();
|
||||
for (const log of logsAcesso) {
|
||||
await ctx.db.delete(log._id);
|
||||
}
|
||||
console.log(` ✅ ${logsAcesso.length} logs de acesso removidos`);
|
||||
|
||||
const logsLogin = await ctx.db.query("logsLogin").collect();
|
||||
for (const log of logsLogin) {
|
||||
await ctx.db.delete(log._id);
|
||||
}
|
||||
console.log(` ✅ ${logsLogin.length} logs de login removidos`);
|
||||
|
||||
const logsAtividades = await ctx.db.query("logsAtividades").collect();
|
||||
for (const log of logsAtividades) {
|
||||
await ctx.db.delete(log._id);
|
||||
}
|
||||
console.log(` ✅ ${logsAtividades.length} logs de atividades removidos`);
|
||||
|
||||
// 2. Sistema de chat
|
||||
const leituras = await ctx.db.query("leituras").collect();
|
||||
for (const leitura of leituras) {
|
||||
await ctx.db.delete(leitura._id);
|
||||
}
|
||||
console.log(` ✅ ${leituras.length} leituras removidas`);
|
||||
|
||||
const mensagens = await ctx.db.query("mensagens").collect();
|
||||
for (const mensagem of mensagens) {
|
||||
await ctx.db.delete(mensagem._id);
|
||||
}
|
||||
console.log(` ✅ ${mensagens.length} mensagens removidas`);
|
||||
|
||||
const digitando = await ctx.db.query("digitando").collect();
|
||||
for (const d of digitando) {
|
||||
await ctx.db.delete(d._id);
|
||||
}
|
||||
console.log(` ✅ ${digitando.length} registros de digitando removidos`);
|
||||
|
||||
const conversas = await ctx.db.query("conversas").collect();
|
||||
for (const conversa of conversas) {
|
||||
await ctx.db.delete(conversa._id);
|
||||
}
|
||||
console.log(` ✅ ${conversas.length} conversas removidas`);
|
||||
|
||||
// 3. Notificações
|
||||
const notificacoes = await ctx.db.query("notificacoes").collect();
|
||||
for (const notificacao of notificacoes) {
|
||||
await ctx.db.delete(notificacao._id);
|
||||
}
|
||||
console.log(` ✅ ${notificacoes.length} notificações removidas`);
|
||||
|
||||
const notificacoesEmail = await ctx.db.query("notificacoesEmail").collect();
|
||||
for (const email of notificacoesEmail) {
|
||||
await ctx.db.delete(email._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${notificacoesEmail.length} notificações de email removidas`
|
||||
);
|
||||
|
||||
const notificacoesFerias = await ctx.db
|
||||
.query("notificacoesFerias")
|
||||
.collect();
|
||||
for (const notif of notificacoesFerias) {
|
||||
await ctx.db.delete(notif._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${notificacoesFerias.length} notificações de férias removidas`
|
||||
);
|
||||
|
||||
// 4. Férias e períodos aquisitivos
|
||||
const solicitacoesFerias = await ctx.db
|
||||
.query("solicitacoesFerias")
|
||||
.collect();
|
||||
for (const solicitacao of solicitacoesFerias) {
|
||||
await ctx.db.delete(solicitacao._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${solicitacoesFerias.length} solicitações de férias removidas`
|
||||
);
|
||||
|
||||
const periodosAquisitivos = await ctx.db
|
||||
.query("periodosAquisitivos")
|
||||
.collect();
|
||||
for (const periodo of periodosAquisitivos) {
|
||||
await ctx.db.delete(periodo._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${periodosAquisitivos.length} períodos aquisitivos removidos`
|
||||
);
|
||||
|
||||
// 5. Atestados
|
||||
const atestados = await ctx.db.query("atestados").collect();
|
||||
for (const atestado of atestados) {
|
||||
await ctx.db.delete(atestado._id);
|
||||
}
|
||||
console.log(` ✅ ${atestados.length} atestados removidos`);
|
||||
|
||||
// 6. Times e membros
|
||||
const timesMembros = await ctx.db.query("timesMembros").collect();
|
||||
for (const membro of timesMembros) {
|
||||
await ctx.db.delete(membro._id);
|
||||
}
|
||||
console.log(` ✅ ${timesMembros.length} membros de times removidos`);
|
||||
|
||||
const times = await ctx.db.query("times").collect();
|
||||
for (const time of times) {
|
||||
await ctx.db.delete(time._id);
|
||||
}
|
||||
console.log(` ✅ ${times.length} times removidos`);
|
||||
|
||||
// 7. Cursos
|
||||
const cursos = await ctx.db.query("cursos").collect();
|
||||
for (const curso of cursos) {
|
||||
await ctx.db.delete(curso._id);
|
||||
}
|
||||
console.log(` ✅ ${cursos.length} cursos removidos`);
|
||||
|
||||
// 8. Bloqueios de usuários
|
||||
const bloqueiosUsuarios = await ctx.db.query("bloqueiosUsuarios").collect();
|
||||
for (const bloqueio of bloqueiosUsuarios) {
|
||||
await ctx.db.delete(bloqueio._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${bloqueiosUsuarios.length} bloqueios de usuários removidos`
|
||||
);
|
||||
|
||||
// 9. Perfis customizados (já está no código da internalMutation mas vazio)
|
||||
|
||||
// 10. Templates de mensagens
|
||||
const templatesMensagens = await ctx.db
|
||||
.query("templatesMensagens")
|
||||
.collect();
|
||||
for (const template of templatesMensagens) {
|
||||
await ctx.db.delete(template._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${templatesMensagens.length} templates de mensagens removidos`
|
||||
);
|
||||
|
||||
// 11. Configurações
|
||||
const configuracaoEmail = await ctx.db.query("configuracaoEmail").collect();
|
||||
for (const config of configuracaoEmail) {
|
||||
await ctx.db.delete(config._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${configuracaoEmail.length} configurações de email removidas`
|
||||
);
|
||||
|
||||
const configuracaoAcesso = await ctx.db
|
||||
.query("configuracaoAcesso")
|
||||
.collect();
|
||||
for (const config of configuracaoAcesso) {
|
||||
await ctx.db.delete(config._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${configuracaoAcesso.length} configurações de acesso removidas`
|
||||
);
|
||||
|
||||
// 12. Monitoramento
|
||||
const alertHistory = await ctx.db.query("alertHistory").collect();
|
||||
for (const alert of alertHistory) {
|
||||
await ctx.db.delete(alert._id);
|
||||
}
|
||||
console.log(` ✅ ${alertHistory.length} histórico de alertas removido`);
|
||||
|
||||
const alertConfigurations = await ctx.db
|
||||
.query("alertConfigurations")
|
||||
.collect();
|
||||
for (const alert of alertConfigurations) {
|
||||
await ctx.db.delete(alert._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${alertConfigurations.length} configurações de alertas removidas`
|
||||
);
|
||||
|
||||
const systemMetrics = await ctx.db.query("systemMetrics").collect();
|
||||
for (const metric of systemMetrics) {
|
||||
await ctx.db.delete(metric._id);
|
||||
}
|
||||
console.log(` ✅ ${systemMetrics.length} métricas do sistema removidas`);
|
||||
|
||||
// 13. Sessões
|
||||
const sessoes = await ctx.db.query("sessoes").collect();
|
||||
for (const sessao of sessoes) {
|
||||
await ctx.db.delete(sessao._id);
|
||||
}
|
||||
console.log(` ✅ ${sessoes.length} sessões removidas`);
|
||||
|
||||
// 14. Menu-permissões personalizadas (já está no código da internalMutation mas vazio)
|
||||
|
||||
// 15. Menu-permissões (já está no código da internalMutation mas vazio)
|
||||
|
||||
// 16. Role-permissões
|
||||
const rolePermissoes = await ctx.db.query("rolePermissoes").collect();
|
||||
for (const rp of rolePermissoes) {
|
||||
await ctx.db.delete(rp._id);
|
||||
}
|
||||
console.log(` ✅ ${rolePermissoes.length} role-permissões removidas`);
|
||||
|
||||
// 17. Permissões
|
||||
const permissoes = await ctx.db.query("permissoes").collect();
|
||||
for (const permissao of permissoes) {
|
||||
await ctx.db.delete(permissao._id);
|
||||
}
|
||||
console.log(` ✅ ${permissoes.length} permissões removidas`);
|
||||
|
||||
// 18. Usuários (deve vir antes de roles se houver referência)
|
||||
const usuarios = await ctx.db.query("usuarios").collect();
|
||||
for (const usuario of usuarios) {
|
||||
await ctx.db.delete(usuario._id);
|
||||
}
|
||||
console.log(` ✅ ${usuarios.length} usuários removidos`);
|
||||
|
||||
// 19. Funcionários
|
||||
const funcionarios = await ctx.db.query("funcionarios").collect();
|
||||
for (const funcionario of funcionarios) {
|
||||
await ctx.db.delete(funcionario._id);
|
||||
}
|
||||
console.log(` ✅ ${funcionarios.length} funcionários removidos`);
|
||||
|
||||
// 20. Solicitações de acesso
|
||||
const solicitacoesAcesso = await ctx.db
|
||||
.query("solicitacoesAcesso")
|
||||
.collect();
|
||||
for (const solicitacao of solicitacoesAcesso) {
|
||||
await ctx.db.delete(solicitacao._id);
|
||||
}
|
||||
console.log(
|
||||
` ✅ ${solicitacoesAcesso.length} solicitações de acesso removidas`
|
||||
);
|
||||
|
||||
// 21. Símbolos
|
||||
const simbolos = await ctx.db.query("simbolos").collect();
|
||||
for (const simbolo of simbolos) {
|
||||
await ctx.db.delete(simbolo._id);
|
||||
}
|
||||
console.log(` ✅ ${simbolos.length} símbolos removidos`);
|
||||
|
||||
// 22. Roles (deve vir por último se outras tabelas referenciam)
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
for (const role of roles) {
|
||||
await ctx.db.delete(role._id);
|
||||
}
|
||||
console.log(` ✅ ${roles.length} roles removidas`);
|
||||
|
||||
// 23. Todos (tabela de exemplo)
|
||||
const todos = await ctx.db.query("todos").collect();
|
||||
for (const todo of todos) {
|
||||
await ctx.db.delete(todo._id);
|
||||
}
|
||||
console.log(` ✅ ${todos.length} todos removidos`);
|
||||
|
||||
console.log("✨ Banco de dados completamente limpo!");
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Query para verificar quantos registros existem no banco
|
||||
*/
|
||||
export const verificarBanco = query({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
usuarios: v.number(),
|
||||
funcionarios: v.number(),
|
||||
roles: v.number(),
|
||||
simbolos: v.number(),
|
||||
total: v.number(),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const usuarios = await ctx.db.query("usuarios").collect();
|
||||
const funcionarios = await ctx.db.query("funcionarios").collect();
|
||||
const roles = await ctx.db.query("roles").collect();
|
||||
const simbolos = await ctx.db.query("simbolos").collect();
|
||||
|
||||
return {
|
||||
usuarios: usuarios.length,
|
||||
funcionarios: funcionarios.length,
|
||||
roles: roles.length,
|
||||
simbolos: simbolos.length,
|
||||
total: usuarios.length + funcionarios.length + roles.length + simbolos.length,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -128,36 +128,69 @@ export const listar = query({
|
||||
matricula: v.optional(v.string()),
|
||||
ativo: v.optional(v.boolean()),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("usuarios"),
|
||||
matricula: v.string(),
|
||||
nome: v.string(),
|
||||
email: v.string(),
|
||||
ativo: v.boolean(),
|
||||
bloqueado: v.optional(v.boolean()),
|
||||
motivoBloqueio: v.optional(v.string()),
|
||||
primeiroAcesso: v.boolean(),
|
||||
ultimoAcesso: v.optional(v.number()),
|
||||
criadoEm: v.number(),
|
||||
role: v.object({
|
||||
_id: v.id("roles"),
|
||||
nome: v.string(),
|
||||
nivel: v.number(),
|
||||
setor: v.optional(v.string()),
|
||||
}),
|
||||
funcionario: v.optional(
|
||||
v.object({
|
||||
_id: v.id("funcionarios"),
|
||||
nome: v.string(),
|
||||
simboloTipo: v.union(
|
||||
v.literal("cargo_comissionado"),
|
||||
v.literal("funcao_gratificada")
|
||||
),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
// returns: v.array(
|
||||
// v.object({
|
||||
// _id: v.id("usuarios"),
|
||||
// matricula: v.string(),
|
||||
// nome: v.string(),
|
||||
// email: v.string(),
|
||||
// ativo: v.boolean(),
|
||||
// bloqueado: v.optional(v.boolean()),
|
||||
// motivoBloqueio: v.optional(v.string()),
|
||||
// primeiroAcesso: v.boolean(),
|
||||
// ultimoAcesso: v.optional(v.number()),
|
||||
// criadoEm: v.number(),
|
||||
// role: v.union(
|
||||
// v.object({
|
||||
// _id: v.id("roles"),
|
||||
// _creationTime: v.optional(v.number()),
|
||||
// criadoPor: v.optional(v.id("usuarios")),
|
||||
// customizado: v.optional(v.boolean()),
|
||||
// descricao: v.string(),
|
||||
// editavel: v.optional(v.boolean()),
|
||||
// nome: v.string(),
|
||||
// nivel: v.number(),
|
||||
// setor: v.optional(v.string()),
|
||||
// }),
|
||||
// v.object({
|
||||
// _id: v.id("roles"),
|
||||
// _creationTime: v.optional(v.number()),
|
||||
// criadoPor: v.optional(v.id("usuarios")),
|
||||
// customizado: v.optional(v.boolean()),
|
||||
// descricao: v.literal("Perfil não encontrado"),
|
||||
// editavel: v.optional(v.boolean()),
|
||||
// nome: v.literal("erro_role_ausente"),
|
||||
// nivel: v.literal(999),
|
||||
// setor: v.optional(v.string()),
|
||||
// erro: v.literal(true),
|
||||
// })
|
||||
// ),
|
||||
// funcionario: v.optional(
|
||||
// v.object({
|
||||
// _id: v.id("funcionarios"),
|
||||
// nome: v.string(),
|
||||
// matricula: v.optional(v.string()),
|
||||
// descricaoCargo: v.optional(v.string()),
|
||||
// simboloTipo: v.union(
|
||||
// v.literal("cargo_comissionado"),
|
||||
// v.literal("funcao_gratificada")
|
||||
// ),
|
||||
// })
|
||||
// ),
|
||||
// avisos: v.optional(
|
||||
// v.array(
|
||||
// v.object({
|
||||
// tipo: v.union(
|
||||
// v.literal("erro"),
|
||||
// v.literal("aviso"),
|
||||
// v.literal("info")
|
||||
// ),
|
||||
// mensagem: v.string(),
|
||||
// })
|
||||
// )
|
||||
// ),
|
||||
// })
|
||||
// ),
|
||||
handler: async (ctx, args) => {
|
||||
let usuarios = await ctx.db.query("usuarios").collect();
|
||||
|
||||
@@ -173,46 +206,136 @@ export const listar = query({
|
||||
|
||||
// Buscar roles e funcionários
|
||||
const resultado = [];
|
||||
const usuariosSemRole: Array<{ nome: string; matricula: string; roleId: Id<"roles"> }> = [];
|
||||
|
||||
for (const usuario of usuarios) {
|
||||
const role = await ctx.db.get(usuario.roleId);
|
||||
if (!role) continue;
|
||||
try {
|
||||
const role = await ctx.db.get(usuario.roleId);
|
||||
|
||||
// Se a role não existe, criar uma role de erro mas ainda incluir o usuário
|
||||
if (!role) {
|
||||
usuariosSemRole.push({
|
||||
nome: usuario.nome,
|
||||
matricula: usuario.matricula,
|
||||
roleId: usuario.roleId,
|
||||
});
|
||||
|
||||
// Filtrar por setor - se filtro está ativo e role não existe, pular
|
||||
if (args.setor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filtrar por setor
|
||||
if (args.setor && role.setor !== args.setor) {
|
||||
continue;
|
||||
}
|
||||
// Incluir usuário com role de erro
|
||||
let funcionario = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
try {
|
||||
const func = await ctx.db.get(usuario.funcionarioId);
|
||||
if (func) {
|
||||
funcionario = {
|
||||
_id: func._id,
|
||||
nome: func.nome,
|
||||
matricula: func.matricula,
|
||||
descricaoCargo: func.descricaoCargo,
|
||||
simboloTipo: func.simboloTipo,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
let funcionario = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
const func = await ctx.db.get(usuario.funcionarioId);
|
||||
if (func) {
|
||||
funcionario = {
|
||||
_id: func._id,
|
||||
nome: func.nome,
|
||||
simboloTipo: func.simboloTipo,
|
||||
};
|
||||
// Criar role de erro (sem _creationTime pois a role não existe)
|
||||
resultado.push({
|
||||
_id: usuario._id,
|
||||
matricula: usuario.matricula,
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
ativo: usuario.ativo,
|
||||
bloqueado: usuario.bloqueado,
|
||||
motivoBloqueio: usuario.motivoBloqueio,
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
ultimoAcesso: usuario.ultimoAcesso,
|
||||
criadoEm: usuario.criadoEm,
|
||||
role: {
|
||||
_id: usuario.roleId,
|
||||
descricao: "Perfil não encontrado" as const,
|
||||
nome: "erro_role_ausente" as const,
|
||||
nivel: 999 as const,
|
||||
erro: true as const,
|
||||
},
|
||||
funcionario,
|
||||
avisos: [
|
||||
{
|
||||
tipo: "erro" as const,
|
||||
mensagem: `Perfil de acesso (ID: ${usuario.roleId}) não encontrado. Este usuário precisa ter seu perfil reatribuído.`,
|
||||
},
|
||||
],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
resultado.push({
|
||||
_id: usuario._id,
|
||||
matricula: usuario.matricula,
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
ativo: usuario.ativo,
|
||||
bloqueado: usuario.bloqueado,
|
||||
motivoBloqueio: usuario.motivoBloqueio,
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
ultimoAcesso: usuario.ultimoAcesso,
|
||||
criadoEm: usuario.criadoEm,
|
||||
role: {
|
||||
// Filtrar por setor
|
||||
if (args.setor && role.setor !== args.setor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Buscar funcionário associado
|
||||
let funcionario = undefined;
|
||||
if (usuario.funcionarioId) {
|
||||
try {
|
||||
const func = await ctx.db.get(usuario.funcionarioId);
|
||||
if (func) {
|
||||
funcionario = {
|
||||
_id: func._id,
|
||||
nome: func.nome,
|
||||
matricula: func.matricula,
|
||||
descricaoCargo: func.descricaoCargo,
|
||||
simboloTipo: func.simboloTipo,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Construir objeto role - incluir _creationTime se existir (campo automático do Convex)
|
||||
const roleObj = {
|
||||
_id: role._id,
|
||||
descricao: role.descricao,
|
||||
nome: role.nome,
|
||||
nivel: role.nivel,
|
||||
setor: role.setor,
|
||||
},
|
||||
funcionario,
|
||||
});
|
||||
...(role.criadoPor !== undefined && { criadoPor: role.criadoPor }),
|
||||
...(role.customizado !== undefined && { customizado: role.customizado }),
|
||||
...(role.editavel !== undefined && { editavel: role.editavel }),
|
||||
...(role.setor !== undefined && { setor: role.setor }),
|
||||
};
|
||||
|
||||
resultado.push({
|
||||
_id: usuario._id,
|
||||
matricula: usuario.matricula,
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
ativo: usuario.ativo,
|
||||
bloqueado: usuario.bloqueado,
|
||||
motivoBloqueio: usuario.motivoBloqueio,
|
||||
primeiroAcesso: usuario.primeiroAcesso,
|
||||
ultimoAcesso: usuario.ultimoAcesso,
|
||||
criadoEm: usuario.criadoEm,
|
||||
role: roleObj,
|
||||
funcionario,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Erro ao processar usuário ${usuario._id}:`, error);
|
||||
// Continua processando outros usuários mesmo se houver erro em um
|
||||
}
|
||||
}
|
||||
|
||||
// Log de usuários sem role para depuração
|
||||
if (usuariosSemRole.length > 0) {
|
||||
console.warn(
|
||||
`⚠️ Encontrados ${usuariosSemRole.length} usuário(s) com perfil ausente:`,
|
||||
usuariosSemRole.map((u) => `${u.nome} (${u.matricula}) - RoleID: ${u.roleId}`)
|
||||
);
|
||||
}
|
||||
|
||||
return resultado;
|
||||
|
||||
Reference in New Issue
Block a user