1use crate::schema::Notification;
2use std::path::PathBuf;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum InboxError {
7 #[error("IO error: {0}")]
8 Io(#[from] std::io::Error),
9 #[error("JSON error: {0}")]
10 Json(#[from] serde_json::Error),
11 #[error("Notification not found: {0}")]
12 NotFound(String),
13}
14
15pub type Result<T> = std::result::Result<T, InboxError>;
16
17pub fn inbox_dir() -> PathBuf {
19 dirs::data_dir()
20 .unwrap_or_else(|| PathBuf::from("."))
21 .join("thor")
22 .join("inbox")
23}
24
25pub async fn ensure_inbox_dir() -> Result<PathBuf> {
27 let dir = inbox_dir();
28 tokio::fs::create_dir_all(&dir).await?;
29 Ok(dir)
30}
31
32pub async fn save_notification(notification: &Notification) -> Result<PathBuf> {
34 let dir = ensure_inbox_dir().await?;
35 let filename = format!(
36 "{}-{}-{}.json",
37 notification.timestamp.format("%Y%m%d%H%M%S"),
38 notification.branch.replace('/', "-"),
39 notification.id
40 );
41 let path = dir.join(filename);
42
43 let json = serde_json::to_string_pretty(notification)?;
44 tokio::fs::write(&path, json).await?;
45
46 Ok(path)
47}
48
49pub async fn list_notifications() -> Result<Vec<Notification>> {
51 let dir = inbox_dir();
52 if !dir.exists() {
53 return Ok(Vec::new());
54 }
55
56 let mut notifications = Vec::new();
57
58 let mut entries = tokio::fs::read_dir(dir).await?;
59 while let Some(entry) = entries.next_entry().await? {
60 let path = entry.path();
61
62 if path.extension().map(|e| e == "json").unwrap_or(false) {
63 let content = tokio::fs::read_to_string(&path).await?;
64 if let Ok(notification) = serde_json::from_str::<Notification>(&content) {
65 notifications.push(notification);
66 }
67 }
68 }
69
70 notifications.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
72
73 Ok(notifications)
74}
75
76pub async fn get_notification(id: &str) -> Result<Notification> {
78 let notifications = list_notifications().await?;
79 notifications
80 .into_iter()
81 .find(|n| n.id.to_string() == id || n.id.to_string().starts_with(id))
82 .ok_or_else(|| InboxError::NotFound(id.to_string()))
83}
84
85pub async fn delete_notification(id: &str) -> Result<()> {
87 let dir = inbox_dir();
88
89 let mut entries = tokio::fs::read_dir(&dir).await?;
90 while let Some(entry) = entries.next_entry().await? {
91 let path = entry.path();
92
93 if path.extension().map(|e| e == "json").unwrap_or(false) {
94 if let Ok(content) = tokio::fs::read_to_string(&path).await {
95 if let Ok(n) = serde_json::from_str::<Notification>(&content) {
96 if n.id.to_string() == id || n.id.to_string().starts_with(id) {
97 tokio::fs::remove_file(path).await?;
98 return Ok(());
99 }
100 }
101 }
102 }
103 }
104
105 Err(InboxError::NotFound(id.to_string()))
106}