Files
sgse-app/packages/backend/convex/configuracaoRelogio.ts
deyvisonwanderley 7cdc726781 feat: implement customizable point registration labels and GMT offset adjustment
- Added functionality to customize labels for point registration types (Entrada, Saída, etc.) in the configuration settings.
- Introduced a GMT offset adjustment feature to account for time zone differences during point registration.
- Updated the backend to ensure default values for custom labels and GMT offset are set correctly.
- Enhanced the UI to allow users to input and save personalized names for each type of point registration.
- Improved the point registration process to utilize the new configuration settings for displaying labels consistently across the application.
2025-11-19 06:22:07 -03:00

210 lines
5.7 KiB
TypeScript

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,
gmtOffset: 0,
};
}
return {
...config,
gmtOffset: config.gmtOffset ?? 0,
};
},
});
/**
* 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(),
gmtOffset: v.optional(v.number()),
},
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,
gmtOffset: args.gmtOffset ?? 0,
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,
gmtOffset: args.gmtOffset ?? 0,
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,
});
},
});