refactor: improve layout and backend monitoring functionality - Streamlined the layout component in Svelte for better readability and consistency. - Enhanced the backend monitoring functions by updating argument structures and improving code clarity. - A #10
@@ -1,12 +0,0 @@
|
|||||||
# EditorConfig is awesome: https://EditorConfig.org
|
|
||||||
|
|
||||||
# top-most EditorConfig file
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
insert_final_newline = false
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,211 +1,184 @@
|
|||||||
import { query, mutation, internalQuery } from "./_generated/server";
|
import { query, mutation, internalQuery } from './_generated/server';
|
||||||
import { v } from "convex/values";
|
import { v } from 'convex/values';
|
||||||
import type { Doc } from "./_generated/dataModel";
|
import type { Doc } from './_generated/dataModel';
|
||||||
|
import { getCurrentUserFunction } from './auth';
|
||||||
|
|
||||||
// Catálogo base de recursos e ações
|
// Catálogo base de recursos e ações
|
||||||
// Ajuste/expanda conforme os módulos disponíveis no sistema
|
// Ajuste/expanda conforme os módulos disponíveis no sistema
|
||||||
export const CATALOGO_RECURSOS = [
|
export const CATALOGO_RECURSOS = [
|
||||||
{
|
{
|
||||||
recurso: "funcionarios",
|
recurso: 'funcionarios',
|
||||||
acoes: ["dashboard", "ver", "listar", "criar", "editar", "excluir"],
|
acoes: ['dashboard', 'ver', 'listar', 'criar', 'editar', 'excluir']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
recurso: "simbolos",
|
recurso: 'simbolos',
|
||||||
acoes: ["dashboard", "ver", "listar", "criar", "editar", "excluir"],
|
acoes: ['dashboard', 'ver', 'listar', 'criar', 'editar', 'excluir']
|
||||||
},
|
}
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const listarRecursosEAcoes = query({
|
export const listarRecursosEAcoes = query({
|
||||||
args: {},
|
args: {},
|
||||||
returns: v.array(
|
returns: v.array(
|
||||||
v.object({
|
v.object({
|
||||||
recurso: v.string(),
|
recurso: v.string(),
|
||||||
acoes: v.array(v.string()),
|
acoes: v.array(v.string())
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
return CATALOGO_RECURSOS.map((r) => ({
|
return CATALOGO_RECURSOS.map((r) => ({
|
||||||
recurso: r.recurso,
|
recurso: r.recurso,
|
||||||
acoes: [...r.acoes],
|
acoes: [...r.acoes]
|
||||||
}));
|
}));
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const listarPermissoesAcoesPorRole = query({
|
export const listarPermissoesAcoesPorRole = query({
|
||||||
args: { roleId: v.id("roles") },
|
args: { roleId: v.id('roles') },
|
||||||
returns: v.array(
|
returns: v.array(
|
||||||
v.object({
|
v.object({
|
||||||
recurso: v.string(),
|
recurso: v.string(),
|
||||||
acoes: v.array(v.string()),
|
acoes: v.array(v.string())
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Buscar vínculos permissao<-role
|
// Buscar vínculos permissao<-role
|
||||||
const rolePerms = await ctx.db
|
const rolePerms = await ctx.db
|
||||||
.query("rolePermissoes")
|
.query('rolePermissoes')
|
||||||
.withIndex("by_role", (q) => q.eq("roleId", args.roleId))
|
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Carregar documentos de permissões
|
// Carregar documentos de permissões
|
||||||
const actionsByResource: Record<string, Set<string>> = {};
|
const actionsByResource: Record<string, Set<string>> = {};
|
||||||
for (const rp of rolePerms) {
|
for (const rp of rolePerms) {
|
||||||
const perm = await ctx.db.get(rp.permissaoId);
|
const perm = await ctx.db.get(rp.permissaoId);
|
||||||
if (!perm) continue;
|
if (!perm) continue;
|
||||||
const set = (actionsByResource[perm.recurso] ||= new Set<string>());
|
const set = (actionsByResource[perm.recurso] ||= new Set<string>());
|
||||||
set.add(perm.acao);
|
set.add(perm.acao);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalizar para todos os recursos do catálogo
|
// Normalizar para todos os recursos do catálogo
|
||||||
const result: Array<{ recurso: string; acoes: Array<string> }> = [];
|
const result: Array<{ recurso: string; acoes: Array<string> }> = [];
|
||||||
for (const item of CATALOGO_RECURSOS) {
|
for (const item of CATALOGO_RECURSOS) {
|
||||||
const granted = Array.from(
|
const granted = Array.from(actionsByResource[item.recurso] ?? new Set<string>());
|
||||||
actionsByResource[item.recurso] ?? new Set<string>()
|
result.push({ recurso: item.recurso, acoes: granted });
|
||||||
);
|
}
|
||||||
result.push({ recurso: item.recurso, acoes: granted });
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const atualizarPermissaoAcao = mutation({
|
export const atualizarPermissaoAcao = mutation({
|
||||||
args: {
|
args: {
|
||||||
roleId: v.id("roles"),
|
roleId: v.id('roles'),
|
||||||
recurso: v.string(),
|
recurso: v.string(),
|
||||||
acao: v.string(),
|
acao: v.string(),
|
||||||
conceder: v.boolean(),
|
conceder: v.boolean()
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Garantir documento de permissão (recurso+acao)
|
// Garantir documento de permissão (recurso+acao)
|
||||||
let permissao = await ctx.db
|
let permissao = await ctx.db
|
||||||
.query("permissoes")
|
.query('permissoes')
|
||||||
.withIndex("by_recurso_e_acao", (q) =>
|
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||||||
q.eq("recurso", args.recurso).eq("acao", args.acao)
|
.first();
|
||||||
)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (!permissao) {
|
if (!permissao) {
|
||||||
const nome = `${args.recurso}.${args.acao}`;
|
const nome = `${args.recurso}.${args.acao}`;
|
||||||
const descricao = `Permite ${args.acao} em ${args.recurso}`;
|
const descricao = `Permite ${args.acao} em ${args.recurso}`;
|
||||||
const id = await ctx.db.insert("permissoes", {
|
const id = await ctx.db.insert('permissoes', {
|
||||||
nome,
|
nome,
|
||||||
descricao,
|
descricao,
|
||||||
recurso: args.recurso,
|
recurso: args.recurso,
|
||||||
acao: args.acao,
|
acao: args.acao
|
||||||
});
|
});
|
||||||
permissao = await ctx.db.get(id);
|
permissao = await ctx.db.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!permissao) return null;
|
if (!permissao) return null;
|
||||||
|
|
||||||
// Verificar vínculo atual
|
// Verificar vínculo atual
|
||||||
const existente = await ctx.db
|
const existente = await ctx.db
|
||||||
.query("rolePermissoes")
|
.query('rolePermissoes')
|
||||||
.withIndex("by_role", (q) => q.eq("roleId", args.roleId))
|
.withIndex('by_role', (q) => q.eq('roleId', args.roleId))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
const vinculo = existente.find((rp) => rp.permissaoId === permissao!._id);
|
const vinculo = existente.find((rp) => rp.permissaoId === permissao!._id);
|
||||||
|
|
||||||
if (args.conceder) {
|
if (args.conceder) {
|
||||||
if (!vinculo) {
|
if (!vinculo) {
|
||||||
await ctx.db.insert("rolePermissoes", {
|
await ctx.db.insert('rolePermissoes', {
|
||||||
roleId: args.roleId,
|
roleId: args.roleId,
|
||||||
permissaoId: permissao._id,
|
permissaoId: permissao._id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (vinculo) {
|
if (vinculo) {
|
||||||
await ctx.db.delete(vinculo._id);
|
await ctx.db.delete(vinculo._id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const verificarAcao = query({
|
export const verificarAcao = query({
|
||||||
args: {
|
args: {
|
||||||
usuarioId: v.id("usuarios"),
|
usuarioId: v.id('usuarios'),
|
||||||
recurso: v.string(),
|
recurso: v.string(),
|
||||||
acao: v.string(),
|
acao: v.string()
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const usuario = await ctx.db.get(args.usuarioId);
|
const usuario = await ctx.db.get(args.usuarioId);
|
||||||
if (!usuario) throw new Error("acesso_negado");
|
if (!usuario) throw new Error('acesso_negado');
|
||||||
|
|
||||||
const role = await ctx.db.get(usuario.roleId);
|
const role = await ctx.db.get(usuario.roleId);
|
||||||
if (!role) throw new Error("acesso_negado");
|
if (!role) throw new Error('acesso_negado');
|
||||||
|
|
||||||
// Níveis administrativos têm acesso total
|
// Níveis administrativos têm acesso total
|
||||||
if (role.nivel <= 1) return null;
|
if (role.nivel <= 1) return null;
|
||||||
|
|
||||||
// Encontrar permissão
|
// Encontrar permissão
|
||||||
const permissao = await ctx.db
|
const permissao = await ctx.db
|
||||||
.query("permissoes")
|
.query('permissoes')
|
||||||
.withIndex("by_recurso_e_acao", (q) =>
|
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||||||
q.eq("recurso", args.recurso).eq("acao", args.acao)
|
.first();
|
||||||
)
|
if (!permissao) throw new Error('acesso_negado');
|
||||||
.first();
|
|
||||||
if (!permissao) throw new Error("acesso_negado");
|
|
||||||
|
|
||||||
const hasLink = await ctx.db
|
const hasLink = await ctx.db
|
||||||
.query("rolePermissoes")
|
.query('rolePermissoes')
|
||||||
.withIndex("by_role", (q) => q.eq("roleId", usuario.roleId))
|
.withIndex('by_role', (q) => q.eq('roleId', usuario.roleId))
|
||||||
.collect();
|
.collect();
|
||||||
const permitido = hasLink.some((rp) => rp.permissaoId === permissao!._id);
|
const permitido = hasLink.some((rp) => rp.permissaoId === permissao!._id);
|
||||||
if (!permitido) throw new Error("acesso_negado");
|
if (!permitido) throw new Error('acesso_negado');
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const assertPermissaoAcaoAtual = internalQuery({
|
export const assertPermissaoAcaoAtual = internalQuery({
|
||||||
args: {
|
args: {
|
||||||
recurso: v.string(),
|
recurso: v.string(),
|
||||||
acao: v.string(),
|
acao: v.string()
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const identity = await ctx.auth.getUserIdentity();
|
const usuarioAtual: Doc<'usuarios'> | null = (await getCurrentUserFunction(ctx)) ?? null;
|
||||||
let usuarioAtual: Doc<"usuarios"> | null = null;
|
if (!usuarioAtual) throw new Error('acesso_negado');
|
||||||
|
|
||||||
if (identity && identity.email) {
|
const role = await ctx.db.get(usuarioAtual.roleId);
|
||||||
usuarioAtual = await ctx.db
|
if (!role) throw new Error('acesso_negado');
|
||||||
.query("usuarios")
|
if (role.nivel <= 1) return null;
|
||||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
|
||||||
.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usuarioAtual) {
|
const permissao = await ctx.db
|
||||||
const sessaoAtiva = await ctx.db
|
.query('permissoes')
|
||||||
.query("sessoes")
|
.withIndex('by_recurso_e_acao', (q) => q.eq('recurso', args.recurso).eq('acao', args.acao))
|
||||||
.filter((q) => q.eq(q.field("ativo"), true))
|
.first();
|
||||||
.order("desc")
|
if (!permissao) throw new Error('acesso_negado');
|
||||||
.first();
|
|
||||||
if (sessaoAtiva) {
|
|
||||||
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usuarioAtual) throw new Error("acesso_negado");
|
const links = await ctx.db
|
||||||
|
.query('rolePermissoes')
|
||||||
const role = await ctx.db.get(usuarioAtual.roleId);
|
.withIndex('by_role', (q) => q.eq('roleId', role._id))
|
||||||
if (!role) throw new Error("acesso_negado");
|
.collect();
|
||||||
if (role.nivel <= 1) return null;
|
const ok = links.some((rp) => rp.permissaoId === permissao!._id);
|
||||||
|
if (!ok) throw new Error('acesso_negado');
|
||||||
const permissao = await ctx.db
|
return null;
|
||||||
.query("permissoes")
|
}
|
||||||
.withIndex("by_recurso_e_acao", (q) =>
|
|
||||||
q.eq("recurso", args.recurso).eq("acao", args.acao)
|
|
||||||
)
|
|
||||||
.first();
|
|
||||||
if (!permissao) throw new Error("acesso_negado");
|
|
||||||
|
|
||||||
const links = await ctx.db
|
|
||||||
.query("rolePermissoes")
|
|
||||||
.withIndex("by_role", (q) => q.eq("roleId", role._id))
|
|
||||||
.collect();
|
|
||||||
const ok = links.some((rp) => rp.permissaoId === permissao!._id);
|
|
||||||
if (!ok) throw new Error("acesso_negado");
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,136 +1,113 @@
|
|||||||
import { v } from "convex/values";
|
import { v } from 'convex/values';
|
||||||
import { mutation, query } from "./_generated/server";
|
import { mutation, query } from './_generated/server';
|
||||||
import { Id } from "./_generated/dataModel";
|
import { getCurrentUserFunction } from './auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obter preferências de notificação para uma conversa
|
* Obter preferências de notificação para uma conversa
|
||||||
*/
|
*/
|
||||||
export const obterPreferenciasConversa = query({
|
export const obterPreferenciasConversa = query({
|
||||||
args: {
|
args: {
|
||||||
conversaId: v.id("conversas"),
|
conversaId: v.id('conversas')
|
||||||
},
|
},
|
||||||
returns: v.union(
|
returns: v.union(
|
||||||
v.object({
|
v.object({
|
||||||
pushAtivado: v.boolean(),
|
pushAtivado: v.boolean(),
|
||||||
emailAtivado: v.boolean(),
|
emailAtivado: v.boolean(),
|
||||||
somAtivado: v.boolean(),
|
somAtivado: v.boolean(),
|
||||||
silenciado: v.boolean(),
|
silenciado: v.boolean(),
|
||||||
apenasMencoes: v.boolean(),
|
apenasMencoes: v.boolean()
|
||||||
}),
|
}),
|
||||||
v.null()
|
v.null()
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const identity = await ctx.auth.getUserIdentity();
|
const usuario = await getCurrentUserFunction(ctx);
|
||||||
if (!identity?.email) {
|
if (!usuario) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usuario = await ctx.db
|
const preferencias = await ctx.db
|
||||||
.query("usuarios")
|
.query('preferenciasNotificacaoConversa')
|
||||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
.withIndex('by_usuario_conversa', (q) =>
|
||||||
.first();
|
q.eq('usuarioId', usuario._id).eq('conversaId', args.conversaId)
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
if (!usuario) {
|
if (!preferencias) {
|
||||||
return null;
|
// Retornar valores padrão
|
||||||
}
|
return {
|
||||||
|
pushAtivado: true,
|
||||||
|
emailAtivado: true,
|
||||||
|
somAtivado: true,
|
||||||
|
silenciado: false,
|
||||||
|
apenasMencoes: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const preferencias = await ctx.db
|
return {
|
||||||
.query("preferenciasNotificacaoConversa")
|
pushAtivado: preferencias.pushAtivado,
|
||||||
.withIndex("by_usuario_conversa", (q) =>
|
emailAtivado: preferencias.emailAtivado,
|
||||||
q.eq("usuarioId", usuario._id).eq("conversaId", args.conversaId)
|
somAtivado: preferencias.somAtivado,
|
||||||
)
|
silenciado: preferencias.silenciado,
|
||||||
.first();
|
apenasMencoes: preferencias.apenasMencoes
|
||||||
|
};
|
||||||
if (!preferencias) {
|
}
|
||||||
// Retornar valores padrão
|
|
||||||
return {
|
|
||||||
pushAtivado: true,
|
|
||||||
emailAtivado: true,
|
|
||||||
somAtivado: true,
|
|
||||||
silenciado: false,
|
|
||||||
apenasMencoes: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
pushAtivado: preferencias.pushAtivado,
|
|
||||||
emailAtivado: preferencias.emailAtivado,
|
|
||||||
somAtivado: preferencias.somAtivado,
|
|
||||||
silenciado: preferencias.silenciado,
|
|
||||||
apenasMencoes: preferencias.apenasMencoes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Atualizar preferências de notificação para uma conversa
|
* Atualizar preferências de notificação para uma conversa
|
||||||
*/
|
*/
|
||||||
export const atualizarPreferenciasConversa = mutation({
|
export const atualizarPreferenciasConversa = mutation({
|
||||||
args: {
|
args: {
|
||||||
conversaId: v.id("conversas"),
|
conversaId: v.id('conversas'),
|
||||||
pushAtivado: v.optional(v.boolean()),
|
pushAtivado: v.optional(v.boolean()),
|
||||||
emailAtivado: v.optional(v.boolean()),
|
emailAtivado: v.optional(v.boolean()),
|
||||||
somAtivado: v.optional(v.boolean()),
|
somAtivado: v.optional(v.boolean()),
|
||||||
silenciado: v.optional(v.boolean()),
|
silenciado: v.optional(v.boolean()),
|
||||||
apenasMencoes: v.optional(v.boolean()),
|
apenasMencoes: v.optional(v.boolean())
|
||||||
},
|
},
|
||||||
returns: v.object({ sucesso: v.boolean() }),
|
returns: v.object({ sucesso: v.boolean() }),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const identity = await ctx.auth.getUserIdentity();
|
const usuario = await getCurrentUserFunction(ctx);
|
||||||
if (!identity?.email) {
|
if (!usuario) return { sucesso: false };
|
||||||
return { sucesso: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const usuario = await ctx.db
|
// Verificar se usuário pertence à conversa
|
||||||
.query("usuarios")
|
const conversa = await ctx.db.get(args.conversaId);
|
||||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
if (!conversa || !conversa.participantes.includes(usuario._id)) {
|
||||||
.first();
|
return { sucesso: false };
|
||||||
|
}
|
||||||
|
|
||||||
if (!usuario) {
|
const preferenciasExistentes = await ctx.db
|
||||||
return { sucesso: false };
|
.query('preferenciasNotificacaoConversa')
|
||||||
}
|
.withIndex('by_usuario_conversa', (q) =>
|
||||||
|
q.eq('usuarioId', usuario._id).eq('conversaId', args.conversaId)
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
// Verificar se usuário pertence à conversa
|
const agora = Date.now();
|
||||||
const conversa = await ctx.db.get(args.conversaId);
|
|
||||||
if (!conversa || !conversa.participantes.includes(usuario._id)) {
|
|
||||||
return { sucesso: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferenciasExistentes = await ctx.db
|
if (preferenciasExistentes) {
|
||||||
.query("preferenciasNotificacaoConversa")
|
// Atualizar preferências existentes
|
||||||
.withIndex("by_usuario_conversa", (q) =>
|
await ctx.db.patch(preferenciasExistentes._id, {
|
||||||
q.eq("usuarioId", usuario._id).eq("conversaId", args.conversaId)
|
pushAtivado: args.pushAtivado ?? preferenciasExistentes.pushAtivado,
|
||||||
)
|
emailAtivado: args.emailAtivado ?? preferenciasExistentes.emailAtivado,
|
||||||
.first();
|
somAtivado: args.somAtivado ?? preferenciasExistentes.somAtivado,
|
||||||
|
silenciado: args.silenciado ?? preferenciasExistentes.silenciado,
|
||||||
|
apenasMencoes: args.apenasMencoes ?? preferenciasExistentes.apenasMencoes,
|
||||||
|
atualizadoEm: agora
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Criar novas preferências com valores padrão
|
||||||
|
await ctx.db.insert('preferenciasNotificacaoConversa', {
|
||||||
|
usuarioId: usuario._id,
|
||||||
|
conversaId: args.conversaId,
|
||||||
|
pushAtivado: args.pushAtivado ?? true,
|
||||||
|
emailAtivado: args.emailAtivado ?? true,
|
||||||
|
somAtivado: args.somAtivado ?? true,
|
||||||
|
silenciado: args.silenciado ?? false,
|
||||||
|
apenasMencoes: args.apenasMencoes ?? false,
|
||||||
|
criadoEm: agora,
|
||||||
|
atualizadoEm: agora
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const agora = Date.now();
|
return { sucesso: true };
|
||||||
|
}
|
||||||
if (preferenciasExistentes) {
|
|
||||||
// Atualizar preferências existentes
|
|
||||||
await ctx.db.patch(preferenciasExistentes._id, {
|
|
||||||
pushAtivado: args.pushAtivado ?? preferenciasExistentes.pushAtivado,
|
|
||||||
emailAtivado: args.emailAtivado ?? preferenciasExistentes.emailAtivado,
|
|
||||||
somAtivado: args.somAtivado ?? preferenciasExistentes.somAtivado,
|
|
||||||
silenciado: args.silenciado ?? preferenciasExistentes.silenciado,
|
|
||||||
apenasMencoes: args.apenasMencoes ?? preferenciasExistentes.apenasMencoes,
|
|
||||||
atualizadoEm: agora,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Criar novas preferências com valores padrão
|
|
||||||
await ctx.db.insert("preferenciasNotificacaoConversa", {
|
|
||||||
usuarioId: usuario._id,
|
|
||||||
conversaId: args.conversaId,
|
|
||||||
pushAtivado: args.pushAtivado ?? true,
|
|
||||||
emailAtivado: args.emailAtivado ?? true,
|
|
||||||
somAtivado: args.somAtivado ?? true,
|
|
||||||
silenciado: args.silenciado ?? false,
|
|
||||||
apenasMencoes: args.apenasMencoes ?? false,
|
|
||||||
criadoEm: agora,
|
|
||||||
atualizadoEm: agora,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { sucesso: true };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,120 +1,111 @@
|
|||||||
import { v } from "convex/values";
|
import { v } from 'convex/values';
|
||||||
import { mutation, query, internalMutation, internalQuery } from "./_generated/server";
|
import { mutation, internalMutation, internalQuery } from './_generated/server';
|
||||||
import { Id } from "./_generated/dataModel";
|
import { internal, api } from './_generated/api';
|
||||||
import { internal, api } from "./_generated/api";
|
import { getCurrentUserFunction } from './auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registrar subscription de push notification
|
* Registrar subscription de push notification
|
||||||
*/
|
*/
|
||||||
export const registrarPushSubscription = mutation({
|
export const registrarPushSubscription = mutation({
|
||||||
args: {
|
args: {
|
||||||
endpoint: v.string(),
|
endpoint: v.string(),
|
||||||
keys: v.object({
|
keys: v.object({
|
||||||
p256dh: v.string(),
|
p256dh: v.string(),
|
||||||
auth: v.string(),
|
auth: v.string()
|
||||||
}),
|
}),
|
||||||
userAgent: v.optional(v.string()),
|
userAgent: v.optional(v.string())
|
||||||
},
|
},
|
||||||
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Obter usuário autenticado
|
// Obter usuário autenticado
|
||||||
const identity = await ctx.auth.getUserIdentity();
|
const usuario = await getCurrentUserFunction(ctx);
|
||||||
if (!identity?.email) {
|
if (!usuario) {
|
||||||
return { sucesso: false, erro: "Usuário não autenticado" };
|
return { sucesso: false, erro: 'Usuário não autenticado' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuario = await ctx.db
|
// Verificar se já existe subscription com este endpoint
|
||||||
.query("usuarios")
|
const existente = await ctx.db
|
||||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
.query('pushSubscriptions')
|
||||||
.first();
|
.withIndex('by_endpoint', (q) => q.eq('endpoint', args.endpoint))
|
||||||
|
.first();
|
||||||
|
|
||||||
if (!usuario) {
|
if (existente) {
|
||||||
return { sucesso: false, erro: "Usuário não encontrado" };
|
// Atualizar subscription existente
|
||||||
}
|
await ctx.db.patch(existente._id, {
|
||||||
|
usuarioId: usuario._id,
|
||||||
|
keys: args.keys,
|
||||||
|
userAgent: args.userAgent,
|
||||||
|
ultimaAtividade: Date.now(),
|
||||||
|
ativo: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Criar nova subscription
|
||||||
|
await ctx.db.insert('pushSubscriptions', {
|
||||||
|
usuarioId: usuario._id,
|
||||||
|
endpoint: args.endpoint,
|
||||||
|
keys: args.keys,
|
||||||
|
userAgent: args.userAgent,
|
||||||
|
criadoEm: Date.now(),
|
||||||
|
ultimaAtividade: Date.now(),
|
||||||
|
ativo: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar se já existe subscription com este endpoint
|
return { sucesso: true };
|
||||||
const existente = await ctx.db
|
}
|
||||||
.query("pushSubscriptions")
|
|
||||||
.withIndex("by_endpoint", (q) => q.eq("endpoint", args.endpoint))
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (existente) {
|
|
||||||
// Atualizar subscription existente
|
|
||||||
await ctx.db.patch(existente._id, {
|
|
||||||
usuarioId: usuario._id,
|
|
||||||
keys: args.keys,
|
|
||||||
userAgent: args.userAgent,
|
|
||||||
ultimaAtividade: Date.now(),
|
|
||||||
ativo: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Criar nova subscription
|
|
||||||
await ctx.db.insert("pushSubscriptions", {
|
|
||||||
usuarioId: usuario._id,
|
|
||||||
endpoint: args.endpoint,
|
|
||||||
keys: args.keys,
|
|
||||||
userAgent: args.userAgent,
|
|
||||||
criadoEm: Date.now(),
|
|
||||||
ultimaAtividade: Date.now(),
|
|
||||||
ativo: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { sucesso: true };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remover subscription de push notification
|
* Remover subscription de push notification
|
||||||
*/
|
*/
|
||||||
export const removerPushSubscription = mutation({
|
export const removerPushSubscription = mutation({
|
||||||
args: {
|
args: {
|
||||||
endpoint: v.string(),
|
endpoint: v.string()
|
||||||
},
|
},
|
||||||
returns: v.object({ sucesso: v.boolean() }),
|
returns: v.object({ sucesso: v.boolean() }),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const subscription = await ctx.db
|
const subscription = await ctx.db
|
||||||
.query("pushSubscriptions")
|
.query('pushSubscriptions')
|
||||||
.withIndex("by_endpoint", (q) => q.eq("endpoint", args.endpoint))
|
.withIndex('by_endpoint', (q) => q.eq('endpoint', args.endpoint))
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
await ctx.db.patch(subscription._id, { ativo: false });
|
await ctx.db.patch(subscription._id, { ativo: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { sucesso: true };
|
return { sucesso: true };
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obter subscriptions ativas de um usuário
|
* Obter subscriptions ativas de um usuário
|
||||||
*/
|
*/
|
||||||
export const obterPushSubscriptions = internalQuery({
|
export const obterPushSubscriptions = internalQuery({
|
||||||
args: {
|
args: {
|
||||||
usuarioId: v.id("usuarios"),
|
usuarioId: v.id('usuarios')
|
||||||
},
|
},
|
||||||
returns: v.array(
|
returns: v.array(
|
||||||
v.object({
|
v.object({
|
||||||
_id: v.id("pushSubscriptions"),
|
_id: v.id('pushSubscriptions'),
|
||||||
endpoint: v.string(),
|
endpoint: v.string(),
|
||||||
keys: v.object({
|
keys: v.object({
|
||||||
p256dh: v.string(),
|
p256dh: v.string(),
|
||||||
auth: v.string(),
|
auth: v.string()
|
||||||
}),
|
})
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const subscriptions = await ctx.db
|
const subscriptions = await ctx.db
|
||||||
.query("pushSubscriptions")
|
.query('pushSubscriptions')
|
||||||
.withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId).eq("ativo", true))
|
.withIndex('by_usuario', (q) => q.eq('usuarioId', args.usuarioId).eq('ativo', true))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return subscriptions.map((sub) => ({
|
return subscriptions.map((sub) => ({
|
||||||
_id: sub._id,
|
_id: sub._id,
|
||||||
endpoint: sub.endpoint,
|
endpoint: sub.endpoint,
|
||||||
keys: sub.keys,
|
keys: sub.keys
|
||||||
}));
|
}));
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,157 +113,156 @@ export const obterPushSubscriptions = internalQuery({
|
|||||||
* Esta função será chamada quando uma nova mensagem chegar
|
* Esta função será chamada quando uma nova mensagem chegar
|
||||||
*/
|
*/
|
||||||
export const enviarPushNotification = internalMutation({
|
export const enviarPushNotification = internalMutation({
|
||||||
args: {
|
args: {
|
||||||
usuarioId: v.id("usuarios"),
|
usuarioId: v.id('usuarios'),
|
||||||
titulo: v.string(),
|
titulo: v.string(),
|
||||||
corpo: v.string(),
|
corpo: v.string(),
|
||||||
data: v.optional(
|
data: v.optional(
|
||||||
v.object({
|
v.object({
|
||||||
conversaId: v.optional(v.id("conversas")),
|
conversaId: v.optional(v.id('conversas')),
|
||||||
mensagemId: v.optional(v.id("mensagens")),
|
mensagemId: v.optional(v.id('mensagens')),
|
||||||
tipo: v.optional(v.string()),
|
tipo: v.optional(v.string())
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
},
|
},
|
||||||
returns: v.object({ enviados: v.number(), falhas: v.number() }),
|
returns: v.object({ enviados: v.number(), falhas: v.number() }),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Buscar subscriptions ativas do usuário
|
// Buscar subscriptions ativas do usuário
|
||||||
const subscriptions = await ctx.runQuery(internal.pushNotifications.obterPushSubscriptions, {
|
const subscriptions = await ctx.runQuery(internal.pushNotifications.obterPushSubscriptions, {
|
||||||
usuarioId: args.usuarioId,
|
usuarioId: args.usuarioId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (subscriptions.length === 0) {
|
if (subscriptions.length === 0) {
|
||||||
return { enviados: 0, falhas: 0 };
|
return { enviados: 0, falhas: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar preferências do usuário
|
// Verificar preferências do usuário
|
||||||
const usuario = await ctx.db.get(args.usuarioId);
|
const usuario = await ctx.db.get(args.usuarioId);
|
||||||
if (!usuario || usuario.notificacoesAtivadas === false) {
|
if (!usuario || usuario.notificacoesAtivadas === false) {
|
||||||
return { enviados: 0, falhas: 0 };
|
return { enviados: 0, falhas: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se há conversaId, verificar preferências específicas da conversa
|
// Se há conversaId, verificar preferências específicas da conversa
|
||||||
const conversaId = args.data?.conversaId;
|
const conversaId = args.data?.conversaId;
|
||||||
if (conversaId) {
|
if (conversaId) {
|
||||||
const preferencias = await ctx.db
|
const preferencias = await ctx.db
|
||||||
.query("preferenciasNotificacaoConversa")
|
.query('preferenciasNotificacaoConversa')
|
||||||
.withIndex("by_usuario_conversa", (q) =>
|
.withIndex('by_usuario_conversa', (q) =>
|
||||||
q.eq("usuarioId", args.usuarioId).eq("conversaId", conversaId)
|
q.eq('usuarioId', args.usuarioId).eq('conversaId', conversaId)
|
||||||
)
|
)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (preferencias) {
|
if (preferencias) {
|
||||||
// Se silenciado ou push desativado, não enviar
|
// Se silenciado ou push desativado, não enviar
|
||||||
if (preferencias.silenciado || !preferencias.pushAtivado) {
|
if (preferencias.silenciado || !preferencias.pushAtivado) {
|
||||||
return { enviados: 0, falhas: 0 };
|
return { enviados: 0, falhas: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se apenas menções e não é menção, não enviar
|
// Se apenas menções e não é menção, não enviar
|
||||||
if (preferencias.apenasMencoes && args.data?.tipo !== "mencao") {
|
if (preferencias.apenasMencoes && args.data?.tipo !== 'mencao') {
|
||||||
return { enviados: 0, falhas: 0 };
|
return { enviados: 0, falhas: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Agendar envio de push via action (que roda em Node.js)
|
// Agendar envio de push via action (que roda em Node.js)
|
||||||
let enviados = 0;
|
let enviados = 0;
|
||||||
let falhas = 0;
|
let falhas = 0;
|
||||||
|
|
||||||
// Converter IDs para strings ao passar para a action
|
// Converter IDs para strings ao passar para a action
|
||||||
// A action espera strings, mas recebemos Ids do Convex
|
// A action espera strings, mas recebemos Ids do Convex
|
||||||
const dataParaAction = args.data
|
const dataParaAction = args.data
|
||||||
? {
|
? {
|
||||||
conversaId: args.data.conversaId ? String(args.data.conversaId) : undefined,
|
conversaId: args.data.conversaId ? String(args.data.conversaId) : undefined,
|
||||||
mensagemId: args.data.mensagemId ? String(args.data.mensagemId) : undefined,
|
mensagemId: args.data.mensagemId ? String(args.data.mensagemId) : undefined,
|
||||||
tipo: args.data.tipo,
|
tipo: args.data.tipo
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
for (const subscription of subscriptions) {
|
for (const subscription of subscriptions) {
|
||||||
try {
|
try {
|
||||||
await ctx.scheduler.runAfter(0, api.actions.pushNotifications.enviarPush, {
|
await ctx.scheduler.runAfter(0, api.actions.pushNotifications.enviarPush, {
|
||||||
subscriptionId: subscription._id,
|
subscriptionId: subscription._id,
|
||||||
titulo: args.titulo,
|
titulo: args.titulo,
|
||||||
corpo: args.corpo,
|
corpo: args.corpo,
|
||||||
data: dataParaAction,
|
data: dataParaAction
|
||||||
});
|
});
|
||||||
enviados++;
|
enviados++;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
console.error(`Erro ao agendar push para subscription ${subscription._id}:`, errorMessage);
|
console.error(`Erro ao agendar push para subscription ${subscription._id}:`, errorMessage);
|
||||||
falhas++;
|
falhas++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { enviados, falhas };
|
return { enviados, falhas };
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obter subscription por ID (para actions)
|
* Obter subscription por ID (para actions)
|
||||||
*/
|
*/
|
||||||
export const getSubscriptionById = internalQuery({
|
export const getSubscriptionById = internalQuery({
|
||||||
args: {
|
args: {
|
||||||
subscriptionId: v.id("pushSubscriptions"),
|
subscriptionId: v.id('pushSubscriptions')
|
||||||
},
|
},
|
||||||
returns: v.union(
|
returns: v.union(
|
||||||
v.object({
|
v.object({
|
||||||
_id: v.id("pushSubscriptions"),
|
_id: v.id('pushSubscriptions'),
|
||||||
endpoint: v.string(),
|
endpoint: v.string(),
|
||||||
keys: v.object({
|
keys: v.object({
|
||||||
p256dh: v.string(),
|
p256dh: v.string(),
|
||||||
auth: v.string(),
|
auth: v.string()
|
||||||
}),
|
}),
|
||||||
ativo: v.boolean(),
|
ativo: v.boolean()
|
||||||
}),
|
}),
|
||||||
v.null()
|
v.null()
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const subscription = await ctx.db.get(args.subscriptionId);
|
const subscription = await ctx.db.get(args.subscriptionId);
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: subscription._id,
|
_id: subscription._id,
|
||||||
endpoint: subscription.endpoint,
|
endpoint: subscription.endpoint,
|
||||||
keys: subscription.keys,
|
keys: subscription.keys,
|
||||||
ativo: subscription.ativo,
|
ativo: subscription.ativo
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marcar subscription como inativa
|
* Marcar subscription como inativa
|
||||||
*/
|
*/
|
||||||
export const marcarSubscriptionInativa = internalMutation({
|
export const marcarSubscriptionInativa = internalMutation({
|
||||||
args: {
|
args: {
|
||||||
subscriptionId: v.id("pushSubscriptions"),
|
subscriptionId: v.id('pushSubscriptions')
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
await ctx.db.patch(args.subscriptionId, { ativo: false });
|
await ctx.db.patch(args.subscriptionId, { ativo: false });
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verificar se usuário está online (última atividade recente)
|
* Verificar se usuário está online (última atividade recente)
|
||||||
*/
|
*/
|
||||||
export const verificarUsuarioOnline = internalQuery({
|
export const verificarUsuarioOnline = internalQuery({
|
||||||
args: {
|
args: {
|
||||||
usuarioId: v.id("usuarios"),
|
usuarioId: v.id('usuarios')
|
||||||
},
|
},
|
||||||
returns: v.boolean(),
|
returns: v.boolean(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const usuario = await ctx.db.get(args.usuarioId);
|
const usuario = await ctx.db.get(args.usuarioId);
|
||||||
if (!usuario || !usuario.ultimaAtividade) {
|
if (!usuario || !usuario.ultimaAtividade) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Considerar online se última atividade foi há menos de 5 minutos
|
// Considerar online se última atividade foi há menos de 5 minutos
|
||||||
const cincoMinutosAtras = Date.now() - 5 * 60 * 1000;
|
const cincoMinutosAtras = Date.now() - 5 * 60 * 1000;
|
||||||
return usuario.ultimaAtividade >= cincoMinutosAtras;
|
return usuario.ultimaAtividade >= cincoMinutosAtras;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user