use std::sync::Arc;
use toasty::Db;
use topcoat::{
Result,
context::Cx,
view::{BoxView, View, ViewExt},
};
use super::{build_catalog, serves_tenants, validation_cx};
use crate::{
MountError,
bus::NotificationBus,
i18n::{Catalog, CatalogSource},
notification::Notifications,
panel::{
Panel,
register::Registry,
state::{PanelState, current, enter},
},
resource::Mounts,
toasty_compat::model::AppSchema,
};
#[derive(Clone)]
pub struct PanelHandle {
pub(super) db: Db,
pub(super) mounts: Arc<Mounts>,
pub(super) catalog: Arc<Catalog>,
pub(super) panel: Option<Arc<PanelState>>,
pub(super) prefix: String,
pub(super) bus: NotificationBus,
}
impl PanelHandle {
pub fn context(&self) -> Cx {
validation_cx(&self.db, &self.mounts).with_many((
HeldCatalog(Arc::clone(&self.catalog)),
CatalogSource(held_catalog),
))
}
pub fn enter(&self, cx: &Cx) -> Cx {
match &self.panel {
Some(panel) => enter(cx, panel),
None => cx.with(EnteredPanel(Arc::clone(&self.mounts))),
}
}
pub fn url(&self) -> Option<String> {
self.panel.as_ref().map(|panel| panel.realm.prefix.clone())
}
pub fn notifications(&self) -> Notifications {
Notifications::new(self.db.clone(), self.bus.clone(), self.prefix.clone(), None)
}
pub async fn prune_audit_log(&self, age: std::time::Duration) -> topcoat::Result<()> {
let cutoff = jiff::Timestamp::now()
.checked_sub(jiff::SignedDuration::try_from(age).map_err(topcoat::Error::from)?)
.map_err(topcoat::Error::from)?;
let fields = crate::AuditEntry::fields();
crate::AuditEntry::filter(
fields
.panel()
.eq(self.prefix.clone())
.and(fields.created_at().lt(cutoff)),
)
.delete()
.exec(&mut self.db.clone())
.await
.map_err(|error| {
crate::error::driver_failure(error, "The audit log is unavailable right now.")
})
}
pub async fn document<'a>(
&'a self,
cx: &'a Cx,
title: impl Into<String>,
body: impl View + 'a,
) -> Result<BoxView<'a>> {
Panel::render_document(cx, self.panel.as_deref(), title.into(), body.boxed()).await
}
}
pub(super) struct EnteredPanel(Arc<Mounts>);
pub(super) fn request_mounts(cx: &Cx) -> Option<&Mounts> {
match current(cx) {
Some(panel) => Some(&*panel.mounts),
None => {
topcoat::context::try_context::<EnteredPanel>(cx).map(|EnteredPanel(mounts)| &**mounts)
}
}
}
struct HeldCatalog(Arc<Catalog>);
fn held_catalog(cx: &Cx) -> Option<&Catalog> {
topcoat::context::try_context::<HeldCatalog>(cx).map(|HeldCatalog(catalog)| &**catalog)
}
impl Panel {
pub fn handle(self, db: &Db) -> Result<PanelHandle> {
let Panel {
prefix,
registrations,
configuration_errors,
uploads,
auth,
limits,
locales,
translations,
audit_log,
..
} = self;
let mut registry = Registry::new(prefix.clone(), Some(AppSchema::of_db(db)));
let (mounts, mut errors) =
registry.register_all(registrations, audit_log, configuration_errors);
let serves_tenants = serves_tenants(&auth, &mounts);
registry.check_all(
db,
&mounts,
serves_tenants,
uploads.is_some(),
&limits,
&mut errors,
);
let catalog = build_catalog(&locales, &translations, &mut errors);
if !errors.is_empty() {
return Err(MountError::new(&prefix, errors).into());
}
Ok(PanelHandle {
db: db.clone(),
mounts,
panel: None,
catalog: Arc::new(catalog.expect("a catalog that failed to build fails the handle")),
prefix,
bus: NotificationBus::new(),
})
}
}