1use super::*;
2use serde::{Deserialize, Serialize};
3
4const JOB_STATE_COLS: &str = "repo, job_id, last_run, last_task_status, active_task_id";
11
12#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
14pub struct JobState {
15 pub repo: String,
16 pub job_id: String,
17 pub last_run: Option<String>,
18 pub last_task_status: Option<String>,
19 pub active_task_id: Option<String>,
20}
21
22impl TaskStore {
23 pub async fn get_job_state(
24 &self,
25 repo: &str,
26 job_id: &str,
27 ) -> anyhow::Result<Option<JobState>> {
28 let sql = format!(
29 "SELECT {} FROM job_state WHERE repo = ? AND job_id = ?",
30 JOB_STATE_COLS
31 );
32 let row: Option<JobState> = sqlx::query_as(&sql)
33 .bind(repo)
34 .bind(job_id)
35 .fetch_optional(&self.pool)
36 .await?;
37 Ok(row)
38 }
39
40 pub async fn upsert_job_state(&self, state: &JobState) -> anyhow::Result<()> {
42 sqlx::query(
43 "INSERT INTO job_state (repo, job_id, last_run, last_task_status, active_task_id)
44 VALUES (?, ?, ?, ?, ?)
45 ON CONFLICT(repo, job_id) DO UPDATE SET
46 last_run = excluded.last_run,
47 last_task_status = excluded.last_task_status,
48 active_task_id = excluded.active_task_id",
49 )
50 .bind(&state.repo)
51 .bind(&state.job_id)
52 .bind(&state.last_run)
53 .bind(&state.last_task_status)
54 .bind(&state.active_task_id)
55 .execute(&self.pool)
56 .await?;
57 Ok(())
58 }
59
60 pub async fn list_job_states(&self, repo: &str) -> anyhow::Result<Vec<JobState>> {
62 let sql = format!("SELECT {} FROM job_state WHERE repo = ?", JOB_STATE_COLS);
63 let rows: Vec<JobState> = sqlx::query_as(&sql)
64 .bind(repo)
65 .fetch_all(&self.pool)
66 .await?;
67 Ok(rows)
68 }
69
70 pub async fn subscribe_channel(
76 &self,
77 channel: &str,
78 thread_id: &str,
79 repo: &str,
80 topic_id: Option<&str>,
81 ) -> anyhow::Result<()> {
82 let topic = topic_id.unwrap_or("");
83 sqlx::query(
84 "INSERT OR IGNORE INTO channel_subscriptions (channel, thread_id, repo, topic_id) VALUES (?, ?, ?, ?)",
85 )
86 .bind(channel)
87 .bind(thread_id)
88 .bind(repo)
89 .bind(topic)
90 .execute(&self.pool)
91 .await?;
92 Ok(())
93 }
94
95 pub async fn unsubscribe_channel(
97 &self,
98 channel: &str,
99 thread_id: &str,
100 repo: &str,
101 ) -> anyhow::Result<()> {
102 sqlx::query(
103 "DELETE FROM channel_subscriptions WHERE channel = ? AND thread_id = ? AND repo = ?",
104 )
105 .bind(channel)
106 .bind(thread_id)
107 .bind(repo)
108 .execute(&self.pool)
109 .await?;
110 Ok(())
111 }
112
113 #[cfg(test)]
115 pub async fn list_channel_subscriptions(
116 &self,
117 channel: &str,
118 thread_id: &str,
119 ) -> anyhow::Result<Vec<String>> {
120 let rows: Vec<(String,)> = sqlx::query_as(
121 "SELECT repo FROM channel_subscriptions WHERE channel = ? AND thread_id = ?",
122 )
123 .bind(channel)
124 .bind(thread_id)
125 .fetch_all(&self.pool)
126 .await?;
127 Ok(rows.into_iter().map(|r| r.0).collect())
128 }
129
130 pub async fn list_subscribers_for_repo(
132 &self,
133 repo: &str,
134 ) -> anyhow::Result<Vec<(String, String, String)>> {
135 let rows: Vec<(String, String, String)> = sqlx::query_as(
136 "SELECT channel, thread_id, topic_id FROM channel_subscriptions WHERE repo = ?",
137 )
138 .bind(repo)
139 .fetch_all(&self.pool)
140 .await?;
141 Ok(rows)
142 }
143}