feat: configurar Supabase via UI no modal (sem mexer em config.json)
Build Fechamento de Caixa / build (macos-14, app) (push) Has been cancelled
Build Fechamento de Caixa / build (ubuntu-22.04, deb) (push) Has been cancelled
Build Fechamento de Caixa / build (windows-2022, msi) (push) Has been cancelled

- 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
This commit is contained in:
Coder Agent
2026-07-05 15:52:21 -03:00
parent c58f01d51b
commit 7c036b916f
3 changed files with 250 additions and 5 deletions
+19 -5
View File
@@ -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");
+84
View File
@@ -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<SupabaseConfigStatus, String> {
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")
}