import { defineTable } from 'convex/server'; import { v } from 'convex/values'; export const chatTables = { // Sistema de Chat conversas: defineTable({ tipo: v.union(v.literal('individual'), v.literal('grupo'), v.literal('sala_reuniao')), nome: v.optional(v.string()), // nome do grupo/sala participantes: v.array(v.id('usuarios')), // IDs dos participantes administradores: v.optional(v.array(v.id('usuarios'))), // IDs dos administradores (apenas para sala_reuniao) ultimaMensagem: v.optional(v.string()), ultimaMensagemTimestamp: v.optional(v.number()), ultimaMensagemRemetenteId: v.optional(v.id('usuarios')), // ID do remetente da última mensagem criadoPor: v.id('usuarios'), criadoEm: v.number() }) .index('by_criado_por', ['criadoPor']) .index('by_tipo', ['tipo']) .index('by_ultima_mensagem', ['ultimaMensagemTimestamp']), mensagens: defineTable({ conversaId: v.id('conversas'), remetenteId: v.id('usuarios'), tipo: v.union(v.literal('texto'), v.literal('arquivo'), v.literal('imagem')), conteudo: v.string(), // texto ou nome do arquivo conteudoBusca: v.optional(v.string()), // versão normalizada para busca arquivoId: v.optional(v.id('_storage')), arquivoNome: v.optional(v.string()), arquivoTamanho: v.optional(v.number()), arquivoTipo: v.optional(v.string()), linkPreview: v.optional( v.object({ url: v.string(), titulo: v.optional(v.string()), descricao: v.optional(v.string()), imagem: v.optional(v.string()), site: v.optional(v.string()) }) ), reagiuPor: v.optional( v.array( v.object({ usuarioId: v.id('usuarios'), emoji: v.string() }) ) ), mencoes: v.optional(v.array(v.id('usuarios'))), respostaPara: v.optional(v.id('mensagens')), // ID da mensagem que está respondendo agendadaPara: v.optional(v.number()), // timestamp enviadaEm: v.number(), editadaEm: v.optional(v.number()), deletada: v.optional(v.boolean()), lidaPor: v.optional(v.array(v.id('usuarios'))) // IDs dos usuários que leram a mensagem }) .index('by_conversa', ['conversaId', 'enviadaEm']) .index('by_remetente', ['remetenteId']) .index('by_agendamento', ['agendadaPara']) .index('by_resposta', ['respostaPara']), leituras: defineTable({ conversaId: v.id('conversas'), usuarioId: v.id('usuarios'), ultimaMensagemLida: v.id('mensagens'), lidaEm: v.number() }) .index('by_conversa_usuario', ['conversaId', 'usuarioId']) .index('by_usuario', ['usuarioId']), // Sistema de Chamadas de Áudio/Vídeo chamadas: defineTable({ conversaId: v.id('conversas'), tipo: v.union(v.literal('audio'), v.literal('video')), roomName: v.string(), // Nome único da sala Jitsi criadoPor: v.id('usuarios'), // Anfitrião/criador participantes: v.array(v.id('usuarios')), status: v.union( v.literal('aguardando'), v.literal('em_andamento'), v.literal('finalizada'), v.literal('cancelada') ), iniciadaEm: v.optional(v.number()), finalizadaEm: v.optional(v.number()), duracaoSegundos: v.optional(v.number()), gravando: v.boolean(), gravacaoIniciadaPor: v.optional(v.id('usuarios')), gravacaoIniciadaEm: v.optional(v.number()), gravacaoFinalizadaEm: v.optional(v.number()), configuracoes: v.optional( v.object({ audioHabilitado: v.boolean(), videoHabilitado: v.boolean(), participantesConfig: v.optional( v.array( v.object({ usuarioId: v.id('usuarios'), audioHabilitado: v.boolean(), videoHabilitado: v.boolean(), forcadoPeloAnfitriao: v.optional(v.boolean()) // Se foi forçado pelo anfitrião }) ) ) }) ), criadoEm: v.number() }) .index('by_conversa', ['conversaId', 'status']) .index('by_criado_por', ['criadoPor']) .index('by_status', ['status']) .index('by_room_name', ['roomName']), notificacoes: defineTable({ usuarioId: v.id('usuarios'), tipo: v.union( v.literal('nova_mensagem'), v.literal('mencao'), v.literal('grupo_criado'), v.literal('adicionado_grupo'), v.literal('alerta_seguranca'), v.literal('etapa_fluxo_concluida') ), conversaId: v.optional(v.id('conversas')), mensagemId: v.optional(v.id('mensagens')), remetenteId: v.optional(v.id('usuarios')), titulo: v.string(), descricao: v.string(), lida: v.boolean(), criadaEm: v.number() }) .index('by_usuario', ['usuarioId', 'lida', 'criadaEm']) .index('by_usuario_lida', ['usuarioId', 'lida']), digitando: defineTable({ conversaId: v.id('conversas'), usuarioId: v.id('usuarios'), iniciouEm: v.number() }) .index('by_conversa', ['conversaId', 'iniciouEm']) .index('by_usuario', ['usuarioId']), // Push Notifications pushSubscriptions: defineTable({ usuarioId: v.id('usuarios'), endpoint: v.string(), // URL do serviço de push keys: v.object({ p256dh: v.string(), // Chave pública auth: v.string() // Chave de autenticação }), userAgent: v.optional(v.string()), criadoEm: v.number(), ultimaAtividade: v.number(), ativo: v.boolean() }) .index('by_usuario', ['usuarioId', 'ativo']) .index('by_endpoint', ['endpoint']), // Preferências de Notificação por Conversa preferenciasNotificacaoConversa: defineTable({ usuarioId: v.id('usuarios'), conversaId: v.id('conversas'), pushAtivado: v.boolean(), // Receber push notifications emailAtivado: v.boolean(), // Receber emails quando offline somAtivado: v.boolean(), // Tocar som silenciado: v.boolean(), // Silenciar completamente apenasMencoes: v.boolean(), // Notificar apenas quando mencionado criadoEm: v.number(), atualizadoEm: v.number() }) .index('by_usuario_conversa', ['usuarioId', 'conversaId']) .index('by_conversa', ['conversaId']) };