Skip to main content

systemprompt_models/services/
scheduler.rs

1//! Scheduler job configuration and the built-in job set.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7use systemprompt_identifiers::UserId;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct JobConfig {
12    #[serde(default)]
13    pub extension: Option<String>,
14    pub name: String,
15    #[serde(default)]
16    pub owner: Option<UserId>,
17    #[serde(default = "default_true")]
18    pub enabled: bool,
19    #[serde(default)]
20    pub schedule: Option<String>,
21}
22
23const fn default_true() -> bool {
24    true
25}
26
27impl JobConfig {
28    /// A job with no explicit `owner` runs as the profile `system_admin`,
29    /// resolved per-environment at scheduler start. Set one with
30    /// [`Self::with_owner`] only for a job that must run as a specific user.
31    #[must_use]
32    pub fn new(name: impl Into<String>) -> Self {
33        Self {
34            extension: None,
35            name: name.into(),
36            owner: None,
37            enabled: true,
38            schedule: None,
39        }
40    }
41
42    #[must_use]
43    pub fn with_owner(mut self, owner: UserId) -> Self {
44        self.owner = Some(owner);
45        self
46    }
47
48    #[must_use]
49    pub fn with_extension(mut self, extension: impl Into<String>) -> Self {
50        self.extension = Some(extension.into());
51        self
52    }
53
54    #[must_use]
55    pub fn with_schedule(mut self, schedule: impl Into<String>) -> Self {
56        self.schedule = Some(schedule.into());
57        self
58    }
59
60    #[must_use]
61    pub const fn disabled(mut self) -> Self {
62        self.enabled = false;
63        self
64    }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct SchedulerConfig {
69    #[serde(default = "default_true")]
70    pub enabled: bool,
71    #[serde(default)]
72    pub jobs: Vec<JobConfig>,
73    #[serde(default = "default_bootstrap_jobs")]
74    pub bootstrap_jobs: Vec<String>,
75    #[serde(default = "default_true")]
76    pub distributed_lock: bool,
77}
78
79fn default_bootstrap_jobs() -> Vec<String> {
80    vec![
81        "database_cleanup".to_owned(),
82        "cleanup_inactive_sessions".to_owned(),
83    ]
84}
85
86impl SchedulerConfig {
87    /// The built-in core job set. The four cleanup jobs
88    /// (`cleanup_anonymous_users`, `cleanup_empty_contexts`,
89    /// `cleanup_inactive_sessions`, `database_cleanup`) have no human
90    /// originator, so they carry no explicit `owner` and run as the profile
91    /// `system_admin` resolved per-environment at scheduler start.
92    #[must_use]
93    pub fn with_system_admin() -> Self {
94        Self {
95            enabled: true,
96            jobs: vec![
97                JobConfig::new("cleanup_anonymous_users")
98                    .with_extension("core")
99                    .with_schedule("0 0 3 * * *"),
100                JobConfig::new("cleanup_empty_contexts")
101                    .with_extension("core")
102                    .with_schedule("0 0 * * * *"),
103                JobConfig::new("cleanup_inactive_sessions")
104                    .with_extension("core")
105                    .with_schedule("0 0 * * * *"),
106                JobConfig::new("database_cleanup")
107                    .with_extension("core")
108                    .with_schedule("0 0 4 * * *"),
109            ],
110            bootstrap_jobs: default_bootstrap_jobs(),
111            distributed_lock: true,
112        }
113    }
114}