import { v } from 'convex/values'; import { action, internalMutation, internalQuery, mutation, query } from './_generated/server'; import { getCurrentUserFunction } from './auth'; import type { Id } from './_generated/dataModel'; import { api, internal } from './_generated/api'; /** * Obtém a configuração do relógio */ export const obterConfiguracao = query({ args: {}, handler: async (ctx) => { const config = await ctx.db .query('configuracaoRelogio') .withIndex('by_ativo', (q) => q.eq('usarServidorExterno', true)) .first(); if (!config) { // Retornar configuração padrão return { servidorNTP: 'pool.ntp.org', portaNTP: 123, usarServidorExterno: false, fallbackParaPC: true, ultimaSincronizacao: null, offsetSegundos: null, }; } return config; }, }); /** * Salva configuração do relógio (apenas TI) */ export const salvarConfiguracao = mutation({ args: { servidorNTP: v.optional(v.string()), portaNTP: v.optional(v.number()), usarServidorExterno: v.boolean(), fallbackParaPC: v.boolean(), }, handler: async (ctx, args) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { throw new Error('Usuário não autenticado'); } // TODO: Verificar se usuário tem permissão de TI // Validar servidor NTP se usar servidor externo if (args.usarServidorExterno) { if (!args.servidorNTP || args.servidorNTP.trim() === '') { throw new Error('Servidor NTP é obrigatório quando usar servidor externo'); } if (!args.portaNTP || args.portaNTP < 1 || args.portaNTP > 65535) { throw new Error('Porta NTP deve estar entre 1 e 65535'); } } // Buscar configuração existente const configExistente = await ctx.db .query('configuracaoRelogio') .withIndex('by_ativo', (q) => q.eq('usarServidorExterno', args.usarServidorExterno)) .first(); if (configExistente) { // Atualizar configuração existente await ctx.db.patch(configExistente._id, { servidorNTP: args.servidorNTP, portaNTP: args.portaNTP, usarServidorExterno: args.usarServidorExterno, fallbackParaPC: args.fallbackParaPC, atualizadoPor: usuario._id as Id<'usuarios'>, atualizadoEm: Date.now(), }); return { configId: configExistente._id }; } else { // Criar nova configuração const configId = await ctx.db.insert('configuracaoRelogio', { servidorNTP: args.servidorNTP, portaNTP: args.portaNTP, usarServidorExterno: args.usarServidorExterno, fallbackParaPC: args.fallbackParaPC, atualizadoPor: usuario._id as Id<'usuarios'>, atualizadoEm: Date.now(), }); return { configId }; } }, }); /** * Obtém tempo do servidor (timestamp atual) */ export const obterTempoServidor = query({ args: {}, handler: async () => { return { timestamp: Date.now(), data: new Date().toISOString(), }; }, }); /** * Sincroniza tempo com servidor NTP (via action) * Nota: NTP real requer biblioteca específica, aqui fazemos uma aproximação */ export const sincronizarTempo = action({ args: {}, handler: async (ctx) => { // Buscar configuração diretamente do banco usando query pública const config = await ctx.runQuery(api.configuracaoRelogio.obterConfiguracao, {}); if (!config.usarServidorExterno) { return { sucesso: true, timestamp: Date.now(), usandoServidorExterno: false, offsetSegundos: 0, }; } // Tentar obter tempo de um servidor NTP público via HTTP // Nota: Esta é uma aproximação. Para NTP real, seria necessário usar uma biblioteca específica try { // Usar API pública de tempo como fallback const response = await fetch('https://worldtimeapi.org/api/timezone/America/Recife'); if (!response.ok) { throw new Error('Falha ao obter tempo do servidor'); } const data = (await response.json()) as { datetime: string }; const serverTime = new Date(data.datetime).getTime(); const localTime = Date.now(); const offsetSegundos = Math.floor((serverTime - localTime) / 1000); // Atualizar configuração com offset // Buscar configuração diretamente usando query interna const configs = await ctx.runQuery(internal.configuracaoRelogio.listarConfiguracoes, {}); const configExistente = configs.find( (c: { usarServidorExterno: boolean; _id: Id<'configuracaoRelogio'> }) => c.usarServidorExterno === config.usarServidorExterno ); if (configExistente) { await ctx.runMutation(internal.configuracaoRelogio.atualizarSincronizacao, { configId: configExistente._id, offsetSegundos, }); } return { sucesso: true, timestamp: serverTime, usandoServidorExterno: true, offsetSegundos, }; } catch { // Se falhar e fallbackParaPC estiver ativo, usar tempo local if (config.fallbackParaPC) { return { sucesso: true, timestamp: Date.now(), usandoServidorExterno: false, offsetSegundos: 0, aviso: 'Falha ao sincronizar com servidor externo, usando relógio do PC', }; } throw new Error('Falha ao sincronizar tempo e fallback desabilitado'); } }, }); /** * Lista configurações (internal) */ export const listarConfiguracoes = internalQuery({ args: {}, handler: async (ctx) => { return await ctx.db.query('configuracaoRelogio').collect(); }, }); /** * Atualiza informações de sincronização (internal) */ export const atualizarSincronizacao = internalMutation({ args: { configId: v.id('configuracaoRelogio'), offsetSegundos: v.number(), }, handler: async (ctx, args) => { await ctx.db.patch(args.configId, { ultimaSincronizacao: Date.now(), offsetSegundos: args.offsetSegundos, }); }, });