1use super::*;
2use crate::cluster::Command;
3use serde::{Deserialize, Serialize};
4use sqlx::SqliteConnection;
5
6const JOB_STATE_COLS: &str = "repo, job_id, last_run, last_task_status, active_task_id";
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, sqlx::FromRow)]
16pub struct JobState {
17 pub repo: String,
18 pub job_id: String,
19 pub last_run: Option<String>,
20 pub last_task_status: Option<String>,
21 pub active_task_id: Option<String>,
22}
23
24impl TaskStore {
25 pub async fn get_job_state(
26 &self,
27 repo: &str,
28 job_id: &str,
29 ) -> anyhow::Result<Option<JobState>> {
30 let sql = format!(
31 "SELECT {} FROM job_state WHERE repo = ? AND job_id = ?",
32 JOB_STATE_COLS
33 );
34 let row: Option<JobState> = sqlx::query_as(&sql)
35 .bind(repo)
36 .bind(job_id)
37 .fetch_optional(&self.pool)
38 .await?;
39 Ok(row)
40 }
41
42 pub async fn upsert_job_state(&self, state: &JobState) -> anyhow::Result<()> {
44 self.write(Command::JobStateUpsert(state.clone()))
45 .await
46 .map(|_| ())
47 }
48
49 pub async fn list_job_states(&self, repo: &str) -> anyhow::Result<Vec<JobState>> {
51 let sql = format!("SELECT {} FROM job_state WHERE repo = ?", JOB_STATE_COLS);
52 let rows: Vec<JobState> = sqlx::query_as(&sql)
53 .bind(repo)
54 .fetch_all(&self.pool)
55 .await?;
56 Ok(rows)
57 }
58
59 pub async fn subscribe_channel(
65 &self,
66 channel: &str,
67 thread_id: &str,
68 repo: &str,
69 topic_id: Option<&str>,
70 ) -> anyhow::Result<()> {
71 let cmd = Command::ChannelSubscribe {
72 channel: channel.to_string(),
73 thread_id: thread_id.to_string(),
74 repo: repo.to_string(),
75 topic_id: topic_id.unwrap_or("").to_string(),
76 };
77 self.write(cmd).await.map(|_| ())
78 }
79
80 pub async fn unsubscribe_channel(
82 &self,
83 channel: &str,
84 thread_id: &str,
85 repo: &str,
86 ) -> anyhow::Result<()> {
87 let cmd = Command::ChannelUnsubscribe {
88 channel: channel.to_string(),
89 thread_id: thread_id.to_string(),
90 repo: repo.to_string(),
91 };
92 self.write(cmd).await.map(|_| ())
93 }
94
95 #[cfg(test)]
97 pub async fn list_channel_subscriptions(
98 &self,
99 channel: &str,
100 thread_id: &str,
101 ) -> anyhow::Result<Vec<String>> {
102 let rows: Vec<(String,)> = sqlx::query_as(
103 "SELECT repo FROM channel_subscriptions WHERE channel = ? AND thread_id = ?",
104 )
105 .bind(channel)
106 .bind(thread_id)
107 .fetch_all(&self.pool)
108 .await?;
109 Ok(rows.into_iter().map(|r| r.0).collect())
110 }
111
112 pub async fn list_subscribers_for_repo(
114 &self,
115 repo: &str,
116 ) -> anyhow::Result<Vec<(String, String, String)>> {
117 let rows: Vec<(String, String, String)> = sqlx::query_as(
118 "SELECT channel, thread_id, topic_id FROM channel_subscriptions WHERE repo = ?",
119 )
120 .bind(repo)
121 .fetch_all(&self.pool)
122 .await?;
123 Ok(rows)
124 }
125}
126
127pub(crate) mod apply {
129 use super::*;
130
131 pub(crate) async fn upsert_job_state(
132 conn: &mut SqliteConnection,
133 state: &JobState,
134 ) -> anyhow::Result<()> {
135 sqlx::query(
136 "INSERT INTO job_state (repo, job_id, last_run, last_task_status, active_task_id)
137 VALUES (?, ?, ?, ?, ?)
138 ON CONFLICT(repo, job_id) DO UPDATE SET
139 last_run = excluded.last_run,
140 last_task_status = excluded.last_task_status,
141 active_task_id = excluded.active_task_id",
142 )
143 .bind(&state.repo)
144 .bind(&state.job_id)
145 .bind(&state.last_run)
146 .bind(&state.last_task_status)
147 .bind(&state.active_task_id)
148 .execute(conn)
149 .await?;
150 Ok(())
151 }
152
153 pub(crate) async fn subscribe(
154 conn: &mut SqliteConnection,
155 channel: &str,
156 thread_id: &str,
157 repo: &str,
158 topic_id: &str,
159 ) -> anyhow::Result<()> {
160 sqlx::query(
161 "INSERT OR IGNORE INTO channel_subscriptions (channel, thread_id, repo, topic_id) VALUES (?, ?, ?, ?)",
162 )
163 .bind(channel)
164 .bind(thread_id)
165 .bind(repo)
166 .bind(topic_id)
167 .execute(conn)
168 .await?;
169 Ok(())
170 }
171
172 pub(crate) async fn unsubscribe(
173 conn: &mut SqliteConnection,
174 channel: &str,
175 thread_id: &str,
176 repo: &str,
177 ) -> anyhow::Result<()> {
178 sqlx::query(
179 "DELETE FROM channel_subscriptions WHERE channel = ? AND thread_id = ? AND repo = ?",
180 )
181 .bind(channel)
182 .bind(thread_id)
183 .bind(repo)
184 .execute(conn)
185 .await?;
186 Ok(())
187 }
188}