pub use oxipage_core::state::SiteScopedDb;
use crate::loader;
use oxipage_core::builder::BuildExt;
use oxipage_core::config::Config;
use oxipage_core::extension::WasmLoader;
use oxipage_core::registry::ExtensionRegistry;
use oxipage_core::sites::SitesFile;
use sqlx::SqlitePool;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct SiteContext {
pub slug: String,
pub path: PathBuf,
pub config: Arc<Config>,
pub db: SqlitePool,
pub registry: Arc<ExtensionRegistry>,
pub builders: Arc<Vec<Box<dyn BuildExt>>>,
pub wasm_loader: Option<Arc<dyn WasmLoader>>,
}
pub struct SiteRegistry {
sites: RwLock<HashMap<String, Arc<SiteContext>>>,
sites_file: RwLock<SitesFile>,
}
impl SiteRegistry {
pub async fn new(sites_file: SitesFile) -> anyhow::Result<Self> {
let mut map = HashMap::new();
for (slug, entry) in &sites_file.sites {
if !entry.path.exists() {
tracing::warn!(slug, path = %entry.path.display(), "site path missing; skipping");
continue;
}
match loader::SiteLoader::load(slug.clone(), entry.path.clone()).await {
Ok(ctx) => {
map.insert(slug.clone(), Arc::new(ctx));
}
Err(e) => {
tracing::warn!(slug, error = %e, "failed to load site; skipping");
}
}
}
Ok(Self {
sites: RwLock::new(map),
sites_file: RwLock::new(sites_file),
})
}
pub async fn db_for(&self, slug: &str) -> Option<SqlitePool> {
self.sites.read().await.get(slug).map(|c| c.db.clone())
}
pub async fn ctx_for(&self, slug: &str) -> Option<Arc<SiteContext>> {
self.sites.read().await.get(slug).cloned()
}
pub async fn default_slug(&self) -> Option<String> {
let sf = self.sites_file.read().await;
sf.default_site
.clone()
.or_else(|| sf.sites.keys().next().cloned())
}
pub fn iter_blocking(&self) -> Vec<(String, Arc<SiteContext>)> {
self.sites
.try_read()
.map(|guard| guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default()
}
pub fn slugs(&self) -> Vec<String> {
self.sites
.try_read()
.map(|guard| {
let mut slugs: Vec<_> = guard.keys().cloned().collect();
slugs.sort();
slugs
})
.unwrap_or_default()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.sites.try_read().map(|g| g.len()).unwrap_or(0)
}
}