feat: improve login process by integrating GPS location tracking and optimizing IP address handling, enhancing user data accuracy and experience
This commit is contained in:
@@ -13,20 +13,7 @@
|
|||||||
import { Menu, User, Home, UserPlus, XCircle, LogIn, Tag, Plus, Check } from 'lucide-svelte';
|
import { Menu, User, Home, UserPlus, XCircle, LogIn, Tag, Plus, Check } from 'lucide-svelte';
|
||||||
import { authClient } from '$lib/auth';
|
import { authClient } from '$lib/auth';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import { obterIPPublico } from '$lib/utils/deviceInfo';
|
||||||
// Função para obter IP público (similar ao sistema de ponto)
|
|
||||||
async function obterIPPublico(): Promise<string | undefined> {
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://api.ipify.org?format=json');
|
|
||||||
if (response.ok) {
|
|
||||||
const data = (await response.json()) as { ip: string };
|
|
||||||
return data.ip;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Erro ao obter IP público:', error);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { children }: { children: Snippet } = $props();
|
let { children }: { children: Snippet } = $props();
|
||||||
|
|
||||||
@@ -137,11 +124,34 @@
|
|||||||
erroLogin = '';
|
erroLogin = '';
|
||||||
carregandoLogin = true;
|
carregandoLogin = true;
|
||||||
|
|
||||||
// Obter IP público e userAgent antes do login
|
// Obter IP público e userAgent (rápido, não bloqueia)
|
||||||
const [ipPublico, userAgent] = await Promise.all([
|
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : undefined;
|
||||||
obterIPPublico().catch(() => undefined),
|
|
||||||
Promise.resolve(typeof navigator !== 'undefined' ? navigator.userAgent : undefined)
|
// Obter IP público com timeout curto (não bloquear login)
|
||||||
]);
|
const ipPublicoPromise = obterIPPublico().catch(() => undefined);
|
||||||
|
const ipPublicoTimeout = new Promise<undefined>((resolve) =>
|
||||||
|
setTimeout(() => resolve(undefined), 2000) // Timeout de 2 segundos
|
||||||
|
);
|
||||||
|
const ipPublico = await Promise.race([ipPublicoPromise, ipPublicoTimeout]);
|
||||||
|
|
||||||
|
// Função para coletar GPS em background (não bloqueia login)
|
||||||
|
async function coletarGPS(): Promise<any> {
|
||||||
|
try {
|
||||||
|
const { obterLocalizacaoRapida } = await import('$lib/utils/deviceInfo');
|
||||||
|
// Usar versão rápida com timeout curto (3 segundos máximo)
|
||||||
|
const gpsPromise = obterLocalizacaoRapida();
|
||||||
|
const gpsTimeout = new Promise<{}>((resolve) =>
|
||||||
|
setTimeout(() => resolve({}), 3000)
|
||||||
|
);
|
||||||
|
return await Promise.race([gpsPromise, gpsTimeout]);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Erro ao obter GPS (não bloqueia login):', err);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar coleta de GPS em background (não esperar)
|
||||||
|
const gpsPromise = coletarGPS();
|
||||||
|
|
||||||
const result = await authClient.signIn.email(
|
const result = await authClient.signIn.email(
|
||||||
{ email: matricula.trim(), password: senha },
|
{ email: matricula.trim(), password: senha },
|
||||||
@@ -149,12 +159,30 @@
|
|||||||
onError: async (ctx) => {
|
onError: async (ctx) => {
|
||||||
// Registrar tentativa de login falha
|
// Registrar tentativa de login falha
|
||||||
try {
|
try {
|
||||||
|
// Tentar obter GPS se já estiver disponível (não esperar)
|
||||||
|
let localizacaoGPS: any = {};
|
||||||
|
try {
|
||||||
|
localizacaoGPS = await Promise.race([
|
||||||
|
gpsPromise,
|
||||||
|
new Promise<{}>((resolve) => setTimeout(() => resolve({}), 100))
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
// Ignorar se GPS não estiver pronto
|
||||||
|
}
|
||||||
|
|
||||||
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
|
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
|
||||||
matriculaOuEmail: matricula.trim(),
|
matriculaOuEmail: matricula.trim(),
|
||||||
sucesso: false,
|
sucesso: false,
|
||||||
motivoFalha: ctx.error?.message || 'Erro desconhecido',
|
motivoFalha: ctx.error?.message || 'Erro desconhecido',
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
ipAddress: ipPublico,
|
ipAddress: ipPublico,
|
||||||
|
latitudeGPS: localizacaoGPS.latitude,
|
||||||
|
longitudeGPS: localizacaoGPS.longitude,
|
||||||
|
precisaoGPS: localizacaoGPS.precisao,
|
||||||
|
enderecoGPS: localizacaoGPS.endereco,
|
||||||
|
cidadeGPS: localizacaoGPS.cidade,
|
||||||
|
estadoGPS: localizacaoGPS.estado,
|
||||||
|
paisGPS: localizacaoGPS.pais,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao registrar tentativa de login falha:', err);
|
console.error('Erro ao registrar tentativa de login falha:', err);
|
||||||
@@ -172,6 +200,17 @@
|
|||||||
// Aguardar um pouco para o usuário ser sincronizado no Convex
|
// Aguardar um pouco para o usuário ser sincronizado no Convex
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// Tentar obter GPS se já estiver disponível (não esperar)
|
||||||
|
let localizacaoGPS: any = {};
|
||||||
|
try {
|
||||||
|
localizacaoGPS = await Promise.race([
|
||||||
|
gpsPromise,
|
||||||
|
new Promise<{}>((resolve) => setTimeout(() => resolve({}), 100))
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
// Ignorar se GPS não estiver pronto
|
||||||
|
}
|
||||||
|
|
||||||
// Buscar o usuário no Convex usando getCurrentUser
|
// Buscar o usuário no Convex usando getCurrentUser
|
||||||
const usuario = await convexClient.query(api.auth.getCurrentUser, {});
|
const usuario = await convexClient.query(api.auth.getCurrentUser, {});
|
||||||
|
|
||||||
@@ -182,6 +221,13 @@
|
|||||||
sucesso: true,
|
sucesso: true,
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
ipAddress: ipPublico,
|
ipAddress: ipPublico,
|
||||||
|
latitudeGPS: localizacaoGPS.latitude,
|
||||||
|
longitudeGPS: localizacaoGPS.longitude,
|
||||||
|
precisaoGPS: localizacaoGPS.precisao,
|
||||||
|
enderecoGPS: localizacaoGPS.endereco,
|
||||||
|
cidadeGPS: localizacaoGPS.cidade,
|
||||||
|
estadoGPS: localizacaoGPS.estado,
|
||||||
|
paisGPS: localizacaoGPS.pais,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Se não encontrou o usuário, registrar sem usuarioId (será atualizado depois)
|
// Se não encontrou o usuário, registrar sem usuarioId (será atualizado depois)
|
||||||
@@ -190,6 +236,13 @@
|
|||||||
sucesso: true,
|
sucesso: true,
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
ipAddress: ipPublico,
|
ipAddress: ipPublico,
|
||||||
|
latitudeGPS: localizacaoGPS.latitude,
|
||||||
|
longitudeGPS: localizacaoGPS.longitude,
|
||||||
|
precisaoGPS: localizacaoGPS.precisao,
|
||||||
|
enderecoGPS: localizacaoGPS.endereco,
|
||||||
|
cidadeGPS: localizacaoGPS.cidade,
|
||||||
|
estadoGPS: localizacaoGPS.estado,
|
||||||
|
paisGPS: localizacaoGPS.pais,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -526,10 +526,97 @@ async function obterLocalizacaoMultipla(): Promise<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtém localização via GPS de forma rápida (uma única leitura, sem reverse geocoding)
|
||||||
|
* Usado para login - não bloqueia o fluxo
|
||||||
|
*/
|
||||||
|
export async function obterLocalizacaoRapida(): Promise<{
|
||||||
|
latitude?: number;
|
||||||
|
longitude?: number;
|
||||||
|
precisao?: number;
|
||||||
|
endereco?: string;
|
||||||
|
cidade?: string;
|
||||||
|
estado?: string;
|
||||||
|
pais?: string;
|
||||||
|
}> {
|
||||||
|
if (typeof navigator === 'undefined' || !navigator.geolocation) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Uma única leitura rápida com timeout curto
|
||||||
|
const leitura = await capturarLocalizacaoUnica(true, 3000); // 3 segundos máximo
|
||||||
|
|
||||||
|
if (!leitura.latitude || !leitura.longitude || leitura.confiabilidade === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tentar obter endereço via reverse geocoding (com timeout curto)
|
||||||
|
let endereco = '';
|
||||||
|
let cidade = '';
|
||||||
|
let estado = '';
|
||||||
|
let pais = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const geocodePromise = fetch(
|
||||||
|
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${leitura.latitude}&lon=${leitura.longitude}&zoom=18&addressdetails=1`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'SGSE-App/1.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const geocodeTimeout = new Promise<Response>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Timeout')), 2000)
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await Promise.race([geocodePromise, geocodeTimeout]);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = (await response.json()) as {
|
||||||
|
address?: {
|
||||||
|
road?: string;
|
||||||
|
house_number?: string;
|
||||||
|
city?: string;
|
||||||
|
town?: string;
|
||||||
|
state?: string;
|
||||||
|
country?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (data.address) {
|
||||||
|
const addr = data.address;
|
||||||
|
if (addr.road) {
|
||||||
|
endereco = `${addr.road}${addr.house_number ? `, ${addr.house_number}` : ''}`;
|
||||||
|
}
|
||||||
|
cidade = addr.city || addr.town || '';
|
||||||
|
estado = addr.state || '';
|
||||||
|
pais = addr.country || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignorar erro de geocoding - não é crítico
|
||||||
|
console.warn('Erro ao obter endereço (não crítico):', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
latitude: leitura.latitude,
|
||||||
|
longitude: leitura.longitude,
|
||||||
|
precisao: leitura.precisao,
|
||||||
|
endereco,
|
||||||
|
cidade,
|
||||||
|
estado,
|
||||||
|
pais
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Erro ao obter localização rápida:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtém localização via GPS com múltiplas tentativas e validações anti-spoofing
|
* Obtém localização via GPS com múltiplas tentativas e validações anti-spoofing
|
||||||
*/
|
*/
|
||||||
async function obterLocalizacao(): Promise<{
|
export async function obterLocalizacao(): Promise<{
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
precisao?: number;
|
precisao?: number;
|
||||||
@@ -644,7 +731,7 @@ async function obterLocalizacao(): Promise<{
|
|||||||
/**
|
/**
|
||||||
* Obtém IP público
|
* Obtém IP público
|
||||||
*/
|
*/
|
||||||
async function obterIPPublico(): Promise<string | undefined> {
|
export async function obterIPPublico(): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://api.ipify.org?format=json');
|
const response = await fetch('https://api.ipify.org?format=json');
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -132,6 +132,37 @@
|
|||||||
function formatarLocalizacao(login: any): string {
|
function formatarLocalizacao(login: any): string {
|
||||||
if (!login || typeof login !== 'object') return "-";
|
if (!login || typeof login !== 'object') return "-";
|
||||||
|
|
||||||
|
// Priorizar localização GPS (mais precisa) quando disponível
|
||||||
|
const usarGPS = login.latitudeGPS && login.longitudeGPS;
|
||||||
|
|
||||||
|
if (usarGPS) {
|
||||||
|
const partes: string[] = [];
|
||||||
|
if (login.cidadeGPS && typeof login.cidadeGPS === 'string' && login.cidadeGPS.trim()) {
|
||||||
|
partes.push(login.cidadeGPS.trim());
|
||||||
|
}
|
||||||
|
if (login.estadoGPS && typeof login.estadoGPS === 'string' && login.estadoGPS.trim()) {
|
||||||
|
partes.push(login.estadoGPS.trim());
|
||||||
|
}
|
||||||
|
if (login.paisGPS && typeof login.paisGPS === 'string' && login.paisGPS.trim()) {
|
||||||
|
partes.push(login.paisGPS.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partes.length > 0) {
|
||||||
|
return `${partes.join(", ")} (GPS)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se não tiver cidade/estado/pais GPS, mas tiver endereco GPS, mostrar endereco
|
||||||
|
if (login.enderecoGPS && typeof login.enderecoGPS === 'string' && login.enderecoGPS.trim()) {
|
||||||
|
return `${login.enderecoGPS.trim()} (GPS)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se tiver coordenadas mas não endereço, mostrar coordenadas
|
||||||
|
if (login.latitudeGPS && login.longitudeGPS) {
|
||||||
|
return `${login.latitudeGPS.toFixed(6)}, ${login.longitudeGPS.toFixed(6)} (GPS)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback para localização por IP
|
||||||
const partes: string[] = [];
|
const partes: string[] = [];
|
||||||
if (login.cidade && typeof login.cidade === 'string' && login.cidade.trim()) {
|
if (login.cidade && typeof login.cidade === 'string' && login.cidade.trim()) {
|
||||||
partes.push(login.cidade.trim());
|
partes.push(login.cidade.trim());
|
||||||
|
|||||||
@@ -2,61 +2,6 @@ import { v } from "convex/values";
|
|||||||
import { mutation, query, QueryCtx, MutationCtx } from "./_generated/server";
|
import { mutation, query, QueryCtx, MutationCtx } from "./_generated/server";
|
||||||
import { Doc, Id } from "./_generated/dataModel";
|
import { Doc, Id } from "./_generated/dataModel";
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtém geolocalização aproximada por IP usando serviço externo
|
|
||||||
* Similar ao sistema de ponto
|
|
||||||
*/
|
|
||||||
async function obterGeoPorIP(ipAddress: string): Promise<{
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
cidade?: string;
|
|
||||||
estado?: string;
|
|
||||||
pais?: string;
|
|
||||||
endereco?: string;
|
|
||||||
} | null> {
|
|
||||||
try {
|
|
||||||
// Usar ipapi.co (gratuito, sem chave para uso limitado)
|
|
||||||
const response = await fetch(`https://ipapi.co/${ipAddress}/json/`, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'SGSE-App/1.0'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = (await response.json()) as {
|
|
||||||
latitude?: number;
|
|
||||||
longitude?: number;
|
|
||||||
city?: string;
|
|
||||||
region?: string;
|
|
||||||
country_name?: string;
|
|
||||||
error?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!data.error && data.latitude && data.longitude) {
|
|
||||||
// Montar endereço completo
|
|
||||||
const partesEndereco: string[] = [];
|
|
||||||
if (data.city) partesEndereco.push(data.city);
|
|
||||||
if (data.region) partesEndereco.push(data.region);
|
|
||||||
if (data.country_name) partesEndereco.push(data.country_name);
|
|
||||||
const endereco = partesEndereco.length > 0 ? partesEndereco.join(', ') : undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
latitude: data.latitude,
|
|
||||||
longitude: data.longitude,
|
|
||||||
cidade: data.city,
|
|
||||||
estado: data.region,
|
|
||||||
pais: data.country_name,
|
|
||||||
endereco
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Erro ao obter geolocalização por IP:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper para registrar tentativas de login
|
* Helper para registrar tentativas de login
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +43,13 @@ export async function registrarLogin(
|
|||||||
motivoFalha?: string;
|
motivoFalha?: string;
|
||||||
ipAddress?: string;
|
ipAddress?: string;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
|
latitudeGPS?: number;
|
||||||
|
longitudeGPS?: number;
|
||||||
|
precisaoGPS?: number;
|
||||||
|
enderecoGPS?: string;
|
||||||
|
cidadeGPS?: string;
|
||||||
|
estadoGPS?: string;
|
||||||
|
paisGPS?: string;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// Extrair informações do userAgent
|
// Extrair informações do userAgent
|
||||||
@@ -108,25 +60,8 @@ export async function registrarLogin(
|
|||||||
// Validar e sanitizar IP antes de salvar
|
// Validar e sanitizar IP antes de salvar
|
||||||
const ipAddressValidado = validarIP(dados.ipAddress);
|
const ipAddressValidado = validarIP(dados.ipAddress);
|
||||||
|
|
||||||
// Obter geolocalização por IP se disponível (de forma assíncrona para não bloquear)
|
// Nota: Geolocalização por IP removida porque fetch() não pode ser usado em mutations do Convex
|
||||||
let geolocalizacao: {
|
// A localização GPS já é coletada no frontend e enviada diretamente
|
||||||
latitude?: number;
|
|
||||||
longitude?: number;
|
|
||||||
cidade?: string;
|
|
||||||
estado?: string;
|
|
||||||
pais?: string;
|
|
||||||
endereco?: string;
|
|
||||||
} | null = null;
|
|
||||||
|
|
||||||
if (ipAddressValidado) {
|
|
||||||
// Obter geolocalização por IP (não bloquear se falhar)
|
|
||||||
try {
|
|
||||||
geolocalizacao = await obterGeoPorIP(ipAddressValidado);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Erro ao obter geolocalização por IP:', error);
|
|
||||||
// Continuar sem localização se houver erro
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.db.insert("logsLogin", {
|
await ctx.db.insert("logsLogin", {
|
||||||
usuarioId: dados.usuarioId,
|
usuarioId: dados.usuarioId,
|
||||||
@@ -138,13 +73,21 @@ export async function registrarLogin(
|
|||||||
device,
|
device,
|
||||||
browser,
|
browser,
|
||||||
sistema,
|
sistema,
|
||||||
// Informações de Localização
|
// Informações de Localização por IP (removido - usar GPS do frontend)
|
||||||
latitude: geolocalizacao?.latitude,
|
latitude: undefined,
|
||||||
longitude: geolocalizacao?.longitude,
|
longitude: undefined,
|
||||||
cidade: geolocalizacao?.cidade,
|
cidade: undefined,
|
||||||
estado: geolocalizacao?.estado,
|
estado: undefined,
|
||||||
pais: geolocalizacao?.pais,
|
pais: undefined,
|
||||||
endereco: geolocalizacao?.endereco,
|
endereco: undefined,
|
||||||
|
// Informações de Localização (GPS do navegador)
|
||||||
|
latitudeGPS: dados.latitudeGPS,
|
||||||
|
longitudeGPS: dados.longitudeGPS,
|
||||||
|
precisaoGPS: dados.precisaoGPS,
|
||||||
|
enderecoGPS: dados.enderecoGPS,
|
||||||
|
cidadeGPS: dados.cidadeGPS,
|
||||||
|
estadoGPS: dados.estadoGPS,
|
||||||
|
paisGPS: dados.paisGPS,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -374,6 +317,13 @@ export const registrarTentativaLogin = mutation({
|
|||||||
motivoFalha: v.optional(v.string()),
|
motivoFalha: v.optional(v.string()),
|
||||||
ipAddress: v.optional(v.string()),
|
ipAddress: v.optional(v.string()),
|
||||||
userAgent: v.optional(v.string()),
|
userAgent: v.optional(v.string()),
|
||||||
|
latitudeGPS: v.optional(v.number()),
|
||||||
|
longitudeGPS: v.optional(v.number()),
|
||||||
|
precisaoGPS: v.optional(v.number()),
|
||||||
|
enderecoGPS: v.optional(v.string()),
|
||||||
|
cidadeGPS: v.optional(v.string()),
|
||||||
|
estadoGPS: v.optional(v.string()),
|
||||||
|
paisGPS: v.optional(v.string()),
|
||||||
},
|
},
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
await registrarLogin(ctx, {
|
await registrarLogin(ctx, {
|
||||||
@@ -383,6 +333,13 @@ export const registrarTentativaLogin = mutation({
|
|||||||
motivoFalha: args.motivoFalha,
|
motivoFalha: args.motivoFalha,
|
||||||
ipAddress: args.ipAddress,
|
ipAddress: args.ipAddress,
|
||||||
userAgent: args.userAgent,
|
userAgent: args.userAgent,
|
||||||
|
latitudeGPS: args.latitudeGPS,
|
||||||
|
longitudeGPS: args.longitudeGPS,
|
||||||
|
precisaoGPS: args.precisaoGPS,
|
||||||
|
enderecoGPS: args.enderecoGPS,
|
||||||
|
cidadeGPS: args.cidadeGPS,
|
||||||
|
estadoGPS: args.estadoGPS,
|
||||||
|
paisGPS: args.paisGPS,
|
||||||
});
|
});
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -787,13 +787,21 @@ export default defineSchema({
|
|||||||
device: v.optional(v.string()),
|
device: v.optional(v.string()),
|
||||||
browser: v.optional(v.string()),
|
browser: v.optional(v.string()),
|
||||||
sistema: v.optional(v.string()),
|
sistema: v.optional(v.string()),
|
||||||
// Informações de Localização
|
// Informações de Localização (por IP)
|
||||||
latitude: v.optional(v.number()),
|
latitude: v.optional(v.number()),
|
||||||
longitude: v.optional(v.number()),
|
longitude: v.optional(v.number()),
|
||||||
endereco: v.optional(v.string()),
|
endereco: v.optional(v.string()),
|
||||||
cidade: v.optional(v.string()),
|
cidade: v.optional(v.string()),
|
||||||
estado: v.optional(v.string()),
|
estado: v.optional(v.string()),
|
||||||
pais: v.optional(v.string()),
|
pais: v.optional(v.string()),
|
||||||
|
// Informações de Localização (GPS do navegador)
|
||||||
|
latitudeGPS: v.optional(v.number()),
|
||||||
|
longitudeGPS: v.optional(v.number()),
|
||||||
|
precisaoGPS: v.optional(v.number()),
|
||||||
|
enderecoGPS: v.optional(v.string()),
|
||||||
|
cidadeGPS: v.optional(v.string()),
|
||||||
|
estadoGPS: v.optional(v.string()),
|
||||||
|
paisGPS: v.optional(v.string()),
|
||||||
timestamp: v.number(),
|
timestamp: v.number(),
|
||||||
})
|
})
|
||||||
.index("by_usuario", ["usuarioId"])
|
.index("by_usuario", ["usuarioId"])
|
||||||
|
|||||||
Reference in New Issue
Block a user