spawn_db/commands/
init.rs1use crate::commands::{Outcome, TelemetryDescribe, TelemetryInfo};
2use crate::config::ConfigLoaderSaver;
3use crate::engine::{CommandSpec, DatabaseConfig, EngineType};
4use anyhow::{anyhow, Result};
5use opendal::Operator;
6use std::collections::HashMap;
7use uuid::Uuid;
8
9pub struct Init {
12 pub config_file: String,
13 pub docker: Option<Option<String>>,
16}
17
18impl TelemetryDescribe for Init {
19 fn telemetry(&self) -> TelemetryInfo {
20 TelemetryInfo::new("init")
21 }
22}
23
24impl Init {
25 pub async fn execute(&self, base_op: &Operator) -> Result<(Outcome, String)> {
28 if base_op.exists(&self.config_file).await? {
30 return Err(anyhow!(
31 "Config file '{}' already exists. Use a different path or remove the existing file.",
32 &self.config_file
33 ));
34 }
35
36 let project_id = Uuid::new_v4().to_string();
38
39 let db_name = match &self.docker {
41 Some(Some(name)) => name.clone(),
42 Some(None) => "postgres".to_string(),
43 None => "postgres".to_string(),
44 };
45
46 let container_name = if self.docker.is_some() {
48 format!("{}-db", db_name)
49 } else {
50 "postgres-db".to_string()
51 };
52
53 let mut databases = HashMap::new();
55 databases.insert(
56 "postgres_psql".to_string(),
57 DatabaseConfig {
58 engine: EngineType::PostgresPSQL,
59 spawn_database: db_name.clone(),
60 spawn_schema: "_spawn".to_string(),
61 environment: "dev".to_string(),
62 command: Some(CommandSpec::Direct {
63 direct: vec![
64 "docker".to_string(),
65 "exec".to_string(),
66 "-i".to_string(),
67 container_name.clone(),
68 "psql".to_string(),
69 "-U".to_string(),
70 "postgres".to_string(),
71 db_name.clone(),
72 ],
73 }),
74 },
75 );
76
77 let config = ConfigLoaderSaver {
79 spawn_folder: "spawn".to_string(),
80 database: Some("postgres_psql".to_string()),
81 environment: None,
82 databases: Some(databases),
83 project_id: Some(project_id.clone()),
84 telemetry: None,
85 };
86
87 config
89 .save(&self.config_file, base_op)
90 .await
91 .map_err(|e| e.context("Failed to write config file"))?;
92
93 let spawn_folder = &config.spawn_folder;
95 let subfolders = ["migrations", "components", "tests", "pinned"];
96 let mut created_folders = Vec::new();
97
98 for subfolder in &subfolders {
99 let path = format!("{}/{}/", spawn_folder, subfolder);
100 base_op
102 .write(&format!("{}.gitkeep", path), "")
103 .await
104 .map_err(|e| {
105 anyhow::Error::from(e).context(format!("Failed to create {} folder", subfolder))
106 })?;
107 created_folders.push(format!(" {}/{}/", spawn_folder, subfolder));
108 }
109
110 if self.docker.is_some() {
112 let docker_compose_content = format!(
113 r#"services:
114 postgres:
115 image: postgres:17
116 container_name: {}
117 ports:
118 - "5432:5432"
119 restart: always
120 environment:
121 POSTGRES_USER: postgres
122 POSTGRES_PASSWORD: postgres
123 POSTGRES_DB: {}
124"#,
125 container_name, db_name
126 );
127
128 base_op
129 .write("docker-compose.yaml", docker_compose_content)
130 .await
131 .map_err(|e| {
132 anyhow::Error::from(e).context("Failed to create docker-compose.yaml")
133 })?;
134
135 println!("Created docker-compose.yaml for database '{}'", db_name);
136 println!("Start the database with: docker compose up -d");
137 println!();
138 }
139
140 crate::show_telemetry_notice();
142
143 println!("Initialized spawn project with project_id: {}", project_id);
144 println!("Created directories:");
145 for folder in &created_folders {
146 println!("{}", folder);
147 }
148 println!(
149 "\nEdit {} to configure your database connection.",
150 &self.config_file
151 );
152
153 Ok((Outcome::Success, project_id))
154 }
155}