use crate::framework::{
middleware::{
Middleware,
MiddlewareStack,
presets::{Cors, TrimStrings}
},
views::{Metadata, set_global_metadata},
database::{self},
inertia::InertiaConfig,
};
use std::sync::Arc;
use tokio::sync::RwLock;
use once_cell::sync::Lazy;
use crate::app::models;
use std::path::Path;
use std::fs;
use sqlx::sqlite::SqlitePool;
static APP: Lazy<Arc<RwLock<Application>>> = Lazy::new(|| {
Arc::new(RwLock::new(Application::new()))
});
#[derive(Clone)]
pub struct Application {
middleware_stack: MiddlewareStack,
middleware_groups: Vec<(String, Vec<Middleware>)>,
metadata: Option<Metadata>,
inertia_config: Option<InertiaConfig>,
}
impl Application {
pub fn new() -> Self {
Self {
middleware_stack: MiddlewareStack::new(),
middleware_groups: Vec::new(),
metadata: None,
inertia_config: None,
}
}
pub async fn instance() -> Arc<RwLock<Self>> {
Arc::clone(&APP)
}
pub async fn middleware<F>(&mut self, configure: F)
where
F: FnOnce(&mut MiddlewareStack),
{
configure(&mut self.middleware_stack);
}
pub async fn middleware_groups<F>(&mut self, configure: F)
where
F: FnOnce(&mut Vec<(String, Vec<Middleware>)>),
{
configure(&mut self.middleware_groups);
}
pub async fn metadata<F>(&mut self, configure: F)
where
F: FnOnce() -> Metadata,
{
self.metadata = Some(configure());
if let Some(metadata) = &self.metadata {
set_global_metadata(metadata.clone());
}
}
pub async fn inertia<F>(&mut self, configure: F)
where
F: FnOnce() -> InertiaConfig,
{
self.inertia_config = Some(configure());
}
pub fn middleware_stack(&self) -> MiddlewareStack {
self.middleware_stack.clone()
}
pub fn groups(&self) -> Vec<(String, Vec<Middleware>)> {
self.middleware_groups.clone()
}
pub fn inertia_config(&self) -> Option<InertiaConfig> {
self.inertia_config.clone()
}
}
pub async fn bootstrap() -> Result<Application, Box<dyn std::error::Error>> {
models::register_models();
println!("Initializing database...");
let db_config = database::config::DatabaseConfig::from_env();
println!("Initializing database at path: {}", db_config.database_path());
if let Some(parent) = Path::new(&db_config.database_path()).parent() {
println!("Creating database directory: {}", parent.display());
fs::create_dir_all(parent)?;
}
println!("Connecting to database with URL: {}", db_config.connection_url());
let pool = SqlitePool::connect(&db_config.connection_url()).await?;
println!("Successfully connected to database");
println!("Enabling foreign key constraints");
sqlx::query("PRAGMA foreign_keys = ON")
.execute(&pool)
.await?;
*database::POOL.lock().unwrap() = Some(Arc::new(pool));
println!("Database pool initialized successfully");
let app = Application::instance().await;
let mut app = app.write().await;
app.middleware(|stack| {
stack.add(Middleware::Cors(Cors::new("*")));
stack.add(Middleware::TrimStrings(TrimStrings::new()));
}).await;
app.middleware_groups(|groups| {
groups.push(("api".to_string(), vec![
Middleware::Cors(Cors::new("*")),
Middleware::TrimStrings(TrimStrings::new()),
]));
}).await;
app.metadata(|| {
Metadata::new("Ruskit")
.with_description("A modern web framework for Rust")
.with_keywords("rust, web, framework")
.with_author("Your Name")
.with_og_title("Ruskit")
.with_og_description("A modern web framework for Rust")
.with_og_image("https://example.com/og-image.jpg")
}).await;
let app_clone = app.clone();
Ok(app_clone)
}
pub async fn middleware_stack() -> MiddlewareStack {
let app = Application::instance().await;
let app = app.read().await;
app.middleware_stack()
}
pub async fn middleware_group(name: &str) -> Option<Vec<Middleware>> {
let app = Application::instance().await;
let app = app.read().await;
app.groups()
.iter()
.find(|(group_name, _)| group_name == name)
.map(|(_, middlewares)| middlewares.clone())
}