use std::path::PathBuf;
use systemprompt_database::DbPool;
use systemprompt_security::authz::{
AccessControlConfig, AccessControlIngestionService, IngestOptions,
};
use crate::error::{SyncError, SyncResult};
use crate::models::{LocalSyncDirection, LocalSyncResult};
#[derive(Debug)]
pub struct AccessControlLocalSync {
db: DbPool,
yaml_path: PathBuf,
}
impl AccessControlLocalSync {
pub const fn new(db: DbPool, yaml_path: PathBuf) -> Self {
Self { db, yaml_path }
}
pub async fn sync_to_db(
&self,
override_existing: bool,
delete_orphans: bool,
) -> SyncResult<LocalSyncResult> {
if !self.yaml_path.exists() {
return Err(SyncError::MissingConfig(format!(
"Access-control config not found at: {}",
self.yaml_path.display()
)));
}
let raw = std::fs::read_to_string(&self.yaml_path).map_err(|e| {
SyncError::internal(format!(
"Failed to read {}: {}",
self.yaml_path.display(),
e
))
})?;
let config: AccessControlConfig = serde_yaml::from_str(&raw).map_err(|e| {
SyncError::invalid_input(format!(
"Failed to parse {} as AccessControlConfig: {}",
self.yaml_path.display(),
e
))
})?;
let svc = AccessControlIngestionService::new(&self.db).map_err(SyncError::internal)?;
let report = svc
.ingest_config(
&config,
IngestOptions {
override_existing,
delete_orphans,
},
)
.await
.map_err(SyncError::internal)?;
Ok(LocalSyncResult {
items_synced: report.rules_inserted + report.rules_updated,
items_skipped: report.rules_skipped,
items_skipped_modified: 0,
items_deleted: report.rules_deleted,
errors: Vec::new(),
direction: LocalSyncDirection::ToDatabase,
})
}
}