doido_generators/generators/
storage_install.rs1use crate::generator::{GeneratedFile, Generator};
9use crate::generators::migration_support::{
10 register_migration, render_migration_file, MIGRATION_LIB_BASE, MIGRATION_SRC_DIR,
11};
12use chrono::Utc;
13use doido_core::Result;
14
15pub struct StorageInstallGenerator;
16
17const IMPORTS: &str = "use doido::model::migration::{add_index, create_table, drop_table};";
18
19const UP_BODY: &str = r#" create_table(manager, "storage_blobs", |t| {
20 t.string("key").not_null().unique_key();
21 t.string("filename").not_null();
22 t.string("content_type");
23 t.text("metadata");
24 t.string("service_name").not_null();
25 t.big_integer("byte_size").not_null();
26 t.string("checksum");
27 t.timestamp("created_at").not_null();
28 })
29 .await?;
30 create_table(manager, "storage_attachments", |t| {
31 t.string("name").not_null();
32 t.string("record_type").not_null();
33 t.string("record_id").not_null();
34 t.string("blob_key").not_null();
35 t.timestamp("created_at").not_null();
36 })
37 .await?;
38 create_table(manager, "storage_variant_records", |t| {
39 t.string("blob_key").not_null();
40 t.string("variation_digest").not_null();
41 })
42 .await?;
43 add_index(
44 manager,
45 "storage_attachments",
46 &["record_type", "record_id", "name"],
47 )
48 .await?;
49 add_index(
50 manager,
51 "storage_variant_records",
52 &["blob_key", "variation_digest"],
53 )
54 .await?;
55 Ok(())
56"#;
57
58const DOWN_BODY: &str = r#" drop_table(manager, "storage_variant_records").await?;
59 drop_table(manager, "storage_attachments").await?;
60 drop_table(manager, "storage_blobs").await
61"#;
62
63fn storage_section(active: &str) -> String {
66 format!(
67 "\nstorage:\n service: {active}\n services:\n local:\n type: disk\n root: storage\n test:\n type: memory\n"
68 )
69}
70
71fn config_file(path: &str, active: &str) -> Option<GeneratedFile> {
73 let existing = std::fs::read_to_string(path).ok()?;
74 if existing.contains("storage:") {
75 return None;
76 }
77 Some(GeneratedFile {
78 path: path.to_string(),
79 content: format!("{}{}", existing.trim_end(), storage_section(active)),
80 })
81}
82
83impl Generator for StorageInstallGenerator {
84 fn name(&self) -> &str {
85 "storage:install"
86 }
87
88 fn generate(&self, _args: &[&str]) -> Result<Vec<GeneratedFile>> {
89 let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
90 let migration_module = format!("m{timestamp}_create_storage_tables");
91 let migration = render_migration_file(&migration_module, IMPORTS, UP_BODY, DOWN_BODY);
92
93 let lib_path = format!("{MIGRATION_SRC_DIR}/lib.rs");
94 let existing =
95 std::fs::read_to_string(&lib_path).unwrap_or_else(|_| MIGRATION_LIB_BASE.to_string());
96 let lib = register_migration(&existing, &migration_module);
97
98 let mut files = vec![
99 GeneratedFile {
100 path: format!("{MIGRATION_SRC_DIR}/{migration_module}.rs"),
101 content: migration,
102 },
103 GeneratedFile {
104 path: lib_path,
105 content: lib,
106 },
107 ];
108
109 if let Some(f) = config_file("config/development.yml", "local") {
111 files.push(f);
112 }
113 if let Some(f) = config_file("config/test.yml", "test") {
114 files.push(f);
115 }
116
117 Ok(files)
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn emits_migration_and_registers_it() {
127 let files = StorageInstallGenerator.generate(&[]).unwrap();
128 let migration = files
130 .iter()
131 .find(|f| f.path.contains("create_storage_tables") && f.path.ends_with(".rs"))
132 .expect("migration file emitted");
133 assert!(migration.content.contains("storage_blobs"));
134 assert!(migration.content.contains("storage_attachments"));
135 assert!(migration.content.contains("storage_variant_records"));
136 assert!(migration
137 .content
138 .contains("impl MigrationName for Migration"));
139 assert!(!migration.content.contains("DeriveMigrationName"));
140 let module = migration
141 .path
142 .strip_prefix("db/migration/src/")
143 .unwrap()
144 .strip_suffix(".rs")
145 .unwrap();
146 assert!(migration.content.contains(&format!("\"{module}\"")));
147
148 let lib = files
149 .iter()
150 .find(|f| f.path.ends_with("lib.rs"))
151 .expect("lib.rs emitted");
152 assert!(lib.content.contains("create_storage_tables"));
153 }
154}