feat: Introduce structured table definitions in convex/tables for various entities and remove the todos example table.

This commit is contained in:
2025-12-02 09:55:07 -03:00
parent 1c0bd219b2
commit 05e7f1181d
30 changed files with 2700 additions and 2535 deletions

View File

@@ -0,0 +1,173 @@
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'])
};