Skip to main content

thor_notify/
inbox.rs

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
17/// Get the inbox directory path
18pub fn inbox_dir() -> PathBuf {
19    dirs::data_dir()
20        .unwrap_or_else(|| PathBuf::from("."))
21        .join("thor")
22        .join("inbox")
23}
24
25/// Ensure inbox directory exists
26pub 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
32/// Save a notification to the inbox
33pub 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
49/// List all notifications in the inbox
50pub 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    // Sort by timestamp, newest first
71    notifications.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
72
73    Ok(notifications)
74}
75
76/// Get a notification by ID
77pub 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
85/// Delete a notification by ID
86pub 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}