1use 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#[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 Serve {
43 #[arg(long)]
45 no_migrate: bool,
46 },
47 Migrate,
49 #[command(name = "migrate:status")]
51 MigrateStatus,
52 #[command(name = "migrate:rollback")]
54 MigrateRollback {
55 #[arg(default_value = "1")]
57 steps: u32,
58 },
59 #[command(name = "migrate:fresh")]
61 MigrateFresh,
62 #[command(name = "schedule:work")]
64 ScheduleWork,
65 #[command(name = "schedule:run")]
67 ScheduleRun,
68 #[command(name = "schedule:list")]
70 ScheduleList,
71}
72
73pub 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
86pub struct NoMigrator;
88
89impl MigratorTrait for NoMigrator {
90 fn migrations() -> Vec<Box<dyn MigrationTrait>> {
91 vec![]
92 }
93}
94
95impl Application<NoMigrator> {
96 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 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 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 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 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 pub async fn run(self) {
204 let cli = Cli::parse();
205
206 Config::init(Path::new("."));
208
209 let Application {
211 config_fn,
212 bootstrap_fn,
213 routes_fn,
214 _migrator,
215 } = self;
216
217 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 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 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 if let Some(bootstrap_fn) = bootstrap_fn {
262 bootstrap_fn().await;
263 }
264
265 let router = if let Some(routes_fn) = routes_fn {
267 routes_fn()
268 } else {
269 Router::new()
270 };
271
272 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 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 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 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}