docbox_database/migrations/
mod.rs1use crate::{
2 DbExecutor, DbResult, DbTransaction,
3 models::{
4 tenant::Tenant,
5 tenant_migration::{CreateTenantMigration, TenantMigration},
6 },
7};
8use chrono::Utc;
9use std::ops::DerefMut;
10
11const TENANT_MIGRATIONS: &[(&str, &str)] = &[
12 (
13 "m1_create_users_table",
14 include_str!("./tenant/m1_create_users_table.sql"),
15 ),
16 (
17 "m2_create_document_box_table",
18 include_str!("./tenant/m2_create_document_box_table.sql"),
19 ),
20 (
21 "m3_create_folders_table",
22 include_str!("./tenant/m3_create_folders_table.sql"),
23 ),
24 (
25 "m4_create_files_table",
26 include_str!("./tenant/m4_create_files_table.sql"),
27 ),
28 (
29 "m5_create_generated_files_table",
30 include_str!("./tenant/m5_create_generated_files_table.sql"),
31 ),
32 (
33 "m6_create_links_table",
34 include_str!("./tenant/m6_create_links_table.sql"),
35 ),
36 (
37 "m7_create_edit_history_table",
38 include_str!("./tenant/m7_create_edit_history_table.sql"),
39 ),
40 (
41 "m8_create_tasks_table",
42 include_str!("./tenant/m8_create_tasks_table.sql"),
43 ),
44 (
45 "m9_create_presigned_upload_tasks_table",
46 include_str!("./tenant/m9_create_presigned_upload_tasks_table.sql"),
47 ),
48 (
49 "m10_add_pinned_column",
50 include_str!("./tenant/m10_add_pinned_column.sql"),
51 ),
52];
53
54pub async fn get_pending_tenant_migrations(
56 db: impl DbExecutor<'_>,
57 tenant: &Tenant,
58) -> DbResult<Vec<String>> {
59 let migrations = TenantMigration::find_by_tenant(db, tenant.id, &tenant.env).await?;
60
61 let pending = TENANT_MIGRATIONS
62 .iter()
63 .filter(|(migration_name, _migration)| {
64 !migrations
66 .iter()
67 .any(|migration| migration.name.eq(migration_name))
68 })
69 .map(|(migration_name, _migration)| migration_name.to_string())
70 .collect();
71
72 Ok(pending)
73}
74
75pub async fn apply_tenant_migrations(
80 root_t: &mut DbTransaction<'_>,
81 t: &mut DbTransaction<'_>,
82 tenant: &Tenant,
83 target_migration_name: Option<&str>,
84) -> DbResult<()> {
85 let migrations =
86 TenantMigration::find_by_tenant(root_t.deref_mut(), tenant.id, &tenant.env).await?;
87
88 for (migration_name, migration) in TENANT_MIGRATIONS {
89 if target_migration_name
91 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
92 {
93 continue;
94 }
95
96 if migrations
98 .iter()
99 .any(|migration| migration.name.eq(migration_name))
100 {
101 continue;
102 }
103
104 apply_tenant_migration(t, migration_name, migration).await?;
106
107 TenantMigration::create(
109 root_t.deref_mut(),
110 CreateTenantMigration {
111 tenant_id: tenant.id,
112 env: tenant.env.clone(),
113 name: migration_name.to_string(),
114 applied_at: Utc::now(),
115 },
116 )
117 .await?;
118 }
119
120 Ok(())
121}
122
123pub async fn force_apply_tenant_migrations(
127 t: &mut DbTransaction<'_>,
128 target_migration_name: Option<&str>,
129) -> DbResult<()> {
130 for (migration_name, migration) in TENANT_MIGRATIONS {
131 if target_migration_name
133 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
134 {
135 continue;
136 }
137
138 apply_tenant_migration(t, migration_name, migration).await?;
139 }
140
141 Ok(())
142}
143
144pub async fn apply_tenant_migration(
146 db: &mut DbTransaction<'_>,
147 migration_name: &str,
148 migration: &str,
149) -> DbResult<()> {
150 let queries = migration
152 .split(';')
153 .map(|query| query.trim())
154 .filter(|query| !query.is_empty());
155
156 for query in queries {
157 let result = sqlx::query(query)
158 .execute(db.deref_mut())
159 .await
160 .inspect_err(|error| {
161 tracing::error!(?error, ?migration_name, "failed to perform migration")
162 })?;
163 let rows_affected = result.rows_affected();
164
165 tracing::debug!(?migration_name, ?rows_affected, "applied migration query");
166 }
167
168 Ok(())
169}