diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e36ad3bc..8faa16d9 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -47,8 +47,7 @@ npm start New migrations can be created from the `src-tauri/` directory: ```shell -cd src-tauri -sqlx migrate add migration-name +npm run migration ``` Run the app to apply the migrations. diff --git a/package.json b/package.json index ba6c213b..5a64405c 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "start": "npm run app-dev", "app-build": "tauri build", "app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json", + "migration": "node scripts/create-migration.cjs", "build": "npm run --workspaces --if-present build", "build-plugins": "npm run --workspaces --if-present build", "bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap", diff --git a/scripts/create-migration.cjs b/scripts/create-migration.cjs new file mode 100644 index 00000000..91145c34 --- /dev/null +++ b/scripts/create-migration.cjs @@ -0,0 +1,46 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const slugify = require('slugify'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function generateTimestamp() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + return `${year}${month}${day}${hours}${minutes}${seconds}`; +} + +async function createMigration() { + try { + const migrationName = await new Promise((resolve) => { + rl.question('Enter migration name: ', resolve); + }); + + const timestamp = generateTimestamp(); + const fileName = `${timestamp}_${slugify(String(migrationName), { lower: true })}.sql`; + const migrationsDir = path.join(__dirname, '../src-tauri/migrations'); + const filePath = path.join(migrationsDir, fileName); + + if (!fs.existsSync(migrationsDir)) { + fs.mkdirSync(migrationsDir, { recursive: true }); + } + + fs.writeFileSync(filePath, '-- Add migration SQL here\n'); + console.log(`Created migration file: ${fileName}`); + } catch (error) { + console.error('Error creating migration:', error); + } finally { + rl.close(); + } +} + +createMigration().catch(console.error); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d6511ad0..eba57380 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -91,12 +91,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -393,15 +387,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -494,12 +479,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bitflags" version = "1.3.2" @@ -973,12 +952,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "convert_case" version = "0.4.0" @@ -1096,21 +1069,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.4.2" @@ -1129,15 +1087,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1262,17 +1211,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.3.11" @@ -1336,7 +1274,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -1411,12 +1348,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "downcast-rs" version = "1.2.1" @@ -1464,9 +1395,6 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -dependencies = [ - "serde", -] [[package]] name = "embed-resource" @@ -1566,17 +1494,6 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -1682,17 +1599,6 @@ dependencies = [ "miniz_oxide 0.8.0", ] -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1782,7 +1688,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -1802,17 +1707,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -2248,7 +2142,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", - "allocator-api2", ] [[package]] @@ -2317,15 +2210,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "html5ever" version = "0.26.0" @@ -2834,9 +2718,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libappindicator" @@ -2911,12 +2792,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "libredox" version = "0.1.3" @@ -2925,7 +2800,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall", ] [[package]] @@ -3031,16 +2906,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "md5" version = "0.7.0" @@ -3307,23 +3172,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -3377,7 +3225,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3879,17 +3726,11 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "path-slash" version = "0.2.1" @@ -3902,15 +3743,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -4104,27 +3936,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.30" @@ -4586,15 +4397,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.3" @@ -4786,26 +4588,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rsa" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rusqlite" version = "0.32.1" @@ -5331,9 +5113,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -5365,16 +5147,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -5407,9 +5179,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -5438,7 +5207,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", "raw-window-handle", - "redox_syscall 0.5.3", + "redox_syscall", "wasm-bindgen", "web-sys", "windows-sys 0.52.0", @@ -5470,229 +5239,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlformat" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" -dependencies = [ - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" -dependencies = [ - "atoi", - "byteorder", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.14.5", - "hashlink", - "hex", - "indexmap 2.3.0", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlformat", - "thiserror 1.0.63", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.87", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-sqlite", - "syn 2.0.87", - "tempfile", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.9.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa 1.0.11", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 1.0.63", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.9.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa 1.0.11", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 1.0.63", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "tracing", - "url", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5731,17 +5277,6 @@ dependencies = [ "quote", ] -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - [[package]] name = "strsim" version = "0.11.1" @@ -6731,7 +6266,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6936,24 +6470,12 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-properties" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" - [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "universal-hash" version = "0.5.1" @@ -7113,12 +6635,6 @@ dependencies = [ "wit-bindgen-rt", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -7403,16 +6919,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "whoami" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" -dependencies = [ - "redox_syscall 0.4.1", - "wasite", -] - [[package]] name = "winapi" version = "0.3.9" @@ -8199,6 +7705,7 @@ name = "yaak-models" version = "0.1.0" dependencies = [ "chrono", + "hex", "log", "nanoid", "r2d2", @@ -8208,7 +7715,7 @@ dependencies = [ "sea-query-rusqlite", "serde", "serde_json", - "sqlx", + "sha2", "tauri", "tauri-plugin", "thiserror 2.0.12", diff --git a/src-tauri/migrations/20250424152740_remove-fks.sql b/src-tauri/migrations/20250424152740_remove-fks.sql index e84cb194..3d383a0b 100644 --- a/src-tauri/migrations/20250424152740_remove-fks.sql +++ b/src-tauri/migrations/20250424152740_remove-fks.sql @@ -164,8 +164,6 @@ DROP TABLE websocket_requests; ALTER TABLE websocket_requests_dg_tmp RENAME TO websocket_requests; -PRAGMA foreign_keys = ON; - --------------------------- -- Remove environment FK -- --------------------------- diff --git a/src-tauri/yaak-models/Cargo.toml b/src-tauri/yaak-models/Cargo.toml index d1a63a26..ed0ad1d6 100644 --- a/src-tauri/yaak-models/Cargo.toml +++ b/src-tauri/yaak-models/Cargo.toml @@ -16,11 +16,12 @@ sea-query = { version = "0.32.1", features = ["with-chrono", "attr"] } sea-query-rusqlite = { version = "0.7.0", features = ["with-chrono"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -sqlx = { version = "0.8.0", default-features = false, features = ["migrate", "sqlite", "runtime-tokio-rustls"] } +sha2 = "0.10.9" tauri = { 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"] } diff --git a/src-tauri/yaak-models/src/lib.rs b/src-tauri/yaak-models/src/lib.rs index 5db752d2..e1569879 100644 --- a/src-tauri/yaak-models/src/lib.rs +++ b/src-tauri/yaak-models/src/lib.rs @@ -1,25 +1,20 @@ use crate::commands::*; use crate::query_manager::QueryManager; use crate::util::ModelChangeEvent; -use log::info; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; -use sqlx::SqlitePool; -use sqlx::migrate::Migrator; -use sqlx::sqlite::SqliteConnectOptions; use std::fs::create_dir_all; -use std::path::PathBuf; -use std::str::FromStr; use std::time::Duration; use tauri::async_runtime::Mutex; -use tauri::path::BaseDirectory; use tauri::plugin::TauriPlugin; -use tauri::{AppHandle, Emitter, Manager, Runtime, generate_handler}; +use tauri::{generate_handler, Emitter, Manager, Runtime}; use tokio::sync::mpsc; +use crate::migrate::must_migrate_db; mod commands; mod connection_or_tx; +mod migrate; pub mod db_context; pub mod error; pub mod models; @@ -55,13 +50,6 @@ pub fn init() -> TauriPlugin { let db_file_path = app_path.join("db.sqlite"); - { - let db_file_path = db_file_path.clone(); - tauri::async_runtime::block_on(async move { - must_migrate_db(app_handle.app_handle(), &db_file_path).await; - }); - }; - let manager = SqliteConnectionManager::file(db_file_path); let pool = Pool::builder() .max_size(100) // Up from 10 (just in case) @@ -69,6 +57,8 @@ pub fn init() -> TauriPlugin { .build(manager) .unwrap(); + must_migrate_db(app_handle.app_handle(), &pool).expect("Failed to run migrations"); + app_handle.manage(SqliteConnection::new(pool.clone())); { @@ -90,21 +80,3 @@ pub fn init() -> TauriPlugin { }) .build() } - -async fn must_migrate_db(app_handle: &AppHandle, sqlite_file_path: &PathBuf) { - info!("Connecting to database at {sqlite_file_path:?}"); - let sqlite_file_path = sqlite_file_path.to_str().unwrap().to_string(); - let opts = SqliteConnectOptions::from_str(&sqlite_file_path).unwrap().create_if_missing(true); - let pool = SqlitePool::connect_with(opts).await.expect("Failed to connect to database"); - let p = app_handle - .path() - .resolve("migrations", BaseDirectory::Resource) - .expect("failed to resolve resource"); - - info!("Running database migrations from: {}", p.to_string_lossy()); - let mut m = Migrator::new(p).await.expect("Failed to load migrations"); - m.set_ignore_missing(true); // So we can roll back versions and not crash - m.run(&pool).await.expect("Failed to run migrations"); - - info!("Database migrations complete"); -} diff --git a/src-tauri/yaak-models/src/migrate.rs b/src-tauri/yaak-models/src/migrate.rs new file mode 100644 index 00000000..cba14b65 --- /dev/null +++ b/src-tauri/yaak-models/src/migrate.rs @@ -0,0 +1,124 @@ +use crate::error::Result; +use log::{error, info}; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use rusqlite::{OptionalExtension, TransactionBehavior, params}; +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 must_migrate_db( + app_handle: &AppHandle, + pool: &Pool, +) -> Result<()> { + let migrations_dir = app_handle + .path() + .resolve("migrations", BaseDirectory::Resource) + .expect("failed to resolve resource"); + + info!("Running database migrations from: {:?}", migrations_dir); + + // Ensure the table exists + // NOTE: Yaak used to use sqlx for migrations, so we need to mirror that table structure. We + // are writing checksum but not verifying because we want to be able to change migrations after + // a release in case something breaks. + pool.get()?.execute( + "CREATE TABLE IF NOT EXISTS _sqlx_migrations ( + version BIGINT PRIMARY KEY, + description TEXT NOT NULL, + installed_on TIMESTAMP default CURRENT_TIMESTAMP NOT NULL, + success BOOLEAN NOT NULL, + checksum BLOB NOT NULL, + execution_time BIGINT NOT NULL + )", + [], + )?; + + // Read and sort all .sql files + let mut entries = fs::read_dir(migrations_dir) + .expect("Failed to find migrations directory") + .filter_map(StdResult::ok) + .filter(|e| e.path().extension().map(|ext| ext == "sql").unwrap_or(false)) + .collect::>(); + + // Ensure they're in the correct order + entries.sort_by_key(|e| e.file_name()); + + // Run each migration in a transaction + for entry in entries { + let mut conn = pool.get()?; + let mut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?; + match run_migration(entry.path().as_path(), &mut tx) { + Ok(_) => tx.commit()?, + Err(e) => { + error!("Failed to apply migration {:?} {e:?}", entry.path().file_name()); + tx.rollback()?; + return Err(e); + } + }; + } + + info!("Finished running migrations"); + + Ok(()) +} + +fn run_migration(migration_path: &Path, tx: &mut rusqlite::Transaction) -> Result { + 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"); + + // Skip if already applied + let row: Option = tx + .query_row("SELECT 1 FROM _sqlx_migrations WHERE version = ?", [version.clone()], |r| { + r.get(0) + }) + .optional()?; + + if row.is_some() { + // Migration was already run + return Ok(false); + } + + let sql = fs::read_to_string(migration_path).expect("Failed to read migration file"); + info!("Applying migration {description}"); + + // Split on `;`? → optional depending on how your SQL is structured + tx.execute_batch(&sql)?; + + let execution_time = start.elapsed().as_nanos() as i64; + let checksum = sha384_hex_prefixed(sql.as_bytes()); + + // NOTE: The success column is never used. It's just there for sqlx compatibility. + tx.execute( + "INSERT INTO _sqlx_migrations (version, description, execution_time, checksum, success) VALUES (?, ?, ?, ?, ?)", + params![version, description, execution_time, checksum, true], + )?; + + Ok(true) +} + +fn split_migration_filename(filename: &str) -> Option<(String, String)> { + // Remove the .sql extension + let trimmed = filename.strip_suffix(".sql")?; + + // Split on the first underscore + let mut parts = trimmed.splitn(2, '_'); + let version = parts.next()?.to_string(); + let description = parts.next()?.to_string(); + + Some((version, description)) +} + +fn sha384_hex_prefixed(input: &[u8]) -> String { + let mut hasher = Sha384::new(); + hasher.update(input); + let result = hasher.finalize(); + + // Format as 0x... with uppercase hex + format!("0x{}", hex::encode_upper(result)) +}