#![allow(dead_code)]
use anyhow::{Context, Result};
use chrono::Local;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use crate::paths;
use crate::worker::config::WorkerConfig;
use crate::worker::supabase::{RuleRow, SupabaseClient};
const POLL_INTERVAL_SECS: u64 = 10;
pub fn run(cfg: WorkerConfig) -> Result<()> {
let client_id = cfg
.client_id
.clone()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "unknown".into());
let (url, key) = match (&cfg.supabase_url, &cfg.supabase_key) {
(Some(u), Some(k)) if !u.is_empty() && !k.is_empty() => (u.clone(), k.clone()),
_ => {
log_line("[rules-sync] disabled (Supabase not configured)");
return Ok(());
}
};
let supabase = SupabaseClient::new(&url, &key, &client_id)?;
log_line("[rules-sync] thread up");
if let Err(e) = bootstrap(&supabase, &cfg) {
log_line(&format!("[rules-sync] bootstrap err: {}", e));
}
let mut last_applied: HashMap<String, String> = HashMap::new();
loop {
let _ = supabase.heartbeat("rules-sync");
match supabase.list_rules() {
Ok(rows) => {
for row in rows {
let prev = last_applied.get(&row.scope);
if prev.map(|s| s == &row.updated_at).unwrap_or(false) {
continue;
}
match write_to_disk(&cfg, &row) {
Ok(path) => {
log_line(&format!(
"[rules-sync] wrote {} (updated_at={})",
path.display(),
row.updated_at
));
last_applied.insert(row.scope.clone(), row.updated_at.clone());
}
Err(e) => log_line(&format!(
"[rules-sync] write err for scope={}: {}",
row.scope, e
)),
}
}
}
Err(e) => log_line(&format!("[rules-sync] list err: {}", e)),
}
thread::sleep(Duration::from_secs(POLL_INTERVAL_SECS));
}
}
fn bootstrap(supabase: &SupabaseClient, _cfg: &WorkerConfig) -> Result<()> {
let existing: std::collections::HashSet<String> = supabase
.list_rules()
.unwrap_or_default()
.into_iter()
.map(|r| r.scope)
.collect();
let global_path = paths::worker_rules_file()?;
if !existing.contains("global") {
if let Ok(text) = fs::read_to_string(&global_path) {
if !text.trim().is_empty() {
supabase.upsert_rule("global", &text)?;
log_line(&format!(
"[rules-sync] bootstrapped global rules from {}",
global_path.display()
));
}
}
}
Ok(())
}
fn write_to_disk(_cfg: &WorkerConfig, row: &RuleRow) -> Result<PathBuf> {
let path = match row.scope.as_str() {
"global" => paths::worker_rules_file()?,
other => {
return Err(anyhow::anyhow!(
"Ignoring rule with unsupported scope: {}",
other
))
}
};
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| format!("mkdir {}", parent.display()))?;
}
fs::write(&path, &row.content).with_context(|| format!("write {}", path.display()))?;
Ok(path)
}
fn log_line(msg: &str) {
let now = Local::now().format("%Y-%m-%d %H:%M:%S");
println!("{} {}", now, msg);
}