systemprompt_sync/local/
access_control_sync.rs1use std::path::PathBuf;
15
16use systemprompt_database::DbPool;
17use systemprompt_security::authz::{
18 AccessControlConfig, AccessControlIngestionService, IngestOptions,
19};
20
21use crate::error::{SyncError, SyncResult};
22use crate::models::{LocalSyncDirection, LocalSyncResult};
23
24#[derive(Debug)]
25pub struct AccessControlLocalSync {
26 db: DbPool,
27 yaml_path: PathBuf,
28}
29
30impl AccessControlLocalSync {
31 pub const fn new(db: DbPool, yaml_path: PathBuf) -> Self {
32 Self { db, yaml_path }
33 }
34
35 pub async fn sync_to_db(
36 &self,
37 override_existing: bool,
38 delete_orphans: bool,
39 ) -> SyncResult<LocalSyncResult> {
40 if !self.yaml_path.exists() {
41 return Err(SyncError::MissingConfig(format!(
42 "Access-control config not found at: {}",
43 self.yaml_path.display()
44 )));
45 }
46
47 let raw = std::fs::read_to_string(&self.yaml_path).map_err(|e| {
48 SyncError::internal(format!(
49 "Failed to read {}: {}",
50 self.yaml_path.display(),
51 e
52 ))
53 })?;
54 let config: AccessControlConfig = serde_yaml::from_str(&raw).map_err(|e| {
55 SyncError::invalid_input(format!(
56 "Failed to parse {} as AccessControlConfig: {}",
57 self.yaml_path.display(),
58 e
59 ))
60 })?;
61
62 let options = IngestOptions {
63 override_existing,
64 delete_orphans,
65 };
66
67 let svc = AccessControlIngestionService::new(&self.db).map_err(SyncError::internal)?;
68 let report = svc
69 .ingest_config(&config, options)
70 .await
71 .map_err(SyncError::internal)?;
72
73 Ok(LocalSyncResult {
74 items_synced: report.inserted + report.updated,
75 items_skipped: report.skipped,
76 items_skipped_modified: 0,
77 items_deleted: report.deleted,
78 errors: Vec::new(),
79 direction: LocalSyncDirection::ToDatabase,
80 })
81 }
82}