Skip to main content

apalis_board_web/
lib.rs

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