From 7c036b916f4540b270011157d40ae0f1676b5e14 Mon Sep 17 00:00:00 2001 From: Coder Agent Date: Sun, 5 Jul 2026 15:52:21 -0300 Subject: [PATCH] feat: configurar Supabase via UI no modal (sem mexer em config.json) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona ler_config_supabase() e salvar_config_supabase() no app.rs - App abre mesmo sem config (modo offline-only); Supabase é configurável depois - Banner amarelo no topo alerta quando Supabase não está configurado - Nova seção '☁️ Supabase' no modal de Configurações com campos mascarados - Botões: Salvar chaves, Testar conexão, Revelar/Esconder --- src-tauri/src/main.rs | 24 ++++-- src-tauri/src/plugins/app.rs | 84 ++++++++++++++++++++ src/index.html | 147 +++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9c957bb..33720b5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -57,11 +57,22 @@ fn main() { fs::create_dir_all(&data_dir).expect("não foi possível criar diretório de dados"); log::info!("Data dir: {:?}", data_dir); - // Carrega chaves do Supabase — obrigatório vir do config.json - let (anon_key, service_key) = load_config().expect("falha ao carregar configuracao do Supabase"); - log::info!("Supabase: keys carregadas (anon={}..., service={}...)", - &anon_key[..8.min(anon_key.len())], - &service_key[..8.min(service_key.len())]); + // Carrega chaves do Supabase — opcional. App abre mesmo sem config (modo offline-only). + // O usuário pode configurar depois via modal ⚙️. + let (anon_key, service_key) = match load_config() { + Ok(keys) => { + log::info!("Supabase: keys carregadas (anon={}..., service={}...)", + &keys.0[..8.min(keys.0.len())], + &keys.1[..8.min(keys.1.len())]); + keys + } + Err(e) => { + log::warn!("Supabase: configuracao ausente — app abrindo em modo offline-only."); + log::warn!("Motivo: {}", e); + log::warn!("Use o botao Configurar no app pra definir as chaves."); + (String::new(), String::new()) + } + }; // Inicializa plugins let storage = storage::init(data_dir.clone()) @@ -111,6 +122,9 @@ fn main() { plugins::app::set_loja, plugins::app::get_config, plugins::app::set_config, + // Supabase config (gerenciado pela UI) + plugins::app::ler_config_supabase, + plugins::app::salvar_config_supabase, ]) .run(tauri::generate_context!()) .expect("erro ao inicializar Tauri"); diff --git a/src-tauri/src/plugins/app.rs b/src-tauri/src/plugins/app.rs index 5019282..0435767 100644 --- a/src-tauri/src/plugins/app.rs +++ b/src-tauri/src/plugins/app.rs @@ -111,3 +111,87 @@ pub fn set_config(state: State<'_, AppState>, cfg: AppConfig) -> Result<(), Stri pub fn init(data_dir: PathBuf) -> AppState { AppState::new(data_dir) } + +/// Lê o config.json cru e devolve as chaves do Supabase. +/// Retorna masked:true se as chaves existirem (sem expor os valores completos). +/// Retorna masked:false se o arquivo não existir ou estiver vazio. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SupabaseConfigStatus { + pub tem_config: bool, + pub anon_masked: String, + pub service_masked: String, + pub config_path: String, +} + +#[tauri::command] +pub fn ler_config_supabase() -> SupabaseConfigStatus { + let config_path = crate::plugins::app::data_dir_for_status() + .join("config.json"); + + let (anon, service) = match fs::read_to_string(&config_path) { + Ok(content) => { + let cfg: serde_json::Value = serde_json::from_str(&content).unwrap_or_default(); + ( + cfg.get("SUPABASE_ANON_KEY").and_then(|v| v.as_str()).unwrap_or("").to_string(), + cfg.get("SUPABASE_SERVICE_KEY").and_then(|v| v.as_str()).unwrap_or("").to_string(), + ) + } + Err(_) => (String::new(), String::new()), + }; + + SupabaseConfigStatus { + tem_config: !anon.is_empty() && !service.is_empty(), + anon_masked: mask_key(&anon), + service_masked: mask_key(&service), + config_path: config_path.to_string_lossy().to_string(), + } +} + +fn mask_key(key: &str) -> String { + if key.is_empty() { + return String::new(); + } + if key.len() <= 12 { + return format!("{}...", &key[..4.min(key.len())]); + } + format!("{}...{}", &key[..6], &key[key.len()-4..]) +} + +/// Salva (ou atualiza) as chaves do Supabase no config.json. +/// Cria o diretório se necessário. Mantém os outros campos do config.json intactos. +#[tauri::command] +pub fn salvar_config_supabase(anon_key: String, service_key: String) -> Result { + if anon_key.trim().is_empty() || service_key.trim().is_empty() { + return Err("As duas chaves são obrigatórias.".to_string()); + } + + let config_path = crate::plugins::app::data_dir_for_status().join("config.json"); + if let Some(parent) = config_path.parent() { + fs::create_dir_all(parent).map_err(|e| format!("Falha ao criar diretório: {}", e))?; + } + + // Lê config existente pra preservar outros campos (loja, pdv_nome, etc.) + let mut cfg: serde_json::Value = fs::read_to_string(&config_path) + .ok() + .and_then(|c| serde_json::from_str(&c).ok()) + .unwrap_or_else(|| serde_json::json!({})); + + cfg["SUPABASE_ANON_KEY"] = serde_json::Value::String(anon_key.trim().to_string()); + cfg["SUPABASE_SERVICE_KEY"] = serde_json::Value::String(service_key.trim().to_string()); + + let json = serde_json::to_string_pretty(&cfg) + .map_err(|e| format!("Falha ao serializar config: {}", e))?; + fs::write(&config_path, json).map_err(|e| format!("Falha ao salvar config: {}", e))?; + + log::info!("Supabase config salva em {:?}", config_path); + + Ok(ler_config_supabase()) +} + +/// Helper pra obter o data_dir sem precisar importar de main.rs. +/// Mantém o mesmo cálculo de `main::data_dir_path()`. +pub fn data_dir_for_status() -> PathBuf { + dirs::data_local_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("FechamentoCaixa") +} diff --git a/src/index.html b/src/index.html index 98f2a9b..54c1750 100644 --- a/src/index.html +++ b/src/index.html @@ -292,6 +292,9 @@ +
Fechamento de Caixa @@ -547,6 +550,23 @@
+ +
+
☁️ Supabase (sincronização na nuvem)
+
+ Verificando... +
+
+ + +
+
+ + + +
+ +