diff --git a/package.json b/package.json index a9e568f..7b23b3b 100644 --- a/package.json +++ b/package.json @@ -16,48 +16,49 @@ "eslint:fix": "eslint --fix" }, "dependencies": { - "@libsql/client": "^0.15.8", + "@libsql/client": "^0.15.9", "@nuxt/eslint": "1.4.1", - "@nuxt/icon": "^1.13.0", + "@nuxt/icon": "^1.14.0", "@nuxt/image": "1.10.0", "@nuxtjs/i18n": "^9.5.5", - "@pinia/nuxt": "^0.11.0", - "@tailwindcss/vite": "^4.1.8", + "@pinia/nuxt": "^0.11.1", + "@tailwindcss/vite": "^4.1.10", "@tauri-apps/api": "^2.5.0", "@tauri-apps/plugin-dialog": "^2.2.2", "@tauri-apps/plugin-fs": "^2.3.0", "@tauri-apps/plugin-http": "~2.4.4", - "@tauri-apps/plugin-notification": "~2.2.2", - "@tauri-apps/plugin-opener": "^2.2.7", - "@tauri-apps/plugin-os": "^2.2.1", - "@tauri-apps/plugin-sql": "~2.2.0", - "@tauri-apps/plugin-store": "^2.2.0", - "@vueuse/components": "^13.3.0", - "@vueuse/core": "^13.3.0", - "@vueuse/nuxt": "^13.3.0", - "drizzle-orm": "^0.43.1", - "eslint": "^9.28.0", + "@tauri-apps/plugin-notification": "~2.2.3", + "@tauri-apps/plugin-opener": "^2.3.0", + "@tauri-apps/plugin-os": "^2.2.2", + "@tauri-apps/plugin-sql": "~2.2.1", + "@tauri-apps/plugin-store": "^2.2.1", + "@vlcn.io/crsqlite": "^0.16.3", + "@vueuse/components": "^13.4.0", + "@vueuse/core": "^13.4.0", + "@vueuse/nuxt": "^13.4.0", + "drizzle-orm": "^0.44.2", + "eslint": "^9.29.0", "flyonui": "^2.2.0", "fuse.js": "^7.1.0", - "nuxt": "^3.17.4", + "nuxt": "^3.17.5", "nuxt-snackbar": "1.3.0", - "nuxt-zod-i18n": "^1.11.5", - "tailwindcss": "^4.1.8", + "nuxt-zod-i18n": "^1.12.0", + "tailwindcss": "^4.1.10", "tailwindcss-intersect": "^2.2.0", - "tailwindcss-motion": "^1.1.0", - "vue": "^3.5.16", + "tailwindcss-motion": "^1.1.1", + "vue": "^3.5.17", "vue-router": "^4.5.1", - "zod": "^3.25.42" + "zod": "^3.25.67" }, "devDependencies": { - "@iconify/json": "^2.2.343", + "@iconify/json": "^2.2.351", "@iconify/tailwind4": "^1.0.6", "@tauri-apps/cli": "^2.5.0", - "drizzle-kit": "^0.31.1", + "drizzle-kit": "^0.31.2", "globals": "^16.2.0", "typescript": "^5.8.3" }, - "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39", + "packageManager": "pnpm@10.12.2", "pnpm": { "ignoredBuiltDependencies": [ "@parcel/watcher", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 107a1cb..5d41e66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@tauri-apps/plugin-store': specifier: ^2.2.0 version: 2.2.1 + '@vlcn.io/crsqlite': + specifier: ^0.16.3 + version: 0.16.3 '@vueuse/components': specifier: ^13.3.0 version: 13.4.0(vue@3.5.17(typescript@5.8.3)) @@ -1938,6 +1941,9 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 + '@vlcn.io/crsqlite@0.16.3': + resolution: {integrity: sha512-1rKylRr2LyW5kwh/a6ZFmvt7kFK+aKLGkz9+O1w9EFVdH11SMUg6MfvxTfoczKCyb3/E5qvdOk2NDW/Zin6OjQ==} + '@volar/language-core@2.4.14': resolution: {integrity: sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==} @@ -7302,6 +7308,8 @@ snapshots: vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0) vue: 3.5.17(typescript@5.8.3) + '@vlcn.io/crsqlite@0.16.3': {} + '@volar/language-core@2.4.14': dependencies: '@volar/source-map': 2.4.14 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 352206b..0b2bd60 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1553,6 +1553,7 @@ dependencies = [ "tauri-plugin-os", "tauri-plugin-store", "tokio", + "uhlc", ] [[package]] @@ -1663,6 +1664,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + [[package]] name = "hyper" version = "1.6.0" @@ -3877,13 +3884,31 @@ dependencies = [ ] [[package]] -name = "sqlparser" -version = "0.56.0" +name = "spin" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68feb51ffa54fc841e086f58da543facfe3d7ae2a60d69b0a8cbbd30d16ae8d" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "sqlparser" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c5f081b292a3d19637f0b32a79e28ff14a9fd23ef47bd7fce08ff5de221eca" dependencies = [ "log", "recursive", + "sqlparser_derive", +] + +[[package]] +name = "sqlparser_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -4077,7 +4102,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.61.1", + "windows", "windows-core 0.61.0", "windows-version", "x11-dl", @@ -4149,7 +4174,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.61.1", + "windows", ] [[package]] @@ -4252,9 +4277,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1edf18000f02903a7c2e5997fb89aca455ecbc0acc15c6535afbb883be223" +checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" dependencies = [ "anyhow", "dunce", @@ -4270,7 +4295,6 @@ dependencies = [ "thiserror 2.0.12", "toml", "url", - "uuid", ] [[package]] @@ -4316,9 +4340,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fdc6cb608e04b7d2b6d1f21e9444ad49245f6d03465ba53323d692d1ceb1a30" +checksum = "2c8983f50326d34437142a6d560b5c3426e91324297519b6eeb32ed0a1d1e0f2" dependencies = [ "dunce", "glob", @@ -4332,7 +4356,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.12", "url", - "windows 0.60.0", + "windows", "zbus", ] @@ -4389,7 +4413,7 @@ dependencies = [ "tauri-utils", "thiserror 2.0.12", "url", - "windows 0.61.1", + "windows", ] [[package]] @@ -4415,7 +4439,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.1", + "windows", "wry", ] @@ -4475,7 +4499,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ "quick-xml 0.37.5", "thiserror 2.0.12", - "windows 0.61.1", + "windows", "windows-version", ] @@ -4822,6 +4846,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "uhlc" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bbb93b0c2258fe1e81a84d8de5391f2577b039decabf75a6441ea1ebbf4cb5" +dependencies = [ + "humantime", + "lazy_static", + "log", + "rand 0.8.5", + "serde", + "spin", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5182,9 +5220,9 @@ checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.61.1", + "windows", "windows-core 0.61.0", - "windows-implement 0.60.0", + "windows-implement", "windows-interface", ] @@ -5206,7 +5244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" dependencies = [ "thiserror 2.0.12", - "windows 0.61.1", + "windows", "windows-core 0.61.0", ] @@ -5256,39 +5294,17 @@ dependencies = [ "windows-version", ] -[[package]] -name = "windows" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" -dependencies = [ - "windows-collections 0.1.1", - "windows-core 0.60.1", - "windows-future 0.1.1", - "windows-link", - "windows-numerics 0.1.1", -] - [[package]] name = "windows" version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ - "windows-collections 0.2.0", + "windows-collections", "windows-core 0.61.0", - "windows-future 0.2.0", + "windows-future", "windows-link", - "windows-numerics 0.2.0", -] - -[[package]] -name = "windows-collections" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" -dependencies = [ - "windows-core 0.60.1", + "windows-numerics", ] [[package]] @@ -5309,42 +5325,19 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" -dependencies = [ - "windows-implement 0.59.0", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings 0.3.1", -] - [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement 0.60.0", + "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings 0.4.0", ] -[[package]] -name = "windows-future" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" -dependencies = [ - "windows-core 0.60.1", - "windows-link", -] - [[package]] name = "windows-future" version = "0.2.0" @@ -5355,17 +5348,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-implement" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -5394,16 +5376,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" -[[package]] -name = "windows-numerics" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" -dependencies = [ - "windows-core 0.60.1", - "windows-link", -] - [[package]] name = "windows-numerics" version = "0.2.0" @@ -5826,7 +5798,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.61.1", + "windows", "windows-core 0.61.0", "windows-version", "x11-dl", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 18e22eb..5a181a8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,7 +20,8 @@ tauri-build = { version = "2.2", features = [] } [dependencies] rusqlite = { version = "0.36.0", features = [ "load_extension", - "bundled-sqlcipher-vendored-openssl" + "bundled-sqlcipher-vendored-openssl", + "functions", ] } #libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] } #sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] } @@ -32,11 +33,12 @@ base64 = "0.22" mime_guess = "2.0" mime = "0.3" fs_extra = "1.3.0" -sqlparser = { version = "0.56.0", features = [] } +sqlparser = { version = "0.57.0", features = ["visitor"] } +uhlc = "0.8" tauri = { version = "2.5", features = ["protocol-asset", "devtools"] } tauri-plugin-dialog = "2.2" -tauri-plugin-fs = "2.2.0" -tauri-plugin-opener = "2.2" +tauri-plugin-fs = "2.3.0" +tauri-plugin-opener = "2.3.0" tauri-plugin-os = "2" tauri-plugin-store = "2" tauri-plugin-http = "2.4" diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib b/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib new file mode 100755 index 0000000..7fa433a Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-apple-darwin.dylib differ diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so b/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so new file mode 100755 index 0000000..3d296ac Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-linux-android.so differ diff --git a/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so b/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so new file mode 100755 index 0000000..28bcefe Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-aarch64-unknown-linux-gnu.so differ diff --git a/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll b/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll new file mode 100755 index 0000000..645e8d2 Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-i686-pcwindows-msvc.dll differ diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib b/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib new file mode 100755 index 0000000..c1de177 Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-apple-darwin.dylib differ diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll b/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll new file mode 100755 index 0000000..77f85cd Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-pcwindows-msvc.dll differ diff --git a/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so b/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so new file mode 100755 index 0000000..22c3157 Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite-x86_64-unknown-linux-gnu.so differ diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist new file mode 100644 index 0000000..2c8f326 --- /dev/null +++ b/src-tauri/cr-sqlite/crsqlite.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + crsqlite.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + crsqlite.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist new file mode 100644 index 0000000..9479102 --- /dev/null +++ b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + crsqlite + CFBundleIdentifier + io.vlcn.crsqlite + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + + diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite new file mode 100755 index 0000000..e2ed0a4 Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite differ diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist new file mode 100644 index 0000000..9479102 --- /dev/null +++ b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + crsqlite + CFBundleIdentifier + io.vlcn.crsqlite + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + + diff --git a/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite new file mode 100755 index 0000000..dfe145d Binary files /dev/null and b/src-tauri/cr-sqlite/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite differ diff --git a/src-tauri/database/schemas/crdt.ts b/src-tauri/database/schemas/crdt.ts new file mode 100644 index 0000000..c714fce --- /dev/null +++ b/src-tauri/database/schemas/crdt.ts @@ -0,0 +1,21 @@ +import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' + +export const haexCrdtMessages = sqliteTable('haex_crdt_messages', { + hlc_timestamp: text().primaryKey(), + table_name: text(), + row_pks: text({ mode: 'json' }), + op_type: text({ enum: ['INSERT', 'UPDATE', 'DELETE'] }), + column_name: text(), + new_value: blob(), + old_value: blob(), +}) +export type InsertHaexCrdtMessages = typeof haexCrdtMessages.$inferInsert +export type SelectHaexCrdtMessages = typeof haexCrdtMessages.$inferSelect + +export const haexCrdtSnapshots = sqliteTable('haex_crdt_snapshots', { + snapshot_id: text().primaryKey(), + created: text(), + epoch_hlc: text(), + location_url: text(), + file_size_bytes: integer(), +}) diff --git a/src-tauri/resources/crsqlite b/src-tauri/resources/crsqlite new file mode 100755 index 0000000..22c3157 Binary files /dev/null and b/src-tauri/resources/crsqlite differ diff --git a/src-tauri/src/database/core.rs b/src-tauri/src/database/core.rs index f39b7d9..9b84402 100644 --- a/src-tauri/src/database/core.rs +++ b/src-tauri/src/database/core.rs @@ -6,8 +6,8 @@ use rusqlite::{ Connection, OpenFlags, ToSql, }; use serde_json::Value as JsonValue; -use std::fs; use std::path::Path; +use std::{fs, path::PathBuf}; use tauri::State; // --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht --- // Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes. @@ -67,32 +67,10 @@ pub async fn execute( Ok(affected_rows) } -/// Führt SQL-Schreiboperationen (INSERT, UPDATE, DELETE, CREATE) ohne Berechtigungsprüfung aus -/* pub async fn execute( - sql: &str, - params: &[String], - state: &State<'_, DbConnection>, -) -> Result { - let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?; - let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?; - - let rows_affected = conn - .execute(sql, rusqlite::params_from_iter(params.iter())) - .map_err(|e| format!("SQL-Ausführungsfehler: {}", e))?; - - let last_id = conn.last_insert_rowid(); - - Ok(serde_json::to_string(&json!({ - "rows_affected": rows_affected, - "last_insert_id": last_id - })) - .map_err(|e| format!("JSON-Serialisierungsfehler: {}", e))?) -} */ - #[tauri::command] pub async fn select( sql: String, - params: Vec, // Parameter als JSON Values empfangen + params: Vec, state: &State<'_, DbConnection>, ) -> Result>, String> { // Ergebnis als Vec @@ -182,45 +160,6 @@ pub async fn select( Ok(result_vec) } -/// Führt SQL-Leseoperationen (SELECT) ohne Berechtigungsprüfung aus -/* pub async fn select( - sql: &str, - params: &[String], - state: &State<'_, DbConnection>, -) -> Result>>, String> { - let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?; - let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?; - - let mut stmt = conn - .prepare(sql) - .map_err(|e| format!("SQL-Vorbereitungsfehler: {}", e))?; - let columns = stmt.column_count(); - let mut rows = stmt - .query(rusqlite::params_from_iter(params.iter())) - .map_err(|e| format!("SQL-Abfragefehler: {}", e))?; - - let mut result = Vec::new(); - while let Some(row) = rows - .next() - .map_err(|e| format!("Zeilenabruffehler: {}", e))? - { - let mut row_data = Vec::new(); - for i in 0..columns { - let value = row - .get(i) - .map_err(|e| format!("Datentypfehler in Spalte {}: {}", i, e))?; - row_data.push(value); - } - /* println!( - "Select Row Data: {}", - &row_data.clone().join("").to_string() - ); */ - result.push(row_data); - } - - Ok(result) -} */ - /// Öffnet und initialisiert eine Datenbank mit Verschlüsselung pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result { let flags = if create { @@ -236,6 +175,16 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result>); +use crate::database::core::open_and_init_db; +pub struct HlcService(pub Mutex); +pub struct DbConnection(pub Arc>>); #[tauri::command] pub async fn sql_select( @@ -145,50 +148,21 @@ pub fn create_encrypted_database( eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!"); eprintln!("Der Befehl 'PRAGMA cipher_version;' schlug fehl: {}", e); eprintln!("Die Datenbank wurde wahrscheinlich NICHT verschlüsselt."); - // Optional: Hier die Verbindung schließen oder weitere Aktionen unterlassen - // return Err(e); // Beende das Programm mit dem Fehler } } - /* // Kopieren der Ressourcen-Datenbank zum Zielpfad - core::copy_file(&resource_path, &path)?; + println!("resource_path: {}", resource_path.display()); - // Öffnen der kopierten Datenbank ohne Verschlüsselung - let conn = Connection::open(&path).map_err(|e| { - format!( - "Fehler beim Öffnen der kopierten Datenbank: {}", - e.to_string() - ) - })?; + conn.close().unwrap(); - // Verschlüsseln der Datenbank mit dem angegebenen Schlüssel - conn.pragma_update(None, "key", &key) - .map_err(|e| format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string()))?; + let new_conn = open_and_init_db(&path, &key, false)?; - // Schließen der Verbindung, um sicherzustellen, dass Änderungen gespeichert werden - drop(conn); - - // Öffnen der verschlüsselten Datenbank mit dem Schlüssel - let encrypted_conn = core::open_and_init_db(&path, &key, false) - .map_err(|e| format!("Fehler beim Öffnen der verschlüsselten Datenbank: {}", e))?; - - // Überprüfen, ob die Datenbank korrekt verschlüsselt wurde, indem wir eine einfache Abfrage ausführen - let validation_result: Result = - encrypted_conn.query_row("SELECT 1", [], |row| row.get(0)); - - if let Err(e) = validation_result { - return Err(format!( - "Fehler beim Testen der verschlüsselten Datenbank: {}", - e.to_string() - )); - } - */ // Aktualisieren der Datenbankverbindung im State let mut db = state .0 .lock() .map_err(|e| format!("Mutex-Fehler: {}", e.to_string()))?; - *db = Some(conn); + *db = Some(new_conn); Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",)) } @@ -205,6 +179,7 @@ pub fn open_encrypted_database( let conn = core::open_and_init_db(&path, &key, false).map_err(|e| format!("Error during open: {}", e)); + let mut db = state.0.lock().map_err(|e| e.to_string())?; *db = Some(conn.unwrap()); @@ -516,3 +491,120 @@ pub fn create_encrypted_database_new( final_encrypted_db_path.display() )) } + +fn get_target_triple() -> Result { + let target_triple = if cfg!(target_os = "linux") { + if cfg!(target_arch = "x86_64") { + "x86_64-unknown-linux-gnu".to_string() + } else if cfg!(target_arch = "aarch64") { + "aarch64-unknown-linux-gnu".to_string() + } else { + return Err(format!( + "Unbekannte Linux-Architektur: {}", + std::env::consts::ARCH + )); + } + } else if cfg!(target_os = "macos") { + if cfg!(target_arch = "x86_64") { + "x86_64-apple-darwin".to_string() + } else if cfg!(target_arch = "aarch64") { + "aarch64-apple-darwin".to_string() + } else { + return Err(format!( + "Unbekannte macOS-Architektur: {}", + std::env::consts::ARCH + )); + } + } else if cfg!(target_os = "windows") { + if cfg!(target_arch = "x86_64") { + "x86_64-pc-windows-msvc".to_string() + } else if cfg!(target_arch = "x86") { + "i686-pc-windows-msvc".to_string() + } else { + return Err(format!( + "Unbekannte Windows-Architektur: {}", + std::env::consts::ARCH + )); + } + } else if cfg!(target_os = "android") { + if cfg!(target_arch = "aarch64") { + "aarch64-linux-android".to_string() + } else { + return Err(format!( + "Unbekannte Android-Architektur: {}", + std::env::consts::ARCH + )); + } + } else if cfg!(target_os = "ios") { + if cfg!(target_arch = "aarch64") { + "aarch64-apple-ios".to_string() + } else { + return Err(format!( + "Unbekannte iOS-Architektur: {}", + std::env::consts::ARCH + )); + } + } else { + return Err("Unbekanntes Zielsystem".to_string()); + }; + Ok(target_triple) +} + +fn get_crsqlite_path(app_handle: AppHandle) -> Result { + // Laden der cr-sqlite Erweiterung + let target_triple = get_target_triple()?; + + println!("target_triple: {}", target_triple); + + let crsqlite_path = app_handle + .path() + .resource_dir() + .map_err(|e| format!("Fehler beim Ermitteln des Ressourcenverzeichnisses: {}", e))? + .join(format!("crsqlite-{}", target_triple)); + + println!("crsqlite_path: {}", crsqlite_path.display()); + Ok(crsqlite_path) +} + +#[tauri::command] +pub fn get_hlc_timestamp(state: tauri::State) -> String { + let hlc = state.0.lock().unwrap(); + hlc.new_timestamp().to_string() +} + +#[tauri::command] +pub fn update_hlc_from_remote( + remote_timestamp_str: String, + state: tauri::State, +) -> Result<(), String> { + let remote_ts = + uhlc::Timestamp::from_str(&remote_timestamp_str).map_err(|e| e.cause.to_string())?; + + let hlc = state.0.lock().unwrap(); + hlc.update_with_timestamp(&remote_ts) + .map_err(|e| format!("HLC update failed: {:?}", e)) +} + +#[derive(Debug, Clone)] +struct SqlTableInfo { + cid: u32, + name: String, + r#type: String, + notnull: bool, + dflt_value: Option, + pk: u8, +} + +#[tauri::command] +pub async fn create_crdt_trigger_for_table( + state: &State<'_, DbConnection>, + table_name: String, +) -> Result>, String> { + let stmt = format!( + "SELECT cid, name, type, notnull, dflt_value, pk from pragma_table_info('{}')", + table_name + ); + + let table_info = core::select(stmt, vec![], state).await; + Ok(table_info.unwrap()) +} diff --git a/src-tauri/src/database/sql_proxy.rs b/src-tauri/src/database/sql_proxy.rs new file mode 100644 index 0000000..5f7307a --- /dev/null +++ b/src-tauri/src/database/sql_proxy.rs @@ -0,0 +1,63 @@ +// src-tauri/src/sql_proxy.rs + +use rusqlite::Connection; +use sqlparser::ast::Statement; +use sqlparser::dialect::SQLiteDialect; +use sqlparser::parser::Parser; +use std::sync::{Arc, Mutex}; + +// Der Schema-Cache wird später benötigt, um zu wissen, welche Tabellen eine 'tombstone'-Spalte haben. +// Für den Anfang lassen wir ihn leer. +pub struct SchemaCache { + // TODO: z.B. HashMap> für Tabellen und ihre Spalten +} + +impl SchemaCache { + pub fn new() -> Self { + Self {} + } + // TODO: Methoden zum Befüllen und Abfragen des Caches +} + +// Die Hauptstruktur unseres Proxys +pub struct SqlProxy { + // Wir benötigen eine threadsichere Referenz auf den Schema-Cache + schema_cache: Arc>, +} + +impl SqlProxy { + pub fn new(schema_cache: Arc>) -> Self { + Self { schema_cache } + } + + // Die zentrale Ausführungsfunktion + pub fn execute(&self, sql: &str, conn: &Connection) -> Result<(), String> { + // 1. Parsen des SQL-Strings in einen AST + let dialect = SQLiteDialect {}; + let mut ast = + Parser::parse_sql(&dialect, sql).map_err(|e| format!("SQL-Parse-Fehler: {}", e))?; + + // Sicherstellen, dass wir nur eine Anweisung haben + if ast.len() != 1 { + return Err("Nur einzelne SQL-Anweisungen werden unterstützt.".to_string()); + } + let statement = &mut ast; + + // 2. Umschreiben des AST (Logik folgt in Abschnitt 2) + self.transform_statement(statement)?; + + // 3. Ausführen der (möglicherweise modifizierten) Anweisung + let final_sql = statement.to_string(); + conn.execute(&final_sql) + .map_err(|e| format!("DB-Ausführungsfehler: {}", e))?; + + Ok(()) + } + + // Platzhalter für die Transformationslogik + fn transform_statement(&self, statement: &mut Statement) -> Result<(), String> { + // HIER KOMMT DIE MAGIE HIN + // TODO: Implementierung der `CREATE TABLE`, `DELETE` und `SELECT` Transformationen + Ok(()) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 55db12e..86e3160 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -5,7 +5,7 @@ mod models; use database::DbConnection; use models::ExtensionState; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -42,7 +42,7 @@ pub fn run() { } } }) - .manage(DbConnection(Mutex::new(None))) + .manage(DbConnection(Arc::new(Mutex::new(None)))) .manage(ExtensionState::default()) .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_dialog::init()) @@ -58,6 +58,8 @@ pub fn run() { database::sql_execute, database::sql_select, database::test, + database::get_hlc_timestamp, + database::update_hlc_from_remote, extension::copy_directory, extension::database::extension_sql_execute, extension::database::extension_sql_select, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ef982a3..aa12a41 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -55,6 +55,21 @@ "icons/icon.icns", "icons/icon.ico" ], - "resources": ["database/vault.db"] + "resources": ["database/vault.db", "resources/"], + + "linux": { + "appimage": { + "bundleMediaFramework": false, + "files": {} + }, + "deb": { + "files": {} + }, + "rpm": { + "epoch": 0, + "files": {}, + "release": "1" + } + } } } diff --git a/src/stores/passwords/items.ts b/src/stores/passwords/items.ts index c9d6d78..09345a7 100644 --- a/src/stores/passwords/items.ts +++ b/src/stores/passwords/items.ts @@ -343,3 +343,20 @@ const deleteKeyValueAsync = async (id: string) => { .delete(haexPasswordsItemKeyValues) .where(eq(haexPasswordsItemKeyValues.id, id)) } + +const areItemsEqual = ( + groupA: unknown | unknown[] | null, + groupB: unknown | unknown[] | null, +) => { + if (groupA === null && groupB === null) return true + + if (Array.isArray(groupA) && Array.isArray(groupB)) { + console.log('compare object arrays', groupA, groupB) + if (groupA.length === groupB.length) return true + + return groupA.some((group, index) => { + return areObjectsEqual(group, groupA[index]) + }) + } + return areObjectsEqual(groupA, groupB) +} \ No newline at end of file diff --git a/src/stores/vault/index.ts b/src/stores/vault/index.ts index 7a8abe2..86c5f08 100644 --- a/src/stores/vault/index.ts +++ b/src/stores/vault/index.ts @@ -3,6 +3,8 @@ import { invoke } from '@tauri-apps/api/core' import { platform } from '@tauri-apps/plugin-os' import * as schema from '@/../src-tauri/database/schemas/vault' import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy' +import { isTable, sql } from 'drizzle-orm' +import { getTableConfig } from 'drizzle-orm/sqlite-core' interface IVault { name: string @@ -63,7 +65,7 @@ export const useVaultStore = defineStore('vaultStore', () => { // If the query is a SELECT, use the select method if (isSelectQuery(sql)) { - console.log('sql_select', sql, params) + console.log('sql_select', sql, params, method) rows = await invoke('sql_select', { sql, params, @@ -72,7 +74,7 @@ export const useVaultStore = defineStore('vaultStore', () => { return [] }) } else { - console.log('sql_execute', sql, params) + console.log('sql_execute', sql, params, method) // Otherwise, use the execute method rows = await invoke('sql_execute', { sql, @@ -95,7 +97,6 @@ export const useVaultStore = defineStore('vaultStore', () => { const { addVaultAsync } = useLastVaultStore() await addVaultAsync({ path }) - return vaultId } catch (error) { console.error('Error openAsync ', error) @@ -145,6 +146,21 @@ const getVaultIdAsync = async (path: string) => { } const isSelectQuery = (sql: string) => { + console.log('check isSelectQuery', sql) const selectRegex = /^\s*SELECT\b/i return selectRegex.test(sql) } + +/* const trackAllHaexTablesAsync = async (vaultId: string) => { + const { openVaults } = useVaultStore() + const promises = Object.values(schema) + .filter(isTable) + .map((table) => { + const stmt = `SELECT crsql_as_crr('${getTableConfig(table).name}');` + console.log('track table', getTableConfig(table).name) + + return openVaults?.[vaultId]?.drizzle.run(sql`${stmt}`.getSQL()) + }) + + await Promise.allSettled(promises) +} */