@@ -188,8 +188,9 @@ async fn generate_crud(name: &str) -> Result<()> {
188188 ) ;
189189 println ! ( ) ;
190190
191+ let singular = singularize ( name) ;
191192 ensure_crud_dependencies ( ) . await ?;
192- ensure_db_module ( & table) . await ?;
193+ upsert_db_resource ( & table, & singular ) . await ?;
193194 generate_sqlx_model ( name, & type_name) . await ?;
194195 generate_sqlx_handler ( name, & type_name, & table) . await ?;
195196
@@ -204,7 +205,8 @@ async fn generate_crud(name: &str) -> Result<()> {
204205 " let pool = db::init_pool(\" sqlite:{}.db\" ).await?;" ,
205206 table
206207 ) ;
207- println ! ( " RustApi::auto().state(pool).run(\" 127.0.0.1:8080\" ).await?;" ) ;
208+ println ! ( " 3. Mount generated handlers (or use RustApi::auto()):" ) ;
209+ print_route_registration_hints ( name) ;
208210
209211 Ok ( ( ) )
210212}
@@ -256,56 +258,106 @@ async fn ensure_crud_dependencies() -> Result<()> {
256258 Ok ( ( ) )
257259}
258260
259- async fn ensure_db_module ( table : & str ) -> Result < ( ) > {
260- let db_path = Path :: new ( "src/db.rs" ) ;
261- if db_path. exists ( ) {
262- return Ok ( ( ) ) ;
263- }
264-
265- let singular = singularize ( table) ;
266- let content = format ! (
267- r#"//! Database bootstrap for generated CRUD resources.
261+ const DB_RS_HEADER : & str = r#"//! Database bootstrap for generated CRUD resources.
262+ //!
263+ //! Each `cargo rustapi generate crud <name>` call adds a table-specific module below.
264+ //! Default columns are the standard scaffold (`id`, `name`, `description`, timestamps).
268265
269266use sqlx::sqlite::SqlitePoolOptions;
270267use sqlx::SqlitePool;
271268
272- /// Table name used by generated CRUD handlers.
273- pub const TABLE: &str = "{table}";
274-
275- /// Singular resource label for error messages.
276- pub const SINGULAR: &str = "{singular}";
277-
278- const COLUMNS_DDL: &str = "
269+ /// Default CRUD column layout for generated resources (customize per model as needed).
270+ macro_rules! crud_columns {
271+ () => {
272+ "
279273 id INTEGER PRIMARY KEY AUTOINCREMENT,
280274 name TEXT NOT NULL,
281275 description TEXT,
282276 created_at TEXT NOT NULL,
283277 updated_at TEXT NOT NULL
284- ";
278+ "
279+ };
280+ }
281+
282+ "# ;
283+
284+ async fn upsert_db_resource ( table : & str , singular : & str ) -> Result < ( ) > {
285+ let db_path = Path :: new ( "src/db.rs" ) ;
286+ let table_mod = table_module_name ( table) ;
287+ let module_block = format ! (
288+ r#"pub mod {table_mod} {{
289+ pub const TABLE: &str = "{table}";
290+ pub const SINGULAR: &str = "{singular}";
291+ pub(crate) const COLUMNS_DDL: &str = crud_columns!();
292+ }}
285293
286- /// Open a SQLite pool and ensure the resource table exists.
294+ "# ,
295+ table_mod = table_mod,
296+ table = table,
297+ singular = singular,
298+ ) ;
299+ let ensure_line = format ! (
300+ " ensure_table(&pool, {table_mod}::TABLE, {table_mod}::COLUMNS_DDL).await?;\n " ,
301+ table_mod = table_mod,
302+ ) ;
303+
304+ if !db_path. exists ( ) {
305+ let content = format ! (
306+ "{header}{module_block}\
307+ async fn ensure_table(pool: &SqlitePool, table: &str, columns: &str) -> Result<(), sqlx::Error> {{
308+ let schema = format!(\" CREATE TABLE IF NOT EXISTS {{}} ({{}})\" , table, columns.trim());
309+ sqlx::query(&schema).execute(pool).await?;
310+ Ok(())
311+ }}
312+
313+ /// Open a SQLite pool and ensure all generated resource tables exist.
287314pub async fn init_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {{
288315 let pool = SqlitePoolOptions::new()
289316 .max_connections(5)
290317 .connect(database_url)
291318 .await?;
292- let schema = format!(
293- "CREATE TABLE IF NOT EXISTS {{}} ({{}})",
294- TABLE,
295- COLUMNS_DDL.trim()
296- );
297- sqlx::query(&schema).execute(&pool).await?;
298- Ok(pool)
319+ {ensure_lines} Ok(pool)
299320}}
300- "# ,
301- table = table,
302- singular = singular,
303- ) ;
321+ " ,
322+ header = DB_RS_HEADER ,
323+ module_block = module_block,
324+ ensure_lines = ensure_line,
325+ ) ;
326+ fs:: write ( db_path, content) . await ?;
327+ println ! (
328+ "{} Generated database module: {}" ,
329+ style( "✓" ) . green( ) ,
330+ db_path. display( )
331+ ) ;
332+ return Ok ( ( ) ) ;
333+ }
334+
335+ let mut content = fs:: read_to_string ( db_path) . await ?;
336+ let mod_marker = format ! ( "pub mod {table_mod} " ) ;
337+ if content. contains ( & mod_marker) {
338+ println ! (
339+ "{} db.rs already tracks table '{}'" ,
340+ style( "•" ) . yellow( ) ,
341+ table
342+ ) ;
343+ return Ok ( ( ) ) ;
344+ }
345+
346+ let insert_at = content
347+ . find ( "async fn ensure_table" )
348+ . ok_or_else ( || anyhow:: anyhow!( "src/db.rs is missing ensure_table helper" ) ) ?;
349+ content. insert_str ( insert_at, & module_block) ;
350+
351+ let ok_pool = content
352+ . rfind ( " Ok(pool)" )
353+ . ok_or_else ( || anyhow:: anyhow!( "src/db.rs is missing init_pool tail" ) ) ?;
354+ content. insert_str ( ok_pool, & ensure_line) ;
304355
305356 fs:: write ( db_path, content) . await ?;
306357 println ! (
307- "{} Generated database module: {}" ,
358+ "{} Updated database module for table '{}' : {}" ,
308359 style( "✓" ) . green( ) ,
360+ table,
309361 db_path. display( )
310362 ) ;
311363 Ok ( ( ) )
@@ -358,15 +410,16 @@ pub struct Update{type_name} {{
358410 Ok ( ( ) )
359411}
360412
361- async fn generate_sqlx_handler ( name : & str , type_name : & str , _table : & str ) -> Result < ( ) > {
413+ async fn generate_sqlx_handler ( name : & str , type_name : & str , table : & str ) -> Result < ( ) > {
362414 let handlers_dir = Path :: new ( "src/handlers" ) ;
363415 ensure_handlers_module ( handlers_dir, name) . await ?;
364416
365417 let singular = singularize ( name) ;
418+ let table_mod = table_module_name ( table) ;
366419 let handler_content = format ! (
367420 r#"//! {} handlers (SQLx SQLite)
368421
369- use crate::db::{{SINGULAR, TABLE}};
422+ use crate::db::{table_mod}::{ {SINGULAR, TABLE}};
370423use crate::models::{{Create{type_name}, Update{type_name}, {type_name}}};
371424use rustapi_rs::prelude::*;
372425use sqlx::SqlitePool;
@@ -580,26 +633,20 @@ async fn ensure_models_module(models_dir: &Path, name: &str) -> Result<()> {
580633
581634fn print_route_registration_hints ( name : & str ) {
582635 println ! ( ) ;
583- println ! ( "Don't forget to register the routes in main.rs:" ) ;
584- println ! (
585- " {}" ,
586- style( format!( ".mount(handlers::{}::list)" , name) ) . cyan( )
587- ) ;
588- println ! (
589- " {}" ,
590- style( format!( ".mount(handlers::{}::get)" , name) ) . cyan( )
591- ) ;
592- println ! (
593- " {}" ,
594- style( format!( ".mount(handlers::{}::create)" , name) ) . cyan( )
595- ) ;
636+ println ! ( "Register the generated handlers in main.rs:" ) ;
596637 println ! (
597638 " {}" ,
598- style( format!( ".mount(handlers::{}::update)" , name) ) . cyan( )
639+ style( format!(
640+ ".route(\" /{name}\" , get(handlers::{name}::list).post(handlers::{name}::create))"
641+ ) )
642+ . cyan( )
599643 ) ;
600644 println ! (
601645 " {}" ,
602- style( format!( ".mount(handlers::{}::delete)" , name) ) . cyan( )
646+ style( format!(
647+ ".route(\" /{name}/{{id}}\" , get(handlers::{name}::get).put(handlers::{name}::update).delete(handlers::{name}::delete))"
648+ ) )
649+ . cyan( )
603650 ) ;
604651}
605652
@@ -616,6 +663,10 @@ fn to_pascal_case(s: &str) -> String {
616663 s. split ( & [ '-' , '_' ] [ ..] ) . map ( capitalize) . collect ( )
617664}
618665
666+ fn table_module_name ( table : & str ) -> String {
667+ table. replace ( '-' , "_" )
668+ }
669+
619670fn singularize ( s : & str ) -> String {
620671 if let Some ( stripped) = s. strip_suffix ( "ies" ) {
621672 format ! ( "{}y" , stripped)
0 commit comments