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 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 pub poll_interval_ms: u64,
59}