apalis_board/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2use apalis_board_types::LogEntry;
3use chrono::{DateTime, Local, Utc};
4use leptos::{prelude::*, reactive::spawn_local};
5use serde::{Deserialize, Serialize};
6
7pub mod api;
8pub mod components;
9pub mod pages;
10pub mod translate;
11
12leptos_i18n::load_locales!();
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
15pub struct User {
16    pub id: String,
17    pub username: String,
18}
19
20type UserSignal = RwSignal<Option<User>>;
21pub fn use_user_signal() -> UserSignal {
22    use_context::<UserSignal>().expect("UserSignal")
23}
24
25pub fn use_sse_signal() -> RwSignal<Vec<String>> {
26    use_context::<RwSignal<Vec<String>>>().expect("SSE Signal")
27}
28
29#[derive(Clone, Copy)]
30pub struct SseProvider {
31    event_source: RwSignal<LogEntry>,
32    is_healthy: RwSignal<bool>,
33}
34
35impl SseProvider {
36    pub fn event_source(&self) -> RwSignal<LogEntry> {
37        self.event_source
38    }
39
40    pub fn is_healthy(&self) -> RwSignal<bool> {
41        self.is_healthy
42    }
43}
44
45pub fn use_sse_provider() -> SseProvider {
46    use_context::<SseProvider>().expect("SSE Provider")
47}
48
49pub fn create_sse_resource(url: &str) -> SseProvider {
50    use futures::StreamExt;
51    let data = RwSignal::new(LogEntry::default());
52    let mut source = gloo_net::eventsource::futures::EventSource::new(url)
53        .expect("couldn't connect to SSE stream");
54    let mut stream = source
55        .subscribe("message")
56        .unwrap()
57        .map(|value| match value {
58            Ok(value) => Ok(serde_json::from_str::<LogEntry>(
59                &value.1.data().as_string().expect("expected string value"),
60            )
61            .expect("couldn't parse message")),
62            Err(e) => Err(e),
63        });
64    let is_healthy = RwSignal::new(true);
65    spawn_local(async move {
66        while let Some(next_value) = stream.next().await {
67            if let Ok(log_entry) = next_value {
68                data.set(log_entry);
69            } else {
70                is_healthy.set(false);
71                source.close();
72                break;
73            }
74        }
75    });
76    SseProvider {
77        event_source: data,
78        is_healthy,
79    }
80}
81
82pub fn relative_timestamp(timestamp: u64) -> String {
83    let now = Utc::now().timestamp() as u64;
84
85    match timestamp.cmp(&now) {
86        std::cmp::Ordering::Greater => {
87            let future_diff = timestamp - now;
88            match future_diff {
89                0..=59 => "in a few seconds".to_string(),
90                60..=3599 => format!("in {} minutes", future_diff / 60),
91                3600..=86399 => format!("in {} hours", future_diff / 3600),
92                86400..=2_592_000 => format!("in {} days", future_diff / 86400),
93                _ => {
94                    let datetime = DateTime::<Utc>::from_timestamp(timestamp as i64, 0)
95                        .expect("Invalid timestamp");
96                    let local: DateTime<Local> = datetime.into();
97                    format!("on {}", local.format("%B %d, %Y"))
98                }
99            }
100        }
101        _ => {
102            let diff = now.saturating_sub(timestamp);
103            match diff {
104                0..=59 => "just now".to_string(),
105                60..=3599 => format!("{} minutes ago", diff / 60),
106                3600..=86399 => format!("{} hours ago", diff / 3600),
107                86400..=2_592_000 => format!("{} days ago", diff / 86400),
108                _ => {
109                    let datetime = DateTime::<Utc>::from_timestamp(timestamp as i64, 0)
110                        .expect("Invalid timestamp");
111                    let local: DateTime<Local> = datetime.into();
112                    local.format("%B %d, %Y").to_string()
113                }
114            }
115        }
116    }
117}
118
119pub type RawTask = apalis_core::task::Task<serde_json::Value, serde_json::Value, String>;