kit_rs/
app.rs

1//! Application builder for Kit framework
2//!
3//! Provides a fluent builder API to configure and run a Kit application.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use kit::Application;
9//!
10//! #[tokio::main]
11//! async fn main() {
12//!     Application::new()
13//!         .config(config::register_all)
14//!         .bootstrap(bootstrap::register)
15//!         .routes(routes::register)
16//!         .migrations::<migrations::Migrator>()
17//!         .run()
18//!         .await;
19//! }
20//! ```
21
22use crate::{Config, Router, Server};
23use clap::{Parser, Subcommand};
24use sea_orm_migration::prelude::*;
25use std::env;
26use std::future::Future;
27use std::path::Path;
28use std::pin::Pin;
29
30/// CLI structure for Kit applications
31#[derive(Parser)]
32#[command(name = "app")]
33#[command(about = "Kit application server and utilities")]
34struct Cli {
35    #[command(subcommand)]
36    command: Option<Commands>,
37}
38
39#[derive(Subcommand)]
40enum Commands {
41    /// Run the web server (default command)
42    Serve {
43        /// Skip running migrations on startup
44        #[arg(long)]
45        no_migrate: bool,
46    },
47    /// Run pending database migrations
48    Migrate,
49    /// Show migration status
50    #[command(name = "migrate:status")]
51    MigrateStatus,
52    /// Rollback the last migration(s)
53    #[command(name = "migrate:rollback")]
54    MigrateRollback {
55        /// Number of migrations to rollback
56        #[arg(default_value = "1")]
57        steps: u32,
58    },
59    /// Drop all tables and re-run all migrations
60    #[command(name = "migrate:fresh")]
61    MigrateFresh,
62    /// Run the scheduler daemon (checks every minute)
63    #[command(name = "schedule:work")]
64    ScheduleWork,
65    /// Run all due scheduled tasks once
66    #[command(name = "schedule:run")]
67    ScheduleRun,
68    /// List all registered scheduled tasks
69    #[command(name = "schedule:list")]
70    ScheduleList,
71}
72
73/// Application builder for Kit framework
74///
75/// Use this to configure and run your Kit application with a fluent API.
76pub struct Application<M = NoMigrator>
77where
78    M: MigratorTrait,
79{
80    config_fn: Option<Box<dyn FnOnce()>>,
81    bootstrap_fn: Option<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
82    routes_fn: Option<Box<dyn FnOnce() -> Router + Send>>,
83    _migrator: std::marker::PhantomData<M>,
84}
85
86/// Placeholder type for when no migrator is configured
87pub struct NoMigrator;
88
89impl MigratorTrait for NoMigrator {
90    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
91        vec![]
92    }
93}
94
95impl Application<NoMigrator> {
96    /// Create a new application builder
97    pub fn new() -> Self {
98        Application {
99            config_fn: None,
100            bootstrap_fn: None,
101            routes_fn: None,
102            _migrator: std::marker::PhantomData,
103        }
104    }
105}
106
107impl Default for Application<NoMigrator> {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl<M> Application<M>
114where
115    M: MigratorTrait,
116{
117    /// Register a configuration function
118    ///
119    /// This function is called early during startup to register
120    /// application configuration.
121    ///
122    /// # Example
123    ///
124    /// ```rust,ignore
125    /// App::new()
126    ///     .config(config::register_all)
127    /// ```
128    pub fn config<F>(mut self, f: F) -> Self
129    where
130        F: FnOnce() + 'static,
131    {
132        self.config_fn = Some(Box::new(f));
133        self
134    }
135
136    /// Register a bootstrap function
137    ///
138    /// This async function is called to register services, middleware,
139    /// and other application components.
140    ///
141    /// # Example
142    ///
143    /// ```rust,ignore
144    /// App::new()
145    ///     .bootstrap(bootstrap::register)
146    /// ```
147    pub fn bootstrap<F, Fut>(mut self, f: F) -> Self
148    where
149        F: FnOnce() -> Fut + Send + 'static,
150        Fut: Future<Output = ()> + Send + 'static,
151    {
152        self.bootstrap_fn = Some(Box::new(move || Box::pin(f())));
153        self
154    }
155
156    /// Register a routes function
157    ///
158    /// This function returns the application's router configuration.
159    ///
160    /// # Example
161    ///
162    /// ```rust,ignore
163    /// App::new()
164    ///     .routes(routes::register)
165    /// ```
166    pub fn routes<F>(mut self, f: F) -> Self
167    where
168        F: FnOnce() -> Router + Send + 'static,
169    {
170        self.routes_fn = Some(Box::new(f));
171        self
172    }
173
174    /// Configure the migrator type for database migrations
175    ///
176    /// # Example
177    ///
178    /// ```rust,ignore
179    /// Application::new()
180    ///     .migrations::<migrations::Migrator>()
181    /// ```
182    pub fn migrations<NewM>(self) -> Application<NewM>
183    where
184        NewM: MigratorTrait,
185    {
186        Application {
187            config_fn: self.config_fn,
188            bootstrap_fn: self.bootstrap_fn,
189            routes_fn: self.routes_fn,
190            _migrator: std::marker::PhantomData,
191        }
192    }
193
194    /// Run the application
195    ///
196    /// This parses CLI arguments and executes the appropriate command:
197    /// - `serve` (default): Run the web server
198    /// - `migrate`: Run pending migrations
199    /// - `migrate:status`: Show migration status
200    /// - `migrate:rollback`: Rollback migrations
201    /// - `migrate:fresh`: Drop and re-run all migrations
202    /// - `schedule:*`: Scheduler commands
203    pub async fn run(self) {
204        let cli = Cli::parse();
205
206        // Initialize framework configuration (loads .env files)
207        Config::init(Path::new("."));
208
209        // Destructure self to avoid partial move issues
210        let Application {
211            config_fn,
212            bootstrap_fn,
213            routes_fn,
214            _migrator,
215        } = self;
216
217        // Run user's config registration
218        if let Some(config_fn) = config_fn {
219            config_fn();
220        }
221
222        match cli.command {
223            None | Some(Commands::Serve { no_migrate: false }) => {
224                // Default: run server with auto-migrate
225                Self::run_migrations_silent::<M>().await;
226                Self::run_server_internal(bootstrap_fn, routes_fn).await;
227            }
228            Some(Commands::Serve { no_migrate: true }) => {
229                // Run server without migrations
230                Self::run_server_internal(bootstrap_fn, routes_fn).await;
231            }
232            Some(Commands::Migrate) => {
233                Self::run_migrations::<M>().await;
234            }
235            Some(Commands::MigrateStatus) => {
236                Self::show_migration_status::<M>().await;
237            }
238            Some(Commands::MigrateRollback { steps }) => {
239                Self::rollback_migrations::<M>(steps).await;
240            }
241            Some(Commands::MigrateFresh) => {
242                Self::fresh_migrations::<M>().await;
243            }
244            Some(Commands::ScheduleWork) => {
245                Self::run_scheduler_daemon_internal(bootstrap_fn).await;
246            }
247            Some(Commands::ScheduleRun) => {
248                Self::run_scheduled_tasks_internal(bootstrap_fn).await;
249            }
250            Some(Commands::ScheduleList) => {
251                Self::list_scheduled_tasks().await;
252            }
253        }
254    }
255
256    async fn run_server_internal(
257        bootstrap_fn: Option<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
258        routes_fn: Option<Box<dyn FnOnce() -> Router + Send>>,
259    ) {
260        // Run bootstrap
261        if let Some(bootstrap_fn) = bootstrap_fn {
262            bootstrap_fn().await;
263        }
264
265        // Get router
266        let router = if let Some(routes_fn) = routes_fn {
267            routes_fn()
268        } else {
269            Router::new()
270        };
271
272        // Create server with configuration from environment
273        Server::from_config(router)
274            .run()
275            .await
276            .expect("Failed to start server");
277    }
278
279    async fn get_database_connection() -> sea_orm::DatabaseConnection {
280        let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
281
282        // For SQLite, ensure the database file can be created
283        let database_url = if database_url.starts_with("sqlite://") {
284            let path = database_url.trim_start_matches("sqlite://");
285            let path = path.trim_start_matches("./");
286
287            if let Some(parent) = Path::new(path).parent() {
288                if !parent.as_os_str().is_empty() {
289                    std::fs::create_dir_all(parent).ok();
290                }
291            }
292
293            if !Path::new(path).exists() {
294                std::fs::File::create(path).ok();
295            }
296
297            format!("sqlite:{}?mode=rwc", path)
298        } else {
299            database_url
300        };
301
302        sea_orm::Database::connect(&database_url)
303            .await
304            .expect("Failed to connect to database")
305    }
306
307    async fn run_migrations_silent<Migrator: MigratorTrait>() {
308        let db = Self::get_database_connection().await;
309        if let Err(e) = Migrator::up(&db, None).await {
310            eprintln!("Warning: Migration failed: {}", e);
311        }
312    }
313
314    async fn run_migrations<Migrator: MigratorTrait>() {
315        println!("Running migrations...");
316        let db = Self::get_database_connection().await;
317        Migrator::up(&db, None)
318            .await
319            .expect("Failed to run migrations");
320        println!("Migrations completed successfully!");
321    }
322
323    async fn show_migration_status<Migrator: MigratorTrait>() {
324        println!("Migration status:");
325        let db = Self::get_database_connection().await;
326        Migrator::status(&db)
327            .await
328            .expect("Failed to get migration status");
329    }
330
331    async fn rollback_migrations<Migrator: MigratorTrait>(steps: u32) {
332        println!("Rolling back {} migration(s)...", steps);
333        let db = Self::get_database_connection().await;
334        Migrator::down(&db, Some(steps))
335            .await
336            .expect("Failed to rollback migrations");
337        println!("Rollback completed successfully!");
338    }
339
340    async fn fresh_migrations<Migrator: MigratorTrait>() {
341        println!("WARNING: Dropping all tables and re-running migrations...");
342        let db = Self::get_database_connection().await;
343        Migrator::fresh(&db)
344            .await
345            .expect("Failed to refresh database");
346        println!("Database refreshed successfully!");
347    }
348
349    async fn run_scheduler_daemon_internal(
350        bootstrap_fn: Option<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
351    ) {
352        // Run bootstrap for scheduler context
353        if let Some(bootstrap_fn) = bootstrap_fn {
354            bootstrap_fn().await;
355        }
356
357        println!("==============================================");
358        println!("  Kit Scheduler Daemon");
359        println!("==============================================");
360        println!();
361        println!("  Note: Create tasks with `kit make:task <name>`");
362        println!("  Press Ctrl+C to stop");
363        println!();
364        println!("==============================================");
365
366        eprintln!("Scheduler daemon is not yet configured.");
367        eprintln!("Create a scheduled task with: kit make:task <name>");
368        eprintln!("Then register it in src/schedule.rs");
369    }
370
371    async fn run_scheduled_tasks_internal(
372        bootstrap_fn: Option<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
373    ) {
374        // Run bootstrap for scheduler context
375        if let Some(bootstrap_fn) = bootstrap_fn {
376            bootstrap_fn().await;
377        }
378
379        println!("Running scheduled tasks...");
380        eprintln!("Scheduler is not yet configured.");
381        eprintln!("Create a scheduled task with: kit make:task <name>");
382    }
383
384    async fn list_scheduled_tasks() {
385        println!("Registered scheduled tasks:");
386        println!();
387        eprintln!("No scheduled tasks registered.");
388        eprintln!("Create a scheduled task with: kit make:task <name>");
389    }
390}