mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
167 lines
5.0 KiB
Rust
167 lines
5.0 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use crate::{
|
|
api::{
|
|
client::{AdminApiClient, ApiResultExt},
|
|
types::{AccountIdentityMode, AccountIdentitySettings, PremiumBranding, ReportReasonEntry},
|
|
},
|
|
config::AdminConfig,
|
|
};
|
|
use std::{
|
|
sync::{Arc, Mutex},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
const PREMIUM_BRANDING_TTL: Duration = Duration::from_secs(60);
|
|
const ACCOUNT_IDENTITY_TTL: Duration = Duration::from_secs(60);
|
|
const ACCOUNT_IDENTITY_RETRY_TTL: Duration = Duration::from_secs(10);
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
inner: Arc<AppStateInner>,
|
|
}
|
|
|
|
struct AppStateInner {
|
|
pub config: AdminConfig,
|
|
pub http_client: reqwest::Client,
|
|
premium_branding: Mutex<Option<(Instant, PremiumBranding)>>,
|
|
account_identity: Mutex<Option<(Instant, AccountIdentitySettings)>>,
|
|
report_reasons: Mutex<Option<Arc<[ReportReasonEntry]>>>,
|
|
}
|
|
|
|
impl AppState {
|
|
pub fn new(config: AdminConfig) -> Self {
|
|
let http_client = reqwest::Client::builder()
|
|
.user_agent(format!("FluxerAdmin/{} (Rust)", config.build_version))
|
|
.build()
|
|
.expect("failed to create HTTP client");
|
|
Self {
|
|
inner: Arc::new(AppStateInner {
|
|
config,
|
|
http_client,
|
|
premium_branding: Mutex::new(None),
|
|
account_identity: Mutex::new(None),
|
|
report_reasons: Mutex::new(None),
|
|
}),
|
|
}
|
|
}
|
|
|
|
pub fn config(&self) -> &AdminConfig {
|
|
&self.inner.config
|
|
}
|
|
|
|
pub fn http_client(&self) -> &reqwest::Client {
|
|
&self.inner.http_client
|
|
}
|
|
|
|
pub fn cached_premium_branding(&self) -> Option<PremiumBranding> {
|
|
let cache = self
|
|
.inner
|
|
.premium_branding
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
cache
|
|
.as_ref()
|
|
.filter(|(fetched_at, _)| fetched_at.elapsed() < PREMIUM_BRANDING_TTL)
|
|
.map(|(_, branding)| branding.clone())
|
|
}
|
|
|
|
pub fn remember_premium_branding(&self, branding: PremiumBranding) {
|
|
*self
|
|
.inner
|
|
.premium_branding
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some((Instant::now(), branding));
|
|
}
|
|
|
|
pub async fn premium_branding(&self, client: &AdminApiClient) -> Option<PremiumBranding> {
|
|
if let Some(branding) = self.cached_premium_branding() {
|
|
return Some(branding);
|
|
}
|
|
let branding = PremiumBranding::from_discovery(
|
|
&client
|
|
.get_instance_premium_discovery()
|
|
.await
|
|
.log_error("load premium branding")?,
|
|
);
|
|
self.remember_premium_branding(branding.clone());
|
|
Some(branding)
|
|
}
|
|
|
|
pub async fn account_identity(&self, client: &AdminApiClient) -> AccountIdentityMode {
|
|
self.account_identity_settings(client).await.mode
|
|
}
|
|
|
|
pub async fn account_identity_settings(
|
|
&self,
|
|
client: &AdminApiClient,
|
|
) -> AccountIdentitySettings {
|
|
if !self.config().self_hosted {
|
|
return AccountIdentitySettings::default();
|
|
}
|
|
let previous = *self
|
|
.inner
|
|
.account_identity
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
if let Some((expires_at, settings)) = previous
|
|
&& Instant::now() < expires_at
|
|
{
|
|
return settings;
|
|
}
|
|
let (settings, ttl) = match client
|
|
.get_instance_account_identity()
|
|
.await
|
|
.log_error("load account identity mode")
|
|
{
|
|
Some(settings) => (settings, ACCOUNT_IDENTITY_TTL),
|
|
None => (
|
|
previous.map_or(AccountIdentitySettings::default(), |(_, settings)| settings),
|
|
ACCOUNT_IDENTITY_RETRY_TTL,
|
|
),
|
|
};
|
|
*self
|
|
.inner
|
|
.account_identity
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner()) =
|
|
Some((Instant::now() + ttl, settings));
|
|
settings
|
|
}
|
|
|
|
pub async fn report_reasons(
|
|
&self,
|
|
client: &AdminApiClient,
|
|
) -> Option<Arc<[ReportReasonEntry]>> {
|
|
if let Some(reasons) = self.cached_report_reasons() {
|
|
return Some(reasons);
|
|
}
|
|
let reasons: Arc<[ReportReasonEntry]> = client
|
|
.list_report_reasons()
|
|
.await
|
|
.log_error("load report reasons")?
|
|
.reasons
|
|
.into();
|
|
*self
|
|
.inner
|
|
.report_reasons
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(reasons.clone());
|
|
Some(reasons)
|
|
}
|
|
|
|
fn cached_report_reasons(&self) -> Option<Arc<[ReportReasonEntry]>> {
|
|
self.inner
|
|
.report_reasons
|
|
.lock()
|
|
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
impl axum::extract::FromRef<AppState> for AdminConfig {
|
|
fn from_ref(state: &AppState) -> Self {
|
|
state.inner.config.clone()
|
|
}
|
|
}
|