Skip to main content

awa_ui/
state.rs

1use std::time::Duration;
2
3use serde::Serialize;
4use sqlx::PgPool;
5
6use crate::cache::DashboardCache;
7use crate::error::ApiError;
8
9#[derive(Clone)]
10pub struct AppState {
11    pub pool: PgPool,
12    pub read_only: bool,
13    pub cache: DashboardCache,
14    pub callback_hmac_secret: Option<[u8; 32]>,
15    /// Suggested frontend poll interval — at least as long as the cache TTL
16    /// so clients don't poll faster than the cache can refresh.
17    pub poll_interval_ms: u64,
18}
19
20impl AppState {
21    pub fn new(
22        pool: PgPool,
23        read_only: bool,
24        cache_ttl: Duration,
25        callback_hmac_secret: Option<[u8; 32]>,
26    ) -> Self {
27        let poll_interval_ms = cache_ttl.as_millis().max(5_000) as u64;
28        Self {
29            pool,
30            read_only,
31            cache: DashboardCache::new(cache_ttl),
32            callback_hmac_secret,
33            poll_interval_ms,
34        }
35    }
36
37    pub fn require_writable(&self) -> Result<(), ApiError> {
38        if self.read_only {
39            return Err(ApiError::read_only());
40        }
41        Ok(())
42    }
43}
44
45pub async fn detect_read_only(pool: &PgPool) -> Result<bool, sqlx::Error> {
46    let read_only =
47        sqlx::query_scalar::<_, bool>("SELECT current_setting('transaction_read_only') = 'on'")
48            .fetch_one(pool)
49            .await?;
50    Ok(read_only)
51}
52
53#[derive(Debug, Serialize)]
54pub struct Capabilities {
55    pub read_only: bool,
56    /// Suggested polling interval in milliseconds. The server sets this
57    /// higher for read-only replicas where data is inherently stale.
58    pub poll_interval_ms: u64,
59}