mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 22:40:26 +01:00
Embed migrations into Rust binary
This commit is contained in:
@@ -27,7 +27,7 @@ async function createMigration() {
|
||||
|
||||
const timestamp = generateTimestamp();
|
||||
const fileName = `${timestamp}_${slugify(String(migrationName), { lower: true })}.sql`;
|
||||
const migrationsDir = path.join(__dirname, '../src-tauri/migrations');
|
||||
const migrationsDir = path.join(__dirname, '../src-tauri/yaak-models/migrations');
|
||||
const filePath = path.join(migrationsDir, fileName);
|
||||
|
||||
if (!fs.existsSync(migrationsDir)) {
|
||||
|
||||
20
src-tauri/Cargo.lock
generated
20
src-tauri/Cargo.lock
generated
@@ -2475,6 +2475,25 @@ dependencies = [
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||
dependencies = [
|
||||
"include_dir_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir_macros"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -7654,6 +7673,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"include_dir",
|
||||
"log",
|
||||
"nanoid",
|
||||
"r2d2",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
],
|
||||
"longDescription": "A cross-platform desktop app for interacting with REST, GraphQL, and gRPC",
|
||||
"resources": [
|
||||
"migrations",
|
||||
"vendored/protoc/include",
|
||||
"vendored/plugins",
|
||||
"vendored/plugin-runtime"
|
||||
|
||||
@@ -7,6 +7,8 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
hex = "0.4.3"
|
||||
include_dir = "0.7"
|
||||
log = "0.4.22"
|
||||
nanoid = "0.4.0"
|
||||
r2d2 = "0.8.10"
|
||||
@@ -22,7 +24,6 @@ tauri-plugin-dialog = { workspace = true }
|
||||
thiserror = "2.0.11"
|
||||
tokio = { workspace = true }
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
hex = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.build(manager)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = migrate_db(app_handle.app_handle(), &pool) {
|
||||
if let Err(e) = migrate_db(&pool) {
|
||||
error!("Failed to run database migration {e:?}");
|
||||
app_handle
|
||||
.dialog()
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
use crate::error::Error::MigrationError;
|
||||
use crate::error::Result;
|
||||
use log::info;
|
||||
use include_dir::{include_dir, Dir, DirEntry};
|
||||
use log::{debug, info};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::{OptionalExtension, TransactionBehavior, params};
|
||||
use rusqlite::{params, OptionalExtension, TransactionBehavior};
|
||||
use sha2::{Digest, Sha384};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::result::Result as StdResult;
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
|
||||
pub(crate) fn migrate_db<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
pool: &Pool<SqliteConnectionManager>,
|
||||
) -> Result<()> {
|
||||
let migrations_dir = app_handle
|
||||
.path()
|
||||
.resolve("migrations", BaseDirectory::Resource)
|
||||
.expect("failed to resolve resource");
|
||||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
|
||||
|
||||
info!("Running database migrations from: {:?}", migrations_dir);
|
||||
pub(crate) fn migrate_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
info!("Running database migrations");
|
||||
|
||||
// Ensure the table exists
|
||||
// NOTE: Yaak used to use sqlx for migrations, so we need to mirror that table structure. We
|
||||
@@ -39,20 +29,22 @@ pub(crate) fn migrate_db<R: Runtime>(
|
||||
)?;
|
||||
|
||||
// Read and sort all .sql files
|
||||
let mut entries = fs::read_dir(migrations_dir)
|
||||
.expect("Failed to find migrations directory")
|
||||
.filter_map(StdResult::ok)
|
||||
let mut entries = MIGRATIONS_DIR
|
||||
.entries()
|
||||
.into_iter()
|
||||
.filter(|e| e.path().extension().map(|ext| ext == "sql").unwrap_or(false))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Ensure they're in the correct order
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
entries.sort_by_key(|e| e.path());
|
||||
|
||||
// Run each migration in a transaction
|
||||
let mut num_migrations = 0;
|
||||
for entry in entries {
|
||||
num_migrations += 1;
|
||||
let mut conn = pool.get()?;
|
||||
let mut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
match run_migration(entry.path().as_path(), &mut tx) {
|
||||
match run_migration(entry, &mut tx) {
|
||||
Ok(_) => tx.commit()?,
|
||||
Err(e) => {
|
||||
let msg = format!(
|
||||
@@ -66,16 +58,15 @@ pub(crate) fn migrate_db<R: Runtime>(
|
||||
};
|
||||
}
|
||||
|
||||
info!("Finished running migrations");
|
||||
info!("Finished running {} migrations", num_migrations);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_migration(migration_path: &Path, tx: &mut rusqlite::Transaction) -> Result<bool> {
|
||||
fn run_migration(migration_path: &DirEntry, tx: &mut rusqlite::Transaction) -> Result<bool> {
|
||||
let start = std::time::Instant::now();
|
||||
let (version, description) =
|
||||
split_migration_filename(migration_path.file_name().unwrap().to_str().unwrap())
|
||||
.expect("Failed to parse migration filename");
|
||||
let (version, description) = split_migration_filename(migration_path.path().to_str().unwrap())
|
||||
.expect("Failed to parse migration filename");
|
||||
|
||||
// Skip if already applied
|
||||
let row: Option<i64> = tx
|
||||
@@ -85,11 +76,13 @@ fn run_migration(migration_path: &Path, tx: &mut rusqlite::Transaction) -> Resul
|
||||
.optional()?;
|
||||
|
||||
if row.is_some() {
|
||||
debug!("Skipping migration {description}");
|
||||
// Migration was already run
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let sql = fs::read_to_string(migration_path).expect("Failed to read migration file");
|
||||
let sql =
|
||||
migration_path.as_file().unwrap().contents_utf8().expect("Failed to read migration file");
|
||||
info!("Applying migration {description}");
|
||||
|
||||
// Split on `;`? → optional depending on how your SQL is structured
|
||||
|
||||
Reference in New Issue
Block a user