doido_generators/commands/
db.rs1use clap::Subcommand;
14use sea_orm_cli::{
15 handle_error, run_generate_command, run_migrate_command, Commands, GenerateSubcommands,
16};
17
18#[derive(Subcommand)]
20#[allow(clippy::large_enum_variant)]
23pub enum DbCommand {
24 Create,
26 Reset,
28 Prepare,
30 Seed,
32 Schema {
34 #[command(subcommand)]
35 action: SchemaCommand,
36 },
37 #[command(flatten)]
39 SeaOrm(Commands),
40}
41
42#[derive(Subcommand)]
44pub enum SchemaCommand {
45 Dump,
47 Load,
49}
50
51const DEFAULT_MIGRATION_DIR: &str = "db/migration";
53const DEFAULT_ENTITY_OUTPUT_DIR: &str = "app/models/_entities";
55const SCHEMA_FILE: &str = "db/schema.sql";
57const SEEDS_FILE: &str = "db/seeds.sql";
59
60const SEA_ORM_CLI_DEFAULT_MIGRATION_DIR: &str = "./migration";
62const SEA_ORM_CLI_DEFAULT_OUTPUT_DIR: &str = "./";
63
64pub fn ensure_database_url_from_config() {
73 if std::env::var_os("DATABASE_URL").is_some() {
74 return;
75 }
76 if let Ok(config) = doido_model::config::YamlConfig::load() {
80 std::env::set_var("DATABASE_URL", config.database.url);
81 }
82}
83
84pub async fn run(command: DbCommand, verbose: bool) {
86 match command {
87 DbCommand::Create => create().await,
88 DbCommand::Reset => reset().await,
89 DbCommand::Prepare => prepare().await,
90 DbCommand::Seed => seed().await,
91 DbCommand::Schema { action } => schema(action).await,
92 DbCommand::SeaOrm(command) => run_sea_orm(command, verbose).await,
93 }
94}
95
96async fn connect() -> doido_model::DatabaseConnection {
98 let url = database_url();
99 match doido_model::connect_with_url(&url).await {
100 Ok(conn) => conn,
101 Err(e) => {
102 doido_core::tracing::error!("failed to connect to {url}: {e}");
103 std::process::exit(1);
104 }
105 }
106}
107
108fn read_sql_file(path: &str) -> Option<String> {
110 match std::fs::read_to_string(path) {
111 Ok(contents) => Some(contents),
112 Err(e) => {
113 doido_core::tracing::error!("could not read {path}: {e}");
114 None
115 }
116 }
117}
118
119async fn reset() {
121 let Some(schema) = read_sql_file(SCHEMA_FILE) else {
122 return;
123 };
124 let conn = connect().await;
125 match doido_model::tasks::reset(&conn, &schema).await {
126 Ok(()) => doido_core::tracing::info!("reset database from {SCHEMA_FILE}"),
127 Err(e) => doido_core::tracing::error!("db reset failed: {e}"),
128 }
129}
130
131async fn prepare() {
133 let Some(schema) = read_sql_file(SCHEMA_FILE) else {
134 return;
135 };
136 let conn = connect().await;
137 match doido_model::tasks::prepare(&conn, &schema).await {
138 Ok(()) => doido_core::tracing::info!("prepared database from {SCHEMA_FILE}"),
139 Err(e) => doido_core::tracing::error!("db prepare failed: {e}"),
140 }
141}
142
143async fn seed() {
146 let Some(sql) = read_sql_file(SEEDS_FILE) else {
147 return;
148 };
149 let conn = connect().await;
150 match doido_model::schema::load(&conn, &sql).await {
151 Ok(()) => doido_core::tracing::info!("seeded database from {SEEDS_FILE}"),
152 Err(e) => doido_core::tracing::error!("db seed failed: {e}"),
153 }
154}
155
156async fn schema(action: SchemaCommand) {
158 let conn = connect().await;
159 match action {
160 SchemaCommand::Dump => match doido_model::schema::dump(&conn).await {
161 Ok(sql) => {
162 if let Some(parent) = std::path::Path::new(SCHEMA_FILE).parent() {
163 let _ = std::fs::create_dir_all(parent);
164 }
165 match std::fs::write(SCHEMA_FILE, sql) {
166 Ok(()) => doido_core::tracing::info!("wrote schema to {SCHEMA_FILE}"),
167 Err(e) => doido_core::tracing::error!("could not write {SCHEMA_FILE}: {e}"),
168 }
169 }
170 Err(e) => doido_core::tracing::error!("schema dump failed: {e}"),
171 },
172 SchemaCommand::Load => {
173 let Some(sql) = read_sql_file(SCHEMA_FILE) else {
174 return;
175 };
176 match doido_model::schema::load(&conn, &sql).await {
177 Ok(()) => doido_core::tracing::info!("loaded schema from {SCHEMA_FILE}"),
178 Err(e) => doido_core::tracing::error!("schema load failed: {e}"),
179 }
180 }
181 }
182}
183
184async fn create() {
186 let url = database_url();
187 match doido_model::create_database(&url).await {
188 Ok(()) => doido_core::tracing::info!("created database: {url}"),
189 Err(e) if e.to_string().contains("already exists") => {
190 doido_core::tracing::info!("database already exists: {url}");
191 }
192 Err(e) => handle_error(e),
193 }
194}
195
196fn database_url() -> String {
199 if let Ok(url) = std::env::var("DATABASE_URL") {
200 return url;
201 }
202 if let Ok(config) = doido_model::config::YamlConfig::load() {
203 return config.database.url;
204 }
205 doido_core::tracing::error!("DATABASE_URL is not set and config/<env>.yml could not be read");
206 std::process::exit(1);
207}
208
209async fn run_sea_orm(command: Commands, verbose: bool) {
211 match command {
212 Commands::Generate { mut command } => {
213 apply_entity_output_default(&mut command);
214 run_generate_command(command, verbose)
215 .await
216 .unwrap_or_else(handle_error);
217 }
218 Commands::Migrate {
219 migration_dir,
220 database_schema,
221 database_url,
222 command,
223 } => {
224 let migration_dir = override_migration_dir(migration_dir);
225 run_migrate_command(
226 command,
227 &migration_dir,
228 database_schema,
229 database_url,
230 verbose,
231 )
232 .unwrap_or_else(handle_error);
233 }
234 }
235}
236
237fn override_migration_dir(migration_dir: String) -> String {
239 if migration_dir == SEA_ORM_CLI_DEFAULT_MIGRATION_DIR {
240 DEFAULT_MIGRATION_DIR.to_string()
241 } else {
242 migration_dir
243 }
244}
245
246fn apply_entity_output_default(command: &mut GenerateSubcommands) {
248 let GenerateSubcommands::Entity { output_dir, .. } = command;
249 if output_dir == SEA_ORM_CLI_DEFAULT_OUTPUT_DIR {
250 *output_dir = DEFAULT_ENTITY_OUTPUT_DIR.to_string();
251 }
252}