1use std::fmt::Display;
2use std::sync::Arc;
3
4use cookie::Key;
5
6use crate::auth::{Gates, LoginThrottle};
7use crate::cache::Cache;
8use crate::db::Db;
9use crate::events::Listeners;
10use crate::i18n::Translator;
11use crate::mail::Mailer;
12use crate::queue::Queue;
13use crate::storage::Storage;
14use crate::{Config, Result, RouteTable, Views};
15
16#[derive(Clone)]
24pub struct AppState(Arc<AppStateInner>);
25
26impl std::ops::Deref for AppState {
27 type Target = AppStateInner;
28
29 fn deref(&self) -> &AppStateInner {
30 &self.0
31 }
32}
33
34impl AppState {
35 pub(crate) fn new(inner: AppStateInner) -> Self {
37 AppState(Arc::new(inner))
38 }
39}
40
41#[non_exhaustive]
44pub struct AppStateInner {
45 pub config: Arc<Config>,
47 pub(crate) routes: Arc<RouteTable>,
49 pub(crate) views: Views,
51 pub db: Db,
53 pub mailer: Mailer,
55 pub(crate) mailers: Arc<std::collections::HashMap<String, Mailer>>,
57 pub queue: Queue,
59 pub cache: Cache,
61 pub storage: Storage,
63 pub(crate) disks: Arc<std::collections::HashMap<String, Storage>>,
65 pub http: crate::http::Http,
67 pub(crate) translator: Arc<Translator>,
69 pub(crate) live: Option<Arc<crate::live::Live>>,
71 pub(crate) notification_hub: Arc<crate::auth::notifications::Hub>,
73 pub(crate) listeners: Listeners,
74 pub(crate) key: Key,
75 pub(crate) session_mirror:
78 Option<Arc<std::sync::Mutex<std::collections::HashMap<String, String>>>>,
79 pub(crate) gates: Gates,
80 pub(crate) async_gates: Arc<std::collections::HashMap<String, crate::auth::AsyncGate>>,
81 pub(crate) throttle: Arc<LoginThrottle>,
82 pub(crate) second_factor: Option<Arc<crate::auth::second_factor::SecondFactor>>,
84 pub(crate) account_sections: Arc<Vec<crate::auth::account::AccountSection>>,
86 pub(crate) auth: Option<Arc<crate::auth::module::Settings>>,
88 pub(crate) detect_locale: bool,
90 pub(crate) security: Arc<crate::security::Security>,
91 pub(crate) webhooks: crate::webhook::Handlers,
92 #[allow(dead_code)]
94 pub(crate) live_components: crate::live_component::LiveMap,
95 pub(crate) shares: Arc<Vec<(String, crate::view::ShareFn)>>,
97 pub(crate) channels:
99 Arc<std::collections::HashMap<String, crate::auth::notifications::ChannelFn>>,
100 pub(crate) provided: crate::provided::ProvidedMap,
102 pub(crate) inspector: Option<Arc<crate::inspector::Inspector>>,
104 pub(crate) limiters: Arc<std::collections::HashMap<String, crate::rate_limit::NamedLimiter>>,
106 pub(crate) reporters: Arc<Vec<crate::report::ReportFn>>,
108 pub(crate) fakes: Arc<Fakes>,
110}
111
112#[derive(Debug, Clone)]
114#[non_exhaustive]
115pub struct SentNotification {
116 pub kind: &'static str,
118 pub to: crate::auth::Recipient,
120}
121
122#[derive(Debug, Clone, PartialEq)]
125#[non_exhaustive]
126pub struct SentBroadcast {
127 pub user_id: Option<i64>,
129 pub event: String,
131 pub data: serde_json::Value,
133}
134
135#[derive(Default)]
138pub(crate) struct Fakes {
139 pub events: std::sync::Mutex<Option<Vec<Box<dyn std::any::Any + Send>>>>,
140 pub notifications: std::sync::Mutex<Option<Vec<SentNotification>>>,
141 pub broadcasts: std::sync::Mutex<Option<Vec<SentBroadcast>>>,
142}
143
144impl Fakes {
145 pub(crate) fn record_event<E: Send + 'static>(&self, event: E) -> bool {
147 let mut events = self.events.lock().unwrap_or_else(|e| e.into_inner());
148 match events.as_mut() {
149 Some(list) => {
150 list.push(Box::new(event));
151 true
152 }
153 None => false,
154 }
155 }
156
157 pub(crate) fn record_broadcast(&self, broadcast: SentBroadcast) -> bool {
159 let mut sent = self.broadcasts.lock().unwrap_or_else(|e| e.into_inner());
160 match sent.as_mut() {
161 Some(list) => {
162 list.push(broadcast);
163 true
164 }
165 None => false,
166 }
167 }
168
169 pub(crate) fn record_notification(
171 &self,
172 kind: &'static str,
173 to: &crate::auth::Recipient,
174 ) -> bool {
175 let mut sent = self.notifications.lock().unwrap_or_else(|e| e.into_inner());
176 match sent.as_mut() {
177 Some(list) => {
178 list.push(SentNotification {
179 kind,
180 to: to.clone(),
181 });
182 true
183 }
184 None => false,
185 }
186 }
187}
188
189impl AppState {
190 pub fn url(&self, name: &str, params: &[&dyn Display]) -> Result<String> {
192 Ok(self.routes.url(name, params)?)
193 }
194
195 pub fn encrypt(&self, plain: &str) -> String {
208 crate::crypto::seal(&self.key, plain)
209 }
210
211 pub fn decrypt(&self, sealed: &str) -> Result<String> {
214 Ok(crate::crypto::open(&self.key, sealed)?)
215 }
216}
217
218impl AppState {
219 pub fn disk_named(&self, name: &str) -> Result<&Storage> {
222 self.disks.get(name).ok_or_else(|| {
223 anyhow::anyhow!("no disk named `{name}`: add it with `App::disk(\"{name}\", …)`").into()
224 })
225 }
226}