diff --git a/loco-gen/src/lib.rs b/loco-gen/src/lib.rs index 90ed56a93..ee95688ff 100644 --- a/loco-gen/src/lib.rs +++ b/loco-gen/src/lib.rs @@ -11,8 +11,11 @@ mod controller; mod model; #[cfg(feature = "with-db")] mod scaffold; +#[cfg(feature = "with-db")] +mod seeder; #[cfg(test)] mod testutil; + use std::{str::FromStr, sync::OnceLock}; const MAILER_T: &str = include_str!("templates/mailer/mailer.t"); @@ -171,6 +174,11 @@ pub enum Component { // k kind: ScaffoldKind, }, + #[cfg(feature = "with-db")] + Seeder { + /// Name of the thing to generate + name: String, + }, Controller { /// Name of the thing to generate name: String, @@ -246,6 +254,10 @@ pub fn generate(component: Component, appinfo: &AppInfo) -> Result<()> { json!({ "name": name, "ts": chrono::Utc::now(), "pkg_name": appinfo.app_name}); rrgen.generate(MIGRATION_T, &vars)?; } + #[cfg(feature = "with-db")] + Component::Seeder { name } => { + seeder::generate(&rrgen, name.as_str())?; + } Component::Controller { name, actions, diff --git a/loco-gen/src/seeder.rs b/loco-gen/src/seeder.rs new file mode 100644 index 000000000..0f0a44973 --- /dev/null +++ b/loco-gen/src/seeder.rs @@ -0,0 +1,12 @@ +use super::{collect_messages, Result}; +use rrgen::RRgen; +use serde_json::json; + +const DEFAULT_SEEDER_T: &str = include_str!("templates/seeder/default.t"); + +pub fn generate(rrgen: &RRgen, name: &str) -> Result { + let vars = json!({"name": name}); + let res = rrgen.generate(DEFAULT_SEEDER_T, &vars)?; + + Ok(collect_messages(vec![res])) +} diff --git a/loco-gen/src/templates/seeder/default.t b/loco-gen/src/templates/seeder/default.t new file mode 100644 index 000000000..a56f3d084 --- /dev/null +++ b/loco-gen/src/templates/seeder/default.t @@ -0,0 +1,15 @@ +{% set module_name = name | snake_case -%} +{% set struct_name = module_name | pascal_case -%} +use loco_rs::prelude::*; + +pub struct {{struct_name}}Seeder; + +impl Seeder for {{struct_name}}Seeder { + fn name(&self) -> String { + "{{struct_name}}Seeder".to_string() + } + + async fn seed(&self, conn: &DatabaseConnection) -> AppResult<()> { + todo!() + } +} diff --git a/src/db.rs b/src/db.rs index 7c8f279f4..8a52b3dde 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,8 +3,6 @@ //! This module defines functions and operations related to the application's //! database interactions. -use std::{collections::HashMap, fs::File, io::Write, path::Path, sync::OnceLock, time::Duration}; - use chrono::{DateTime, Utc}; use duct::cmd; use fs_err::{self as fs, create_dir_all}; @@ -14,6 +12,8 @@ use sea_orm::{ DatabaseConnection, DbConn, EntityTrait, IntoActiveModel, Statement, }; use sea_orm_migration::MigratorTrait; +use std::future::Future; +use std::{collections::HashMap, fs::File, io::Write, path::Path, sync::OnceLock, time::Duration}; use tracing::info; use super::Result as AppResult; @@ -35,6 +35,16 @@ fn get_extract_db_name() -> &'static Regex { EXTRACT_DB_NAME.get_or_init(|| Regex::new(r"/([^/]+)$").unwrap()) } +/// A trait for seeding the database with initial data. +/// Seeders should be kept in `src/seeders`. +pub trait Seeder: Send + Sync { + /// The unique name of the seeder. + fn name(&self) -> String; + + /// Seeds the database with initial data. + fn seed(&self, db: &DatabaseConnection) -> impl Future> + Send + Sync; +} + #[derive(Default, Clone, Debug)] pub struct MultiDb { pub db: HashMap, @@ -277,6 +287,15 @@ where Ok(()) } +/// Seed the database with the given Seeder. +/// +/// # Errors +/// +/// Returns an error if the seeder fails. +pub async fn seed_via_seeder(db: &DatabaseConnection, seeder: &impl Seeder) -> crate::Result<()> { + seeder.seed(db).await +} + /// Checks if the specified table has an 'id' column. /// /// This function checks if the specified table has an 'id' column, which is a