create_rust_app/dev/
controller.rs

1use crate::Database;
2use anyhow::{bail, Result};
3use diesel::{
4    migration::{Migration, MigrationSource},
5    query_dsl::RunQueryDsl,
6    sql_query,
7    sql_types::Text,
8};
9use diesel_migrations::{FileBasedMigrations, MigrationHarness};
10use serde::{Deserialize, Serialize};
11
12use super::{CreateRustAppMigration, MigrationStatus};
13
14#[derive(Debug, Deserialize, QueryableByName)]
15pub struct MyQueryResult {
16    #[diesel(sql_type=Text)]
17    pub json: String,
18}
19
20#[derive(Serialize, Deserialize)]
21pub struct MySqlQuery {
22    pub query: String,
23}
24
25#[derive(Serialize, Deserialize)]
26pub struct HealthCheckResponse {
27    pub message: String,
28}
29
30/// /db/query
31///
32/// # Errors
33/// * query fails
34///
35/// # Panics
36/// * cannot connect to the database
37pub fn query_db(db: &Database, body: &MySqlQuery) -> Result<String, diesel::result::Error> {
38    let q = format!("SELECT json_agg(q) as json FROM ({}) q;", body.query);
39    let mut db = db.get_connection().unwrap();
40
41    Ok(sql_query(q.as_str())
42        .get_result::<MyQueryResult>(&mut db)?
43        .json)
44}
45
46/// /db/is-connected
47#[must_use]
48pub fn is_connected(db: &Database) -> bool {
49    let Ok(mut db) = db.pool.clone().get() else {
50        return false;
51    };
52    let is_connected = sql_query("SELECT 1;").execute(&mut db);
53    is_connected.is_err()
54}
55
56/// # Panics
57/// * cannot connect to the database
58/// * cannot find the migrations directory
59#[must_use]
60pub fn get_migrations(db: &Database) -> Vec<CreateRustAppMigration> {
61    // Vec<diesel::migration::Migration> {
62    let mut db = db.pool.clone().get().unwrap();
63
64    let source = FileBasedMigrations::find_migrations_directory().unwrap();
65
66    #[cfg(feature = "database_sqlite")]
67    let file_migrations =
68        MigrationSource::<crate::database::DieselBackend>::migrations(&source).unwrap();
69    #[cfg(feature = "database_postgres")]
70    let file_migrations =
71        MigrationSource::<crate::database::DieselBackend>::migrations(&source).unwrap();
72
73    let db_migrations = MigrationHarness::applied_migrations(&mut db).unwrap();
74    let pending_migrations = MigrationHarness::pending_migrations(&mut db, source).unwrap();
75
76    let mut all_migrations = vec![];
77
78    for fm in &file_migrations {
79        all_migrations.push(CreateRustAppMigration {
80            name: fm.name().to_string(),
81            version: fm.name().version().to_string(),
82            status: MigrationStatus::Unknown,
83        });
84    }
85
86    // update the status for any pending file_migrations
87    for pm in &pending_migrations {
88        if let Some(existing) = all_migrations.iter_mut().find(|m| {
89            m.version
90                .eq_ignore_ascii_case(&pm.name().version().to_string())
91        }) {
92            existing.status = MigrationStatus::Pending;
93        }
94    }
95
96    for dm in &db_migrations {
97        match all_migrations
98            .iter_mut()
99            .find(|m| m.version.eq_ignore_ascii_case(&dm.to_string()))
100        {
101            Some(existing) => {
102                existing.status = MigrationStatus::Applied;
103            }
104            None => all_migrations.push(CreateRustAppMigration {
105                name: format!("{dm}_?"),
106                version: dm.to_string(),
107                status: MigrationStatus::AppliedButMissingLocally,
108            }),
109        }
110    }
111
112    all_migrations
113}
114
115/// /db/needs-migration
116/// checks if a migration is needed
117///
118/// # Panics
119/// * cannot connect to the database
120/// * cannot find the migrations directory
121///
122/// TODO: return a Result instead of panicking
123#[must_use]
124pub fn needs_migration(db: &Database) -> bool {
125    let mut db = db.pool.clone().get().unwrap();
126
127    let source = FileBasedMigrations::find_migrations_directory().unwrap();
128    MigrationHarness::has_pending_migration(&mut db, source).unwrap()
129}
130
131/// /db/migrate
132/// performs any pending migrations
133///
134/// # Panics
135/// * cannot connect to the database
136/// * cannot find the migrations directory
137/// * cannot run the migrations
138///
139/// TODO: Propagate more of these errors instead of panicking
140pub fn migrate_db(db: &Database) -> Result<()> {
141    let mut db = db.pool.clone().get().unwrap();
142
143    let source = FileBasedMigrations::find_migrations_directory().unwrap();
144    let has_pending_migrations =
145        MigrationHarness::has_pending_migration(&mut db, source.clone()).unwrap();
146
147    if !has_pending_migrations {
148        return Ok(());
149    }
150
151    let op = MigrationHarness::run_pending_migrations(&mut db, source);
152    match op {
153        Ok(_) => Ok(()),
154        Err(err) => {
155            println!("{err:#?}");
156            bail!(err)
157        }
158    }
159}
160
161/// /health
162pub const fn health() {}