systemprompt_sync/jobs/
access_control_sync.rs1use std::path::PathBuf;
9use std::sync::Arc;
10
11use async_trait::async_trait;
12use systemprompt_database::{Database, DbPool};
13use systemprompt_models::AppPaths;
14use systemprompt_traits::{Job, JobContext, JobResult, ProviderError, ProviderResult};
15
16use crate::local::AccessControlLocalSync;
17
18const DEFAULT_YAML_RELATIVE: &str = "access-control/config.yaml";
19
20#[derive(Debug, Clone, Copy)]
21pub struct AccessControlSyncJob;
22
23#[async_trait]
24impl Job for AccessControlSyncJob {
25 fn name(&self) -> &'static str {
26 "access_control_sync"
27 }
28
29 fn description(&self) -> &'static str {
30 "Project services/access-control YAML into access_control_rules"
31 }
32
33 fn schedule(&self) -> &'static str {
34 ""
35 }
36
37 fn tags(&self) -> Vec<&'static str> {
38 vec!["access-control", "sync", "bootstrap"]
39 }
40
41 fn enabled(&self) -> bool {
42 false
43 }
44
45 async fn execute(&self, ctx: &JobContext) -> ProviderResult<JobResult> {
46 let start = std::time::Instant::now();
47
48 let db_pool: &DbPool = ctx.db_pool::<DbPool>().ok_or_else(|| {
49 ProviderError::Configuration("DbPool not available in job context".into())
50 })?;
51
52 let paths = ctx
53 .app_paths::<Arc<AppPaths>>()
54 .ok_or_else(|| {
55 ProviderError::Configuration("AppPaths not available in job context".into())
56 })?
57 .as_ref();
58
59 let yaml_path = resolve_yaml_path(ctx, paths.system().services());
60 let override_existing = bool_param(ctx, "override_existing", true);
61 let delete_orphans = bool_param(ctx, "delete_orphans", true);
62
63 tracing::info!(
64 yaml_path = %yaml_path.display(),
65 override_existing,
66 delete_orphans,
67 "access_control_sync job started",
68 );
69
70 let sync = AccessControlLocalSync::new(Arc::<Database>::clone(db_pool), yaml_path);
71 let result = sync
72 .sync_to_db(override_existing, delete_orphans)
73 .await
74 .map_err(|e| ProviderError::RenderFailed(e.to_string()))?;
75
76 let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
77 tracing::info!(
78 items_synced = result.items_synced,
79 items_skipped = result.items_skipped,
80 items_deleted = result.items_deleted,
81 duration_ms,
82 "access_control_sync job completed",
83 );
84
85 Ok(JobResult::success()
86 .with_stats(result.items_synced as u64, result.errors.len() as u64)
87 .with_duration(duration_ms))
88 }
89}
90
91fn resolve_yaml_path(ctx: &JobContext, services_path: &std::path::Path) -> PathBuf {
92 ctx.parameters().get("yaml_path").map_or_else(
93 || services_path.join(DEFAULT_YAML_RELATIVE),
94 |raw| {
95 let p = std::path::Path::new(raw);
96 if p.is_absolute() {
97 p.to_path_buf()
98 } else {
99 services_path.join(p)
100 }
101 },
102 )
103}
104
105fn bool_param(ctx: &JobContext, key: &str, default: bool) -> bool {
106 ctx.parameters().get(key).map_or(default, |v| {
107 matches!(v.as_str(), "true" | "1" | "yes" | "TRUE" | "True")
108 })
109}
110
111systemprompt_provider_contracts::submit_job!(&AccessControlSyncJob);