From 225835e5d1a846e379949fa8355e5fb9b89339af Mon Sep 17 00:00:00 2001 From: haex Date: Thu, 2 Oct 2025 17:18:28 +0200 Subject: [PATCH] add more typesafty --- src-tauri/Cargo.toml | 2 +- src-tauri/build.rs | 107 +- src-tauri/database/generate-rust-types.ts | 31 +- .../0001_green_stark_industries.sql | 1 + .../migrations/meta/0001_snapshot.json | 926 ++++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + src-tauri/database/schemas/crdt.ts | 47 +- src-tauri/database/schemas/haex.ts | 58 +- src-tauri/database/schemas/vault.ts | 18 - src-tauri/database/tableNames.json | 100 +- src-tauri/{src/build => generator}/mod.rs | 2 +- .../{src/build => generator}/rust_types.rs | 0 src-tauri/generator/table_names.rs | 213 ++++ src-tauri/src/build/table_names.rs | 64 -- src-tauri/src/crdt/trigger.rs | 20 +- src-tauri/src/database/generated.rs | 8 +- src-tauri/src/database/mod.rs | 10 +- src-tauri/src/extension/database/executor.rs | 58 +- .../src/extension/permissions/manager.rs | 267 ++--- src-tauri/src/extension/permissions/types.rs | 126 ++- 20 files changed, 1600 insertions(+), 465 deletions(-) create mode 100644 src-tauri/database/migrations/0001_green_stark_industries.sql create mode 100644 src-tauri/database/migrations/meta/0001_snapshot.json rename src-tauri/{src/build => generator}/mod.rs (57%) rename src-tauri/{src/build => generator}/rust_types.rs (100%) create mode 100644 src-tauri/generator/table_names.rs delete mode 100644 src-tauri/src/build/table_names.rs diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ddb382d..01c2d76 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,9 +16,9 @@ crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] serde_json = "1.0.145" - tauri-build = { version = "2.2", features = [] } serde = { version = "1.0.228", features = ["derive"] } + [dependencies] rusqlite = { version = "0.37.0", features = [ "load_extension", diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 51fb828..9f2729e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,106 +1,7 @@ -use serde::Deserialize; -use std::env; -use std::fs::File; -use std::io::{BufReader, Write}; -use std::path::Path; - -#[derive(Debug, Deserialize)] -struct Schema { - haex: Haex, -} - -#[derive(Debug, Deserialize)] -struct Haex { - settings: String, - extensions: String, - extension_permissions: String, - notifications: String, - passwords: Passwords, - crdt: Crdt, -} - -#[derive(Debug, Deserialize)] -struct Passwords { - groups: String, - group_items: String, - item_details: String, - item_key_values: String, - item_histories: String, -} - -#[derive(Debug, Deserialize)] -struct Crdt { - logs: String, - snapshots: String, - configs: String, -} +mod generator; fn main() { - // Pfad zur Eingabe-JSON und zur Ausgabe-Rust-Datei festlegen. - // `OUT_DIR` ist ein spezielles Verzeichnis, das Cargo für generierte Dateien bereitstellt. - let schema_path = Path::new("database/tableNames.json"); - let out_dir = - env::var("OUT_DIR").expect("OUT_DIR ist nicht gesetzt. Führen Sie dies mit Cargo aus."); - let dest_path = Path::new(&out_dir).join("tableNames.rs"); - - // --- 2. JSON-Datei lesen und mit serde parsen --- - let file = File::open(&schema_path).expect("Konnte tableNames.json nicht öffnen"); - let reader = BufReader::new(file); - let schema: Schema = - serde_json::from_reader(reader).expect("Konnte tableNames.json nicht parsen"); - let haex = schema.haex; - - // --- 3. Den zu generierenden Rust-Code als String erstellen --- - // Wir verwenden das `format!`-Makro, um die Werte aus den geparsten Structs - // in einen vordefinierten Code-Template-String einzufügen. - // Das `r#""#`-Format erlaubt uns, mehrzeilige Strings mit Anführungszeichen zu verwenden. - let code = format!( - r#" - // HINWEIS: Diese Datei wurde automatisch von build.rs generiert. - // Manuelle Änderungen werden bei der nächsten Kompilierung überschrieben! - - pub const TABLE_SETTINGS: &str = "{settings}"; - pub const TABLE_EXTENSIONS: &str = "{extensions}"; - pub const TABLE_EXTENSION_PERMISSIONS: &str = "{extension_permissions}"; - pub const TABLE_NOTIFICATIONS: &str = "{notifications}"; - - // Passwords - pub const TABLE_PASSWORDS_GROUPS: &str = "{pw_groups}"; - pub const TABLE_PASSWORDS_GROUP_ITEMS: &str = "{pw_group_items}"; - pub const TABLE_PASSWORDS_ITEM_DETAILS: &str = "{pw_item_details}"; - pub const TABLE_PASSWORDS_ITEM_KEY_VALUES: &str = "{pw_item_key_values}"; - pub const TABLE_PASSWORDS_ITEM_HISTORIES: &str = "{pw_item_histories}"; - - // CRDT - pub const TABLE_CRDT_LOGS: &str = "{crdt_logs}"; - pub const TABLE_CRDT_SNAPSHOTS: &str = "{crdt_snapshots}"; - pub const TABLE_CRDT_CONFIGS: &str = "{crdt_configs}"; - - "#, - // Hier werden die Werte aus dem `haex`-Struct in die Platzhalter oben eingesetzt. - settings = haex.settings, - extensions = haex.extensions, - extension_permissions = haex.extension_permissions, - notifications = haex.notifications, - pw_groups = haex.passwords.groups, - pw_group_items = haex.passwords.group_items, - pw_item_details = haex.passwords.item_details, - pw_item_key_values = haex.passwords.item_key_values, - pw_item_histories = haex.passwords.item_histories, - crdt_logs = haex.crdt.logs, - crdt_snapshots = haex.crdt.snapshots, - crdt_configs = haex.crdt.configs - ); - - // --- 4. Den generierten Code in die Zieldatei schreiben --- - let mut f = File::create(&dest_path).expect("Konnte die Zieldatei nicht erstellen"); - f.write_all(code.as_bytes()) - .expect("Konnte nicht in die Zieldatei schreiben"); - - // --- 5. Cargo anweisen, das Skript erneut auszuführen, wenn sich die JSON-Datei ändert --- - // Diese Zeile ist extrem wichtig für eine reibungslose Entwicklung! Ohne sie - // würde Cargo Änderungen an der JSON-Datei nicht bemerken. - println!("cargo:rerun-if-changed=database/tableNames.json"); - - tauri_build::build() + generator::table_names::generate_table_names(); + generator::rust_types::generate_rust_types(); + tauri_build::build(); } diff --git a/src-tauri/database/generate-rust-types.ts b/src-tauri/database/generate-rust-types.ts index d278806..210ff11 100644 --- a/src-tauri/database/generate-rust-types.ts +++ b/src-tauri/database/generate-rust-types.ts @@ -23,23 +23,16 @@ function drizzleToRustType(colDef: AnySQLiteColumn): { let isOptional = !colDef.notNull if (colDef.columnType === 'SQLiteText') { - // Das 'mode' Property ist ebenfalls direkt auf dem Objekt verfügbar if ('mode' in colDef && colDef.mode === 'json') { baseType = 'serde_json::Value' } else { baseType = 'String' } } else if (colDef.columnType === 'SQLiteInteger') { - if ('mode' in colDef && colDef.mode === 'boolean') { - baseType = 'bool' - } else if ('mode' in colDef && colDef.mode === 'timestamp') { - baseType = 'i64' - } else { - baseType = 'i64' - } - } - // ... weitere SQLite-Typen hier ... - else if (colDef.columnType === 'SQLiteReal') { + baseType = 'i64' + } else if (colDef.columnType === 'SQLiteBoolean') { + baseType = 'bool' + } else if (colDef.columnType === 'SQLiteReal') { baseType = 'f64' } else if (colDef.columnType === 'SQLiteBlob') { baseType = 'Vec' @@ -81,6 +74,7 @@ function toSnakeCase(str: string): string { } function toPascalCase(str: string): string { + console.log('toPascalCase:', str) return str .split('_') .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) @@ -164,15 +158,18 @@ use serde::{Deserialize, Serialize}; ` const schemas = [ - { name: tablesNames.haex.settings, table: schema.haexSettings }, - { name: tablesNames.haex.extensions, table: schema.haexExtensions }, + { name: tablesNames.haex.settings.name, table: schema.haexSettings }, + { name: tablesNames.haex.extensions.name, table: schema.haexExtensions }, { - name: tablesNames.haex.extension_permissions, + name: tablesNames.haex.extension_permissions.name, table: schema.haexExtensionPermissions, }, - { name: tablesNames.haex.crdt.logs, table: schema.haexCrdtLogs }, - { name: tablesNames.haex.crdt.snapshots, table: schema.haexCrdtSnapshots }, - { name: tablesNames.haex.crdt.configs, table: schema.haexCrdtConfigs }, + { name: tablesNames.haex.crdt.logs.name, table: schema.haexCrdtLogs }, + { + name: tablesNames.haex.crdt.snapshots.name, + table: schema.haexCrdtSnapshots, + }, + { name: tablesNames.haex.crdt.configs.name, table: schema.haexCrdtConfigs }, ] for (const { name, table } of schemas) { diff --git a/src-tauri/database/migrations/0001_green_stark_industries.sql b/src-tauri/database/migrations/0001_green_stark_industries.sql new file mode 100644 index 0000000..34610f6 --- /dev/null +++ b/src-tauri/database/migrations/0001_green_stark_industries.sql @@ -0,0 +1 @@ +ALTER TABLE `haex_notifications` ADD `haex_timestamp` text; \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/0001_snapshot.json b/src-tauri/database/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..6347f78 --- /dev/null +++ b/src-tauri/database/migrations/meta/0001_snapshot.json @@ -0,0 +1,926 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "862ac1d5-3065-4244-8652-2b6782254862", + "prevId": "3bbe52b8-5933-4b21-8b24-de3927a2f9b0", + "tables": { + "haex_crdt_configs": { + "name": "haex_crdt_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_logs": { + "name": "haex_crdt_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "row_pks": { + "name": "row_pks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op_type": { + "name": "op_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_haex_timestamp": { + "name": "idx_haex_timestamp", + "columns": [ + "haex_timestamp" + ], + "isUnique": false + }, + "idx_table_row": { + "name": "idx_table_row", + "columns": [ + "table_name", + "row_pks" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_crdt_snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epoch_hlc": { + "name": "epoch_hlc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_url": { + "name": "location_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "extension_id": { + "name": "extension_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "constraints": { + "name": "constraints", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'denied'" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "haex_extension_permissions_extension_id_resource_type_action_target_unique": { + "name": "haex_extension_permissions_extension_id_resource_type_action_target_unique", + "columns": [ + "extension_id", + "resource_type", + "action", + "target" + ], + "isUnique": true + } + }, + "foreignKeys": { + "haex_extension_permissions_extension_id_haex_extensions_id_fk": { + "name": "haex_extension_permissions_extension_id_haex_extensions_id_fk", + "tableFrom": "haex_extension_permissions", + "tableTo": "haex_extensions", + "columnsFrom": [ + "extension_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_extensions": { + "name": "haex_extensions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entry": { + "name": "entry", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "homepage": { + "name": "homepage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_notifications": { + "name": "haex_notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alt": { + "name": "alt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_settings": { + "name": "haex_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_timestamp": { + "name": "haex_timestamp", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_group_items": { + "name": "haex_passwords_group_items", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_group_items", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "haex_passwords_group_items_item_id_group_id_pk": { + "columns": [ + "item_id", + "group_id" + ], + "name": "haex_passwords_group_items_item_id_group_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_groups": { + "name": "haex_passwords_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": { + "name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk", + "tableFrom": "haex_passwords_groups", + "tableTo": "haex_passwords_groups", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_details": { + "name": "haex_passwords_item_details", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_history": { + "name": "haex_passwords_item_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_property": { + "name": "changed_property", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "old_value": { + "name": "old_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "new_value": { + "name": "new_value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_history", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "haex_passwords_item_key_values": { + "name": "haex_passwords_item_key_values", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haex_tombstone": { + "name": "haex_tombstone", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": { + "name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk", + "tableFrom": "haex_passwords_item_key_values", + "tableTo": "haex_passwords_item_details", + "columnsFrom": [ + "item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/src-tauri/database/migrations/meta/_journal.json b/src-tauri/database/migrations/meta/_journal.json index 492d9fe..62224ea 100644 --- a/src-tauri/database/migrations/meta/_journal.json +++ b/src-tauri/database/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1759402321133, "tag": "0000_glamorous_hulk", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1759418087677, + "tag": "0001_green_stark_industries", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src-tauri/database/schemas/crdt.ts b/src-tauri/database/schemas/crdt.ts index 68efa6d..ed08caa 100644 --- a/src-tauri/database/schemas/crdt.ts +++ b/src-tauri/database/schemas/crdt.ts @@ -2,18 +2,24 @@ import { integer, sqliteTable, text, index } from 'drizzle-orm/sqlite-core' import tableNames from '../tableNames.json' export const haexCrdtLogs = sqliteTable( - tableNames.haex.crdt.logs, + tableNames.haex.crdt.logs.name, { id: text() .primaryKey() .$defaultFn(() => crypto.randomUUID()), - haexTimestamp: text('haex_timestamp'), - tableName: text('table_name'), - rowPks: text('row_pks', { mode: 'json' }), - opType: text('op_type', { enum: ['INSERT', 'UPDATE', 'DELETE'] }), - columnName: text('column_name'), - newValue: text('new_value', { mode: 'json' }), - oldValue: text('old_value', { mode: 'json' }), + haexTimestamp: text(tableNames.haex.crdt.logs.columns.haexTimestamp), + tableName: text(tableNames.haex.crdt.logs.columns.tableName), + rowPks: text(tableNames.haex.crdt.logs.columns.rowPks, { mode: 'json' }), + opType: text(tableNames.haex.crdt.logs.columns.opType, { + enum: ['INSERT', 'UPDATE', 'DELETE'], + }), + columnName: text(tableNames.haex.crdt.logs.columns.columnName), + newValue: text(tableNames.haex.crdt.logs.columns.newValue, { + mode: 'json', + }), + oldValue: text(tableNames.haex.crdt.logs.columns.oldValue, { + mode: 'json', + }), }, (table) => [ index('idx_haex_timestamp').on(table.haexTimestamp), @@ -23,17 +29,22 @@ export const haexCrdtLogs = sqliteTable( export type InsertHaexCrdtLogs = typeof haexCrdtLogs.$inferInsert export type SelectHaexCrdtLogs = typeof haexCrdtLogs.$inferSelect -export const haexCrdtSnapshots = sqliteTable(tableNames.haex.crdt.snapshots, { - snapshot_id: text() - .primaryKey() - .$defaultFn(() => crypto.randomUUID()), - created: text(), - epoch_hlc: text(), - location_url: text(), - file_size_bytes: integer(), -}) +export const haexCrdtSnapshots = sqliteTable( + tableNames.haex.crdt.snapshots.name, + { + snapshotId: text(tableNames.haex.crdt.snapshots.columns.snapshotId) + .primaryKey() + .$defaultFn(() => crypto.randomUUID()), + created: text(), + epochHlc: text(tableNames.haex.crdt.snapshots.columns.epochHlc), + locationUrl: text(tableNames.haex.crdt.snapshots.columns.locationUrl), + fileSizeBytes: integer( + tableNames.haex.crdt.snapshots.columns.fileSizeBytes, + ), + }, +) -export const haexCrdtConfigs = sqliteTable(tableNames.haex.crdt.configs, { +export const haexCrdtConfigs = sqliteTable(tableNames.haex.crdt.configs.name, { key: text() .primaryKey() .$defaultFn(() => crypto.randomUUID()), diff --git a/src-tauri/database/schemas/haex.ts b/src-tauri/database/schemas/haex.ts index b209be8..6c6a4f0 100644 --- a/src-tauri/database/schemas/haex.ts +++ b/src-tauri/database/schemas/haex.ts @@ -8,20 +8,22 @@ import { } from 'drizzle-orm/sqlite-core' import tableNames from '../tableNames.json' -export const haexSettings = sqliteTable(tableNames.haex.settings, { +export const haexSettings = sqliteTable(tableNames.haex.settings.name, { id: text() .primaryKey() .$defaultFn(() => crypto.randomUUID()), key: text(), type: text(), value: text(), - haexTombstone: integer('haex_tombstone', { mode: 'boolean' }), - haexTimestamp: text('haex_timestamp'), + haexTombstone: integer(tableNames.haex.settings.columns.haexTombstone, { + mode: 'boolean', + }), + haexTimestamp: text(tableNames.haex.settings.columns.haexTimestamp), }) export type InsertHaexSettings = typeof haexSettings.$inferInsert export type SelectHaexSettings = typeof haexSettings.$inferSelect -export const haexExtensions = sqliteTable(tableNames.haex.extensions, { +export const haexExtensions = sqliteTable(tableNames.haex.extensions.name, { id: text() .primaryKey() .$defaultFn(() => crypto.randomUUID()), @@ -36,21 +38,23 @@ export const haexExtensions = sqliteTable(tableNames.haex.extensions, { signature: text(), url: text(), version: text(), - haexTombstone: integer('haex_tombstone', { mode: 'boolean' }), - haexTimestamp: text('haex_timestamp'), + haexTombstone: integer(tableNames.haex.extensions.columns.haexTombstone, { + mode: 'boolean', + }), + haexTimestamp: text(tableNames.haex.extensions.columns.haexTimestamp), }) export type InsertHaexExtensions = typeof haexExtensions.$inferInsert export type SelectHaexExtensions = typeof haexExtensions.$inferSelect export const haexExtensionPermissions = sqliteTable( - tableNames.haex.extension_permissions, + tableNames.haex.extension_permissions.name, { id: text() .primaryKey() .$defaultFn(() => crypto.randomUUID()), - extensionId: text('extension_id').references( - (): AnySQLiteColumn => haexExtensions.id, - ), + extensionId: text( + tableNames.haex.extension_permissions.columns.extensionId, + ).references((): AnySQLiteColumn => haexExtensions.id), resourceType: text('resource_type', { enum: ['fs', 'http', 'db', 'shell'], }), @@ -64,8 +68,13 @@ export const haexExtensionPermissions = sqliteTable( updateAt: integer('updated_at', { mode: 'timestamp' }).$onUpdate( () => new Date(), ), - haexTombstone: integer('haex_tombstone', { mode: 'boolean' }), - haexTimestamp: text('haex_timestamp'), + haexTombstone: integer( + tableNames.haex.extension_permissions.columns.haexTombstone, + { mode: 'boolean' }, + ), + haexTimestamp: text( + tableNames.haex.extension_permissions.columns.haexTimestamp, + ), }, (table) => [ unique().on( @@ -80,3 +89,28 @@ export type InserthaexExtensionPermissions = typeof haexExtensionPermissions.$inferInsert export type SelecthaexExtensionPermissions = typeof haexExtensionPermissions.$inferSelect + +export const haexNotifications = sqliteTable( + tableNames.haex.notifications.name, + { + id: text().primaryKey(), + alt: text(), + date: text(), + icon: text(), + image: text(), + read: integer({ mode: 'boolean' }), + source: text(), + text: text(), + title: text(), + type: text({ + enum: ['error', 'success', 'warning', 'info', 'log'], + }).notNull(), + haexTombstone: integer( + tableNames.haex.notifications.columns.haexTombstone, + { mode: 'boolean' }, + ), + haexTimestamp: text(tableNames.haex.notifications.columns.haexTimestamp), + }, +) +export type InsertHaexNotifications = typeof haexNotifications.$inferInsert +export type SelectHaexNotifications = typeof haexNotifications.$inferSelect diff --git a/src-tauri/database/schemas/vault.ts b/src-tauri/database/schemas/vault.ts index 4971aed..7b2b259 100644 --- a/src-tauri/database/schemas/vault.ts +++ b/src-tauri/database/schemas/vault.ts @@ -8,24 +8,6 @@ import { } from 'drizzle-orm/sqlite-core' import tableNames from '../tableNames.json' -export const haexNotifications = sqliteTable(tableNames.haex.notifications, { - id: text().primaryKey(), - alt: text(), - date: text(), - icon: text(), - image: text(), - read: integer({ mode: 'boolean' }), - source: text(), - text: text(), - title: text(), - type: text({ - enum: ['error', 'success', 'warning', 'info', 'log'], - }).notNull(), - haex_tombstone: integer({ mode: 'boolean' }), -}) -export type InsertHaexNotifications = typeof haexNotifications.$inferInsert -export type SelectHaexNotifications = typeof haexNotifications.$inferSelect - export const haexPasswordsItemDetails = sqliteTable( tableNames.haex.passwords.item_details, { diff --git a/src-tauri/database/tableNames.json b/src-tauri/database/tableNames.json index b779350..6f077de 100644 --- a/src-tauri/database/tableNames.json +++ b/src-tauri/database/tableNames.json @@ -1,9 +1,68 @@ { "haex": { - "settings": "haex_settings", - "extensions": "haex_extensions", - "extension_permissions": "haex_extension_permissions", - "notifications": "haex_notifications", + "settings": { + "name": "haex_settings", + "columns": { + "id": "id", + "key": "key", + "type": "type", + "value": "value", + "haexTombstone": "haex_tombstone", + "haexTimestamp": "haex_timestamp" + } + }, + "extensions": { + "name": "haex_extensions", + "columns": { + "id": "id", + "author": "author", + "description": "description", + "entry": "entry", + "homepage": "homepage", + "enabled": "enabled", + "icon": "icon", + "name": "name", + "public_key": "public_key", + "signature": "signature", + "url": "url", + "version": "version", + "haexTombstone": "haex_tombstone", + "haexTimestamp": "haex_timestamp" + } + }, + "extension_permissions": { + "name": "haex_extension_permissions", + "columns": { + "id": "id", + "extensionId": "extension_id", + "resourceType": "resource_type", + "action": "action", + "target": "target", + "constraints": "constraints", + "status": "status", + "createdAt": "created_at", + "updateAt": "updated_at", + "haexTombstone": "haex_tombstone", + "haexTimestamp": "haex_timestamp" + } + }, + "notifications": { + "name": "haex_notifications", + "columns": { + "id": "id", + "alt": "alt", + "date": "date", + "icon": "icon", + "image": "image", + "read": "read", + "source": "source", + "text": "text", + "title": "title", + "type": "type", + "haexTombstone": "haex_tombstone", + "haexTimestamp": "haex_timestamp" + } + }, "passwords": { "groups": "haex_passwords_groups", "group_items": "haex_passwords_group_items", @@ -12,9 +71,36 @@ "item_histories": "haex_passwords_item_history" }, "crdt": { - "logs": "haex_crdt_logs", - "snapshots": "haex_crdt_snapshots", - "configs": "haex_crdt_configs" + "logs": { + "name": "haex_crdt_logs", + "columns": { + "id": "id", + "haexTimestamp": "haex_timestamp", + "tableName": "table_name", + "rowPks": "row_pks", + "opType": "op_type", + "columnName": "column_name", + "newValue": "new_value", + "oldValue": "old_value" + } + }, + "snapshots": { + "name": "haex_crdt_snapshots", + "columns": { + "snapshotId": "snapshot_id", + "created": "created", + "epochHlc": "epoch_hlc", + "locationUrl": "location_url", + "fileSizeBytes": "file_size_bytes" + } + }, + "configs": { + "name": "haex_crdt_configs", + "columns": { + "key": "key", + "value": "value" + } + } } } } diff --git a/src-tauri/src/build/mod.rs b/src-tauri/generator/mod.rs similarity index 57% rename from src-tauri/src/build/mod.rs rename to src-tauri/generator/mod.rs index 9faabd4..16eeef6 100644 --- a/src-tauri/src/build/mod.rs +++ b/src-tauri/generator/mod.rs @@ -1,3 +1,3 @@ -// src-tauri/src/build/mod.rs +// build/mod.rs pub mod rust_types; pub mod table_names; diff --git a/src-tauri/src/build/rust_types.rs b/src-tauri/generator/rust_types.rs similarity index 100% rename from src-tauri/src/build/rust_types.rs rename to src-tauri/generator/rust_types.rs diff --git a/src-tauri/generator/table_names.rs b/src-tauri/generator/table_names.rs new file mode 100644 index 0000000..23fce0c --- /dev/null +++ b/src-tauri/generator/table_names.rs @@ -0,0 +1,213 @@ +// src-tarui/src/build/table_names.rs +use serde::Deserialize; +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{BufReader, Write}; +use std::path::Path; + +#[derive(Debug, Deserialize)] +struct Schema { + haex: Haex, +} + +#[derive(Debug, Deserialize)] +#[allow(non_snake_case)] +struct Haex { + settings: TableDefinition, + extensions: TableDefinition, + extension_permissions: TableDefinition, + notifications: TableDefinition, + crdt: Crdt, +} + +#[derive(Debug, Deserialize)] +struct Crdt { + logs: TableDefinition, + snapshots: TableDefinition, + configs: TableDefinition, +} + +#[derive(Debug, Deserialize)] +struct TableDefinition { + name: String, + columns: HashMap, +} + +pub fn generate_table_names() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR ist nicht gesetzt."); + println!("Generiere Tabellennamen nach {}", out_dir); + let schema_path = Path::new("database/tableNames.json"); + let dest_path = Path::new(&out_dir).join("tableNames.rs"); + + let file = File::open(&schema_path).expect("Konnte tableNames.json nicht öffnen"); + let reader = BufReader::new(file); + let schema: Schema = + serde_json::from_reader(reader).expect("Konnte tableNames.json nicht parsen"); + let haex = schema.haex; + + let code = format!( + r#" +// ================================================================== +// HINWEIS: Diese Datei wurde automatisch von build.rs generiert. +// Manuelle Änderungen werden bei der nächsten Kompilierung überschrieben! +// ================================================================== + +// --- Table: haex_settings --- +pub const TABLE_SETTINGS: &str = "{t_settings}"; +pub const COL_SETTINGS_ID: &str = "{c_settings_id}"; +pub const COL_SETTINGS_KEY: &str = "{c_settings_key}"; +pub const COL_SETTINGS_TYPE: &str = "{c_settings_type}"; +pub const COL_SETTINGS_VALUE: &str = "{c_settings_value}"; +pub const COL_SETTINGS_HAEX_TOMBSTONE: &str = "{c_settings_tombstone}"; +pub const COL_SETTINGS_HAEX_TIMESTAMP: &str = "{c_settings_timestamp}"; + +// --- Table: haex_extensions --- +pub const TABLE_EXTENSIONS: &str = "{t_extensions}"; +pub const COL_EXTENSIONS_ID: &str = "{c_ext_id}"; +pub const COL_EXTENSIONS_AUTHOR: &str = "{c_ext_author}"; +pub const COL_EXTENSIONS_DESCRIPTION: &str = "{c_ext_description}"; +pub const COL_EXTENSIONS_ENTRY: &str = "{c_ext_entry}"; +pub const COL_EXTENSIONS_HOMEPAGE: &str = "{c_ext_homepage}"; +pub const COL_EXTENSIONS_ENABLED: &str = "{c_ext_enabled}"; +pub const COL_EXTENSIONS_ICON: &str = "{c_ext_icon}"; +pub const COL_EXTENSIONS_NAME: &str = "{c_ext_name}"; +pub const COL_EXTENSIONS_PUBLIC_KEY: &str = "{c_ext_public_key}"; +pub const COL_EXTENSIONS_SIGNATURE: &str = "{c_ext_signature}"; +pub const COL_EXTENSIONS_URL: &str = "{c_ext_url}"; +pub const COL_EXTENSIONS_VERSION: &str = "{c_ext_version}"; +pub const COL_EXTENSIONS_HAEX_TOMBSTONE: &str = "{c_ext_tombstone}"; +pub const COL_EXTENSIONS_HAEX_TIMESTAMP: &str = "{c_ext_timestamp}"; + +// --- Table: haex_extension_permissions --- +pub const TABLE_EXTENSION_PERMISSIONS: &str = "{t_ext_perms}"; +pub const COL_EXT_PERMS_ID: &str = "{c_extp_id}"; +pub const COL_EXT_PERMS_EXTENSION_ID: &str = "{c_extp_extensionId}"; +pub const COL_EXT_PERMS_RESOURCE_TYPE: &str = "{c_extp_resourceType}"; +pub const COL_EXT_PERMS_ACTION: &str = "{c_extp_action}"; +pub const COL_EXT_PERMS_TARGET: &str = "{c_extp_target}"; +pub const COL_EXT_PERMS_CONSTRAINTS: &str = "{c_extp_constraints}"; +pub const COL_EXT_PERMS_STATUS: &str = "{c_extp_status}"; +pub const COL_EXT_PERMS_CREATED_AT: &str = "{c_extp_createdAt}"; +pub const COL_EXT_PERMS_UPDATE_AT: &str = "{c_extp_updateAt}"; +pub const COL_EXT_PERMS_HAEX_TOMBSTONE: &str = "{c_extp_tombstone}"; +pub const COL_EXT_PERMS_HAEX_TIMESTAMP: &str = "{c_extp_timestamp}"; + +// --- Table: haex_notifications --- +pub const TABLE_NOTIFICATIONS: &str = "{t_notifications}"; +pub const COL_NOTIFICATIONS_ID: &str = "{c_notif_id}"; +pub const COL_NOTIFICATIONS_ALT: &str = "{c_notif_alt}"; +pub const COL_NOTIFICATIONS_DATE: &str = "{c_notif_date}"; +pub const COL_NOTIFICATIONS_ICON: &str = "{c_notif_icon}"; +pub const COL_NOTIFICATIONS_IMAGE: &str = "{c_notif_image}"; +pub const COL_NOTIFICATIONS_READ: &str = "{c_notif_read}"; +pub const COL_NOTIFICATIONS_SOURCE: &str = "{c_notif_source}"; +pub const COL_NOTIFICATIONS_TEXT: &str = "{c_notif_text}"; +pub const COL_NOTIFICATIONS_TITLE: &str = "{c_notif_title}"; +pub const COL_NOTIFICATIONS_TYPE: &str = "{c_notif_type}"; +pub const COL_NOTIFICATIONS_HAEX_TOMBSTONE: &str = "{c_notif_tombstone}"; + +// --- Table: haex_crdt_logs --- +pub const TABLE_CRDT_LOGS: &str = "{t_crdt_logs}"; +pub const COL_CRDT_LOGS_ID: &str = "{c_crdt_logs_id}"; +pub const COL_CRDT_LOGS_HAEX_TIMESTAMP: &str = "{c_crdt_logs_timestamp}"; +pub const COL_CRDT_LOGS_TABLE_NAME: &str = "{c_crdt_logs_tableName}"; +pub const COL_CRDT_LOGS_ROW_PKS: &str = "{c_crdt_logs_rowPks}"; +pub const COL_CRDT_LOGS_OP_TYPE: &str = "{c_crdt_logs_opType}"; +pub const COL_CRDT_LOGS_COLUMN_NAME: &str = "{c_crdt_logs_columnName}"; +pub const COL_CRDT_LOGS_NEW_VALUE: &str = "{c_crdt_logs_newValue}"; +pub const COL_CRDT_LOGS_OLD_VALUE: &str = "{c_crdt_logs_oldValue}"; + +// --- Table: haex_crdt_snapshots --- +pub const TABLE_CRDT_SNAPSHOTS: &str = "{t_crdt_snapshots}"; +pub const COL_CRDT_SNAPSHOTS_ID: &str = "{c_crdt_snap_id}"; +pub const COL_CRDT_SNAPSHOTS_CREATED: &str = "{c_crdt_snap_created}"; +pub const COL_CRDT_SNAPSHOTS_EPOCH_HLC: &str = "{c_crdt_snap_epoch}"; +pub const COL_CRDT_SNAPSHOTS_LOCATION_URL: &str = "{c_crdt_snap_location}"; +pub const COL_CRDT_SNAPSHOTS_FILE_SIZE: &str = "{c_crdt_snap_size}"; + +// --- Table: haex_crdt_configs --- +pub const TABLE_CRDT_CONFIGS: &str = "{t_crdt_configs}"; +pub const COL_CRDT_CONFIGS_KEY: &str = "{c_crdt_configs_key}"; +pub const COL_CRDT_CONFIGS_VALUE: &str = "{c_crdt_configs_value}"; +"#, + // Settings + t_settings = haex.settings.name, + c_settings_id = haex.settings.columns["id"], + c_settings_key = haex.settings.columns["key"], + c_settings_type = haex.settings.columns["type"], + c_settings_value = haex.settings.columns["value"], + c_settings_tombstone = haex.settings.columns["haexTombstone"], + c_settings_timestamp = haex.settings.columns["haexTimestamp"], + // Extensions + t_extensions = haex.extensions.name, + c_ext_id = haex.extensions.columns["id"], + c_ext_author = haex.extensions.columns["author"], + c_ext_description = haex.extensions.columns["description"], + c_ext_entry = haex.extensions.columns["entry"], + c_ext_homepage = haex.extensions.columns["homepage"], + c_ext_enabled = haex.extensions.columns["enabled"], + c_ext_icon = haex.extensions.columns["icon"], + c_ext_name = haex.extensions.columns["name"], + c_ext_public_key = haex.extensions.columns["public_key"], + c_ext_signature = haex.extensions.columns["signature"], + c_ext_url = haex.extensions.columns["url"], + c_ext_version = haex.extensions.columns["version"], + c_ext_tombstone = haex.extensions.columns["haexTombstone"], + c_ext_timestamp = haex.extensions.columns["haexTimestamp"], + // Extension Permissions + t_ext_perms = haex.extension_permissions.name, + c_extp_id = haex.extension_permissions.columns["id"], + c_extp_extensionId = haex.extension_permissions.columns["extensionId"], + c_extp_resourceType = haex.extension_permissions.columns["resourceType"], + c_extp_action = haex.extension_permissions.columns["action"], + c_extp_target = haex.extension_permissions.columns["target"], + c_extp_constraints = haex.extension_permissions.columns["constraints"], + c_extp_status = haex.extension_permissions.columns["status"], + c_extp_createdAt = haex.extension_permissions.columns["createdAt"], + c_extp_updateAt = haex.extension_permissions.columns["updateAt"], + c_extp_tombstone = haex.extension_permissions.columns["haexTombstone"], + c_extp_timestamp = haex.extension_permissions.columns["haexTimestamp"], + // Notifications + t_notifications = haex.notifications.name, + c_notif_id = haex.notifications.columns["id"], + c_notif_alt = haex.notifications.columns["alt"], + c_notif_date = haex.notifications.columns["date"], + c_notif_icon = haex.notifications.columns["icon"], + c_notif_image = haex.notifications.columns["image"], + c_notif_read = haex.notifications.columns["read"], + c_notif_source = haex.notifications.columns["source"], + c_notif_text = haex.notifications.columns["text"], + c_notif_title = haex.notifications.columns["title"], + c_notif_type = haex.notifications.columns["type"], + c_notif_tombstone = haex.notifications.columns["haexTombstone"], + // CRDT Logs + t_crdt_logs = haex.crdt.logs.name, + c_crdt_logs_id = haex.crdt.logs.columns["id"], + c_crdt_logs_timestamp = haex.crdt.logs.columns["haexTimestamp"], + c_crdt_logs_tableName = haex.crdt.logs.columns["tableName"], + c_crdt_logs_rowPks = haex.crdt.logs.columns["rowPks"], + c_crdt_logs_opType = haex.crdt.logs.columns["opType"], + c_crdt_logs_columnName = haex.crdt.logs.columns["columnName"], + c_crdt_logs_newValue = haex.crdt.logs.columns["newValue"], + c_crdt_logs_oldValue = haex.crdt.logs.columns["oldValue"], + // CRDT Snapshots + t_crdt_snapshots = haex.crdt.snapshots.name, + c_crdt_snap_id = haex.crdt.snapshots.columns["snapshotId"], + c_crdt_snap_created = haex.crdt.snapshots.columns["created"], + c_crdt_snap_epoch = haex.crdt.snapshots.columns["epochHlc"], + c_crdt_snap_location = haex.crdt.snapshots.columns["locationUrl"], + c_crdt_snap_size = haex.crdt.snapshots.columns["fileSizeBytes"], + // CRDT Configs + t_crdt_configs = haex.crdt.configs.name, + c_crdt_configs_key = haex.crdt.configs.columns["key"], + c_crdt_configs_value = haex.crdt.configs.columns["value"] + ); + + // --- Datei schreiben --- + let mut f = File::create(&dest_path).expect("Konnte Zieldatei nicht erstellen"); + f.write_all(code.as_bytes()) + .expect("Konnte nicht in Zieldatei schreiben"); + + println!("cargo:rerun-if-changed=database/tableNames.json"); +} diff --git a/src-tauri/src/build/table_names.rs b/src-tauri/src/build/table_names.rs deleted file mode 100644 index b92090a..0000000 --- a/src-tauri/src/build/table_names.rs +++ /dev/null @@ -1,64 +0,0 @@ -// build/table_names.rs -use serde::Deserialize; -use std::fs::File; -use std::io::{BufReader, Write}; -use std::path::Path; - -#[derive(Debug, Deserialize)] -pub struct Schema { - pub haex: Haex, -} - -#[derive(Debug, Deserialize)] -pub struct Haex { - pub settings: String, - pub extensions: String, - pub extension_permissions: String, - pub crdt: Crdt, -} - -#[derive(Debug, Deserialize)] -pub struct Crdt { - pub logs: String, - pub snapshots: String, - pub configs: String, -} - -pub fn generate_table_names(out_dir: &str) { - let schema_path = Path::new("database/tableNames.json"); - let dest_path = Path::new(out_dir).join("tableNames.rs"); - - let file = File::open(&schema_path).expect("Konnte tableNames.json nicht öffnen"); - let reader = BufReader::new(file); - let schema: Schema = - serde_json::from_reader(reader).expect("Konnte tableNames.json nicht parsen"); - let haex = schema.haex; - - let code = format!( - r#" - // Auto-generated - DO NOT EDIT - - // Core Tables - pub const TABLE_SETTINGS: &str = "{settings}"; - pub const TABLE_EXTENSIONS: &str = "{extensions}"; - pub const TABLE_EXTENSION_PERMISSIONS: &str = "{extension_permissions}"; - - // CRDT Tables - pub const TABLE_CRDT_LOGS: &str = "{crdt_logs}"; - pub const TABLE_CRDT_SNAPSHOTS: &str = "{crdt_snapshots}"; - pub const TABLE_CRDT_CONFIGS: &str = "{crdt_configs}"; - "#, - settings = haex.settings, - extensions = haex.extensions, - extension_permissions = haex.extension_permissions, - crdt_logs = haex.crdt.logs, - crdt_snapshots = haex.crdt.snapshots, - crdt_configs = haex.crdt.configs - ); - - let mut f = File::create(&dest_path).expect("Konnte Zieldatei nicht erstellen"); - f.write_all(code.as_bytes()) - .expect("Konnte nicht in Zieldatei schreiben"); - - println!("cargo:rerun-if-changed=database/tableNames.json"); -} diff --git a/src-tauri/src/crdt/trigger.rs b/src-tauri/src/crdt/trigger.rs index cc83f46..73e0058 100644 --- a/src-tauri/src/crdt/trigger.rs +++ b/src-tauri/src/crdt/trigger.rs @@ -259,9 +259,10 @@ fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String] let column_inserts = if cols.is_empty() { // Nur PKs -> einfacher Insert ins Log format!( - "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks) - VALUES (hlc_new_timestamp(), 'INSERT', '{table}', json_object({pk_payload}));", + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks) + VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}));", log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload ) @@ -269,9 +270,10 @@ fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String] cols.iter().fold(String::new(), |mut acc, col| { writeln!( &mut acc, - "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks, column_name, new_value) - VALUES (hlc_new_timestamp(), 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));", + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks, column_name, new_value) + VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));", log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload, column = col @@ -312,11 +314,12 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] for col in cols { writeln!( &mut body, - "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks, column_name, new_value, old_value) - SELECT hlc_new_timestamp(), 'UPDATE', '{table}', json_object({pk_payload}), '{column}', + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks, column_name, new_value, old_value) + SELECT NEW.\"{hlc_col}\", 'UPDATE', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\") WHERE NEW.\"{column}\" IS NOT OLD.\"{column}\";", log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload, column = col @@ -327,10 +330,11 @@ fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String] // Soft-delete loggen writeln!( &mut body, - "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pks) - SELECT hlc_new_timestamp(), 'DELETE', '{table}', json_object({pk_payload}) + "INSERT INTO {log_table} (haex_timestamp, op_type, table_name, row_pks) + SELECT NEW.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload}) WHERE NEW.\"{tombstone_col}\" = 1 AND OLD.\"{tombstone_col}\" = 0;", log_table = TABLE_CRDT_LOGS, + hlc_col = HLC_TIMESTAMP_COLUMN, table = table_name, pk_payload = pk_json_payload, tombstone_col = TOMBSTONE_COLUMN diff --git a/src-tauri/src/database/generated.rs b/src-tauri/src/database/generated.rs index d9bb9f3..fd852aa 100644 --- a/src-tauri/src/database/generated.rs +++ b/src-tauri/src/database/generated.rs @@ -16,7 +16,7 @@ pub struct HaexSettings { #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub haex_tombstone: Option, + pub haex_tombstone: Option, #[serde(skip_serializing_if = "Option::is_none")] pub haex_timestamp: Option, } @@ -47,7 +47,7 @@ pub struct HaexExtensions { #[serde(skip_serializing_if = "Option::is_none")] pub homepage: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, + pub enabled: Option, #[serde(skip_serializing_if = "Option::is_none")] pub icon: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -61,7 +61,7 @@ pub struct HaexExtensions { #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub haex_tombstone: Option, + pub haex_tombstone: Option, #[serde(skip_serializing_if = "Option::is_none")] pub haex_timestamp: Option, } @@ -107,7 +107,7 @@ pub struct HaexExtensionPermissions { #[serde(skip_serializing_if = "Option::is_none")] pub updated_at: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub haex_tombstone: Option, + pub haex_tombstone: Option, #[serde(skip_serializing_if = "Option::is_none")] pub haex_timestamp: Option, } diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index f44ad3a..6db7b47 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -2,7 +2,12 @@ pub mod core; pub mod error; +pub mod generated; +use crate::crdt::hlc::HlcService; +use crate::database::error::DatabaseError; +use crate::table_names::TABLE_CRDT_CONFIGS; +use crate::AppState; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -13,13 +18,8 @@ use std::time::UNIX_EPOCH; use std::{fs, sync::Arc}; use tauri::{path::BaseDirectory, AppHandle, Manager, State}; use tauri_plugin_fs::FsExt; -use thiserror::Error; use ts_rs::TS; -use crate::crdt::hlc::HlcService; -use crate::database::error::DatabaseError; -use crate::table_names::TABLE_CRDT_CONFIGS; -use crate::AppState; pub struct DbConnection(pub Arc>>); const VAULT_EXTENSION: &str = ".db"; diff --git a/src-tauri/src/extension/database/executor.rs b/src-tauri/src/extension/database/executor.rs index 207887c..3b6d994 100644 --- a/src-tauri/src/extension/database/executor.rs +++ b/src-tauri/src/extension/database/executor.rs @@ -5,7 +5,7 @@ use crate::crdt::transformer::CrdtTransformer; use crate::crdt::trigger; use crate::database::core::{parse_sql_statements, ValueConverter}; use crate::database::error::DatabaseError; -use rusqlite::{params_from_iter, Transaction}; +use rusqlite::{params_from_iter, Params, Transaction}; use serde_json::Value as JsonValue; use sqlparser::ast::Statement; use std::collections::HashSet; @@ -14,6 +14,62 @@ use std::collections::HashSet; pub struct SqlExecutor; impl SqlExecutor { + pub fn execute_internal_typed

( + tx: &Transaction, + hlc_service: &HlcService, + sql: &str, + params: P, // Akzeptiert jetzt alles, was rusqlite als Parameter versteht + ) -> Result, DatabaseError> + where + P: Params, + { + let mut ast_vec = parse_sql_statements(sql)?; + + // Wir stellen sicher, dass wir nur EIN Statement verarbeiten. Das ist sicherer. + if ast_vec.len() != 1 { + return Err(DatabaseError::ExecutionError { + sql: sql.to_string(), + reason: "execute_internal_typed sollte nur ein einzelnes SQL-Statement erhalten" + .to_string(), + table: None, + }); + } + // Wir nehmen das einzige Statement aus dem Vektor. + let mut statement = ast_vec.pop().unwrap(); + + let transformer = CrdtTransformer::new(); + let hlc_timestamp = + hlc_service + .new_timestamp_and_persist(tx) + .map_err(|e| DatabaseError::HlcError { + reason: e.to_string(), + })?; + + let mut modified_schema_tables = HashSet::new(); + if let Some(table_name) = + transformer.transform_execute_statement(&mut statement, &hlc_timestamp)? + { + modified_schema_tables.insert(table_name); + } + + // Führe das transformierte Statement aus. + // `params` wird jetzt nur noch einmal hierher bewegt, was korrekt ist. + let sql_str = statement.to_string(); + tx.execute(&sql_str, params) + .map_err(|e| DatabaseError::ExecutionError { + sql: sql_str.clone(), + table: None, + reason: e.to_string(), + })?; + + // Die Trigger-Logik für CREATE TABLE bleibt erhalten. + if let Statement::CreateTable(create_table_details) = statement { + let table_name_str = create_table_details.name.to_string(); + trigger::setup_triggers_for_table(tx, &table_name_str, false)?; + } + + Ok(modified_schema_tables) + } /// Führt SQL aus (mit CRDT-Transformation) - OHNE Permission-Check pub fn execute_internal( tx: &Transaction, diff --git a/src-tauri/src/extension/permissions/manager.rs b/src-tauri/src/extension/permissions/manager.rs index 031b063..06b1aba 100644 --- a/src-tauri/src/extension/permissions/manager.rs +++ b/src-tauri/src/extension/permissions/manager.rs @@ -1,14 +1,17 @@ +use crate::table_names::TABLE_EXTENSION_PERMISSIONS; use crate::AppState; use crate::database::core::with_connection; use crate::database::error::DatabaseError; use crate::extension::database::executor::SqlExecutor; use crate::extension::error::ExtensionError; -use crate::extension::permissions::types::{Action, DbConstraints, ExtensionPermission, FsConstraints, HttpConstraints, PermissionConstraints, PermissionStatus, ResourceType, ShellConstraints}; +use crate::extension::permissions::types::{parse_constraints, Action, DbConstraints, ExtensionPermission, FsConstraints, HttpConstraints, PermissionConstraints, PermissionStatus, ResourceType, ShellConstraints}; use serde_json; use serde_json::json; use std::path::Path; use tauri::State; use url::Url; +use crate::database::generated::HaexExtensionPermissions; +use rusqlite::{params, ToSql}; pub struct PermissionManager; @@ -29,31 +32,29 @@ impl PermissionManager { reason: "Failed to lock HLC service".to_string(), })?; + let sql = format!( + "INSERT INTO {} (id, extension_id, resource_type, action, target, constraints, status) VALUES (?, ?, ?, ?, ?, ?, ?)", + TABLE_EXTENSION_PERMISSIONS + ); + for perm in permissions { - let resource_type_str = format!("{:?}", perm.resource_type).to_lowercase(); - let action_str = format!("{:?}", perm.action).to_lowercase(); + // 1. Konvertiere App-Struct zu DB-Struct + let db_perm: HaexExtensionPermissions = perm.into(); - let constraints_json = perm - .constraints - .as_ref() - .map(|c| serde_json::to_string(c).ok()) - .flatten(); - - let sql = "INSERT INTO haex_extension_permissions - (id, extension_id, resource_type, action, target, constraints, status) - VALUES (?, ?, ?, ?, ?, ?, ?)"; - - let params = vec![ - json!(perm.id), - json!(extension_id), - json!(resource_type_str), - json!(action_str), - json!(perm.target), - json!(constraints_json), - json!(perm.status.as_str()), + // 2. Erstelle typsichere Parameter + let params = params![ + db_perm.id, + db_perm.extension_id, + db_perm.resource_type, + db_perm.action, + db_perm.target, + db_perm.constraints, + db_perm.status, ]; - SqlExecutor::execute_internal(&tx, &hlc_service, sql, ¶ms)?; + // 3. Führe mit dem typsicheren Executor aus + // HINWEIS: Du musst eine `execute_internal_typed` Funktion erstellen! + SqlExecutor::execute_internal_typed(&tx, &hlc_service, &sql, params)?; } tx.commit().map_err(DatabaseError::from)?; @@ -77,32 +78,24 @@ impl PermissionManager { reason: "Failed to lock HLC service".to_string(), })?; - let resource_type_str = format!("{:?}", permission.resource_type).to_lowercase(); - let action_str = format!("{:?}", permission.action).to_lowercase(); + let db_perm: HaexExtensionPermissions = permission.into(); + + let sql = format!( + "UPDATE {} SET resource_type = ?, action = ?, target = ?, constraints = ?, status = ? WHERE id = ?", + TABLE_EXTENSION_PERMISSIONS + ); - let constraints_json = permission - .constraints - .as_ref() - .map(|c| serde_json::to_string(c).ok()) - .flatten(); - - let sql = "UPDATE haex_extension_permissions - SET resource_type = ?, action = ?, target = ?, constraints = ?, status = ? - WHERE id = ?"; - - let params = vec![ - json!(resource_type_str), - json!(action_str), - json!(permission.target), - json!(constraints_json), - json!(permission.status.as_str()), - json!(permission.id), + let params = params![ + db_perm.resource_type, + db_perm.action, + db_perm.target, + db_perm.constraints, + db_perm.status, + db_perm.id, ]; - SqlExecutor::execute_internal(&tx, &hlc_service, sql, ¶ms)?; - - tx.commit().map_err(DatabaseError::from)?; - Ok(()) + SqlExecutor::execute_internal_typed(&tx, &hlc_service, &sql, params)?; + tx.commit().map_err(DatabaseError::from) }) .map_err(ExtensionError::from) } @@ -123,16 +116,10 @@ impl PermissionManager { reason: "Failed to lock HLC service".to_string(), })?; - let sql = "UPDATE haex_extension_permissions - SET status = ? - WHERE id = ?"; - - let params = vec![json!(new_status.as_str()), json!(permission_id)]; - - SqlExecutor::execute_internal(&tx, &hlc_service, sql, ¶ms)?; - - tx.commit().map_err(DatabaseError::from)?; - Ok(()) + let sql = format!("UPDATE {} SET status = ? WHERE id = ?", TABLE_EXTENSION_PERMISSIONS); + let params = params![new_status.as_str(), permission_id]; + SqlExecutor::execute_internal_typed(&tx, &hlc_service, &sql, params)?; + tx.commit().map_err(DatabaseError::from) }) .map_err(ExtensionError::from) } @@ -151,14 +138,9 @@ impl PermissionManager { })?; // Echtes DELETE - wird vom CrdtTransformer zu UPDATE umgewandelt - let sql = "DELETE FROM haex_extension_permissions WHERE id = ?"; - - let params = vec![json!(permission_id)]; - - SqlExecutor::execute_internal(&tx, &hlc_service, sql, ¶ms)?; - - tx.commit().map_err(DatabaseError::from)?; - Ok(()) + let sql = format!("DELETE FROM {} WHERE id = ?", TABLE_EXTENSION_PERMISSIONS); + SqlExecutor::execute_internal_typed(&tx, &hlc_service, &sql, params![permission_id])?; + tx.commit().map_err(DatabaseError::from) }).map_err(ExtensionError::from) } @@ -175,15 +157,9 @@ impl PermissionManager { reason: "Failed to lock HLC service".to_string(), })?; - // Echtes DELETE - wird vom CrdtTransformer zu UPDATE umgewandelt - let sql = "DELETE FROM haex_extension_permissions WHERE extension_id = ?"; - - let params = vec![json!(extension_id)]; - - SqlExecutor::execute_internal(&tx, &hlc_service, sql, ¶ms)?; - - tx.commit().map_err(DatabaseError::from)?; - Ok(()) + let sql = format!("DELETE FROM {} WHERE extension_id = ?", TABLE_EXTENSION_PERMISSIONS); + SqlExecutor::execute_internal_typed(&tx, &hlc_service, &sql, params![extension_id])?; + tx.commit().map_err(DatabaseError::from) }).map_err(ExtensionError::from) } /// Lädt alle Permissions einer Extension @@ -192,92 +168,23 @@ impl PermissionManager { extension_id: &str, ) -> Result, ExtensionError> { with_connection(&app_state.db, |conn| { - let sql = "SELECT id, extension_id, resource_type, action, target, constraints, status, haex_timestamp, haex_tombstone - FROM haex_extension_permissions - WHERE extension_id = ?"; + let sql = format!("SELECT * FROM {} WHERE extension_id = ?", TABLE_EXTENSION_PERMISSIONS); + let mut stmt = conn.prepare(&sql).map_err(DatabaseError::from)?; - let params = vec![json!(extension_id)]; + let perms_iter = stmt.query_map(params![extension_id], |row| { + HaexExtensionPermissions::from_row(row) + })?; - // SELECT nutzt select_internal - let results = SqlExecutor::select_internal(conn, sql, ¶ms)?; - - // Parse JSON results zu ExtensionPermission - let permissions = results - .into_iter() - .map(|row| Self::parse_permission_from_json(row)) - .collect::, _>>()?; + let permissions = perms_iter + .filter_map(Result::ok) + .map(Into::into) + .collect(); Ok(permissions) }).map_err(ExtensionError::from) } - // Helper für JSON -> ExtensionPermission Konvertierung - fn parse_permission_from_json(json: serde_json::Value) -> Result { - - let obj = json.as_object().ok_or_else(|| DatabaseError::SerializationError { - reason: "Expected JSON object".to_string(), - })?; - - let resource_type = Self::parse_resource_type( - obj.get("resource_type") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing resource_type".to_string(), - })? - )?; - - let action = Self::parse_action( - obj.get("action") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing action".to_string(), - })? - )?; - - let status = PermissionStatus::from_str( - obj.get("status") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing status".to_string(), - })? - )?; // Jetzt funktioniert das ? - - let constraints = obj.get("constraints") - .and_then(|v| v.as_str()) - .map(|json_str| Self::parse_constraints(&resource_type, json_str)) - .transpose()?; - - Ok(ExtensionPermission { - id: obj.get("id") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing id".to_string(), - })? - .to_string(), - extension_id: obj.get("extension_id") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing extension_id".to_string(), - })? - .to_string(), - resource_type, - action, - target: obj.get("target") - .and_then(|v| v.as_str()) - .ok_or_else(|| DatabaseError::SerializationError { - reason: "Missing target".to_string(), - })? - .to_string(), - constraints, - status, - haex_timestamp: obj.get("haex_timestamp") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()), - haex_tombstone: obj.get("haex_tombstone") - .and_then(|v| v.as_i64()) - .map(|i| i == 1), - }) - } + /// Prüft Datenbankberechtigungen pub async fn check_database_permission( @@ -311,7 +218,7 @@ impl PermissionManager { Ok(()) } - /// Prüft Dateisystem-Berechtigungen +/* /// Prüft Dateisystem-Berechtigungen pub async fn check_filesystem_permission( app_state: &State<'_, AppState>, extension_id: &str, @@ -471,9 +378,9 @@ impl PermissionManager { Ok(()) } - + */ // Helper-Methoden - müssen DatabaseError statt ExtensionError zurückgeben - fn parse_resource_type(s: &str) -> Result { + pub fn parse_resource_type(s: &str) -> Result { match s { "fs" => Ok(ResourceType::Fs), "http" => Ok(ResourceType::Http), @@ -485,52 +392,8 @@ impl PermissionManager { } } - fn parse_action(s: &str) -> Result { - match s { - "read" => Ok(Action::Read), - "write" => Ok(Action::Write), - _ => Err(DatabaseError::SerializationError { - reason: format!("Unknown action: {}", s), - }), - } - } - - fn parse_constraints( - resource_type: &ResourceType, - json: &str, - ) -> Result { - match resource_type { - ResourceType::Db => { - let constraints: DbConstraints = serde_json::from_str(json) - .map_err(|e| DatabaseError::SerializationError { - reason: format!("Failed to parse DB constraints: {}", e), - })?; - Ok(PermissionConstraints::Database(constraints)) - } - ResourceType::Fs => { - let constraints: FsConstraints = serde_json::from_str(json) - .map_err(|e| DatabaseError::SerializationError { - reason: format!("Failed to parse FS constraints: {}", e), - })?; - Ok(PermissionConstraints::Filesystem(constraints)) - } - ResourceType::Http => { - let constraints: HttpConstraints = serde_json::from_str(json) - .map_err(|e| DatabaseError::SerializationError { - reason: format!("Failed to parse HTTP constraints: {}", e), - })?; - Ok(PermissionConstraints::Http(constraints)) - } - ResourceType::Shell => { - let constraints: ShellConstraints = serde_json::from_str(json) - .map_err(|e| DatabaseError::SerializationError { - reason: format!("Failed to parse Shell constraints: {}", e), - })?; - Ok(PermissionConstraints::Shell(constraints)) - } - } - } - + + fn matches_path_pattern(pattern: &str, path: &str) -> bool { if pattern.ends_with("/*") { let prefix = &pattern[..pattern.len() - 2]; @@ -557,7 +420,7 @@ impl PermissionManager { } // Convenience-Funktionen für die verschiedenen Subsysteme -impl PermissionManager { +/* impl PermissionManager { // Convenience-Methoden pub async fn can_read_file( app_state: &State<'_, AppState>, @@ -647,4 +510,4 @@ impl PermissionManager { .filter(|perm| perm.status == PermissionStatus::Ask) .collect()) } -} +} */ diff --git a/src-tauri/src/extension/permissions/types.rs b/src-tauri/src/extension/permissions/types.rs index c4104c4..debd354 100644 --- a/src-tauri/src/extension/permissions/types.rs +++ b/src-tauri/src/extension/permissions/types.rs @@ -1,6 +1,12 @@ -use serde::{Deserialize, Serialize}; +// src-tauri/src/extension/permissions/types.rs -use crate::database::error::DatabaseError; +use std::str::FromStr; + +use crate::{ + database::{error::DatabaseError, generated::HaexExtensionPermissions}, + extension::permissions::manager::PermissionManager, +}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ExtensionPermission { @@ -20,6 +26,52 @@ pub struct ExtensionPermission { pub haex_timestamp: Option, } +impl From for ExtensionPermission { + fn from(db_perm: HaexExtensionPermissions) -> Self { + let resource_type = ResourceType::from_str(&db_perm.resource_type.unwrap_or_default()) + .unwrap_or(ResourceType::Db); // Fallback + + let constraints = db_perm + .constraints + .and_then(|json_str| parse_constraints(&resource_type, &json_str).ok()); + + ExtensionPermission { + id: db_perm.id, + extension_id: db_perm.extension_id.unwrap_or_default(), + resource_type, + action: Action::from_str(&db_perm.action.unwrap_or_default()).unwrap_or(Action::Read), + target: db_perm.target.unwrap_or_default(), + status: PermissionStatus::from_str(&db_perm.status).unwrap_or(PermissionStatus::Ask), + constraints, + haex_timestamp: db_perm.haex_timestamp, + haex_tombstone: db_perm.haex_tombstone, + } + } +} + +impl From<&ExtensionPermission> for HaexExtensionPermissions { + fn from(perm: &ExtensionPermission) -> Self { + let constraints_json = perm + .constraints + .as_ref() + .and_then(|c| serde_json::to_string(c).ok()); + + HaexExtensionPermissions { + id: perm.id.clone(), + extension_id: Some(perm.extension_id.clone()), + resource_type: Some(format!("{:?}", perm.resource_type).to_lowercase()), + action: Some(format!("{:?}", perm.action).to_lowercase()), + target: Some(perm.target.clone()), + constraints: constraints_json, + status: perm.status.as_str().to_string(), + created_at: None, // Wird von der DB gesetzt + updated_at: None, // Wird von der DB gesetzt + haex_timestamp: perm.haex_timestamp.clone(), + haex_tombstone: perm.haex_tombstone, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "lowercase")] pub enum ResourceType { @@ -29,13 +81,43 @@ pub enum ResourceType { Shell, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +impl FromStr for ResourceType { + type Err = DatabaseError; + + fn from_str(s: &str) -> Result { + match s { + "fs" => Ok(ResourceType::Fs), + "http" => Ok(ResourceType::Http), + "db" => Ok(ResourceType::Db), + "shell" => Ok(ResourceType::Shell), + _ => Err(DatabaseError::SerializationError { + reason: format!("Unbekannter Ressourcentyp: {}", s), + }), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Action { Read, Write, } +impl FromStr for Action { + type Err = DatabaseError; + + fn from_str(s: &str) -> Result { + match s { + "read" => Ok(Action::Read), + "write" => Ok(Action::Write), + _ => Err(DatabaseError::SerializationError { + reason: format!("Unbekannte Aktion: {}", s), + }), + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "lowercase")] pub enum PermissionStatus { @@ -125,7 +207,7 @@ pub struct EditablePermissions { } // Oder gruppiert nach Typ: -impl EditablePermissions { +/* impl EditablePermissions { pub fn database_permissions(&self) -> Vec<&ExtensionPermission> { self.permissions .iter() @@ -153,4 +235,40 @@ impl EditablePermissions { .filter(|p| p.resource_type == ResourceType::Shell) .collect() } +} */ + +pub fn parse_constraints( + resource_type: &ResourceType, + json: &str, +) -> Result { + match resource_type { + ResourceType::Db => { + let constraints: DbConstraints = + serde_json::from_str(json).map_err(|e| DatabaseError::SerializationError { + reason: format!("Failed to parse DB constraints: {}", e), + })?; + Ok(PermissionConstraints::Database(constraints)) + } + ResourceType::Fs => { + let constraints: FsConstraints = + serde_json::from_str(json).map_err(|e| DatabaseError::SerializationError { + reason: format!("Failed to parse FS constraints: {}", e), + })?; + Ok(PermissionConstraints::Filesystem(constraints)) + } + ResourceType::Http => { + let constraints: HttpConstraints = + serde_json::from_str(json).map_err(|e| DatabaseError::SerializationError { + reason: format!("Failed to parse HTTP constraints: {}", e), + })?; + Ok(PermissionConstraints::Http(constraints)) + } + ResourceType::Shell => { + let constraints: ShellConstraints = + serde_json::from_str(json).map_err(|e| DatabaseError::SerializationError { + reason: format!("Failed to parse Shell constraints: {}", e), + })?; + Ok(PermissionConstraints::Shell(constraints)) + } + } }