add more typesafty

This commit is contained in:
2025-10-02 17:18:28 +02:00
parent fc841f238b
commit 225835e5d1
20 changed files with 1600 additions and 465 deletions

View File

@ -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",

View File

@ -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();
}

View File

@ -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<u8>'
@ -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) {

View File

@ -0,0 +1 @@
ALTER TABLE `haex_notifications` ADD `haex_timestamp` text;

View File

@ -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": {}
}
}

View File

@ -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
}
]
}

View File

@ -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()),

View File

@ -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

View File

@ -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,
{

View File

@ -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"
}
}
}
}
}

View File

@ -1,3 +1,3 @@
// src-tauri/src/build/mod.rs
// build/mod.rs
pub mod rust_types;
pub mod table_names;

View File

@ -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<String, String>,
}
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");
}

View File

@ -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");
}

View File

@ -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

View File

@ -16,7 +16,7 @@ pub struct HaexSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_tombstone: Option<String>,
pub haex_tombstone: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_timestamp: Option<String>,
}
@ -47,7 +47,7 @@ pub struct HaexExtensions {
#[serde(skip_serializing_if = "Option::is_none")]
pub homepage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<String>,
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -61,7 +61,7 @@ pub struct HaexExtensions {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_tombstone: Option<String>,
pub haex_tombstone: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_timestamp: Option<String>,
}
@ -107,7 +107,7 @@ pub struct HaexExtensionPermissions {
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_tombstone: Option<String>,
pub haex_tombstone: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub haex_timestamp: Option<String>,
}

View File

@ -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<Mutex<Option<Connection>>>);
const VAULT_EXTENSION: &str = ".db";

View File

@ -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<P>(
tx: &Transaction,
hlc_service: &HlcService,
sql: &str,
params: P, // Akzeptiert jetzt alles, was rusqlite als Parameter versteht
) -> Result<HashSet<String>, 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,

View File

@ -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, &params)?;
// 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, &params)?;
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, &params)?;
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, &params)?;
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, &params)?;
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<Vec<ExtensionPermission>, 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, &params)?;
// Parse JSON results zu ExtensionPermission
let permissions = results
.into_iter()
.map(|row| Self::parse_permission_from_json(row))
.collect::<Result<Vec<_>, _>>()?;
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<ExtensionPermission, DatabaseError> {
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<ResourceType, DatabaseError> {
pub fn parse_resource_type(s: &str) -> Result<ResourceType, DatabaseError> {
match s {
"fs" => Ok(ResourceType::Fs),
"http" => Ok(ResourceType::Http),
@ -485,52 +392,8 @@ impl PermissionManager {
}
}
fn parse_action(s: &str) -> Result<Action, DatabaseError> {
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<PermissionConstraints, DatabaseError> {
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())
}
}
} */

View File

@ -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<String>,
}
impl From<HaexExtensionPermissions> 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<Self, Self::Err> {
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<Self, Self::Err> {
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<PermissionConstraints, DatabaseError> {
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))
}
}
}