add desktop

This commit is contained in:
2025-10-16 20:56:21 +02:00
parent 033c9135c6
commit a291619f63
27 changed files with 1755 additions and 124 deletions

9
.claude-session.json Normal file
View File

@ -0,0 +1,9 @@
{
"lastUpdated": "2025-10-16T00:00:00.000Z",
"todos": [],
"context": {
"description": "Session context file for Claude Code. This file is automatically updated to persist state across sessions.",
"currentFocus": null,
"notes": []
}
}

View File

@ -1,5 +1,5 @@
{
"name": "tauri-app",
"name": "haex-hub",
"private": true,
"version": "0.1.0",
"type": "module",

View File

@ -0,0 +1,9 @@
CREATE TABLE `haex_desktop_items` (
`id` text PRIMARY KEY NOT NULL,
`item_type` text NOT NULL,
`reference_id` text NOT NULL,
`position_x` integer DEFAULT 0 NOT NULL,
`position_y` integer DEFAULT 0 NOT NULL,
`haex_tombstone` integer,
`haex_timestamp` text
);

View File

@ -0,0 +1,991 @@
{
"version": "6",
"dialect": "sqlite",
"id": "2f40a42e-9b3f-42be-8951-8e94baadcd65",
"prevId": "5387568f-75b3-4a85-86c5-67f539c3fedf",
"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_desktop_items": {
"name": "haex_desktop_items",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"item_type": {
"name": "item_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reference_id": {
"name": "reference_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"position_x": {
"name": "position_x",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"position_y": {
"name": "position_y",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"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_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
},
"public_key": {
"name": "public_key",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"version": {
"name": "version",
"type": "text",
"primaryKey": false,
"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": true,
"autoincrement": false,
"default": "'index.html'"
},
"homepage": {
"name": "homepage",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": true
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"signature": {
"name": "signature",
"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": {
"haex_extensions_public_key_name_unique": {
"name": "haex_extensions_public_key_name_unique",
"columns": [
"public_key",
"name"
],
"isUnique": true
}
},
"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

@ -22,6 +22,13 @@
"when": 1760272083150,
"tag": "0002_amazing_iron_fist",
"breakpoints": true
},
{
"idx": 3,
"version": "6",
"when": 1760611690801,
"tag": "0003_daily_polaris",
"breakpoints": true
}
]
}

View File

@ -5,9 +5,20 @@ import {
text,
unique,
type AnySQLiteColumn,
type SQLiteColumnBuilderBase,
} from 'drizzle-orm/sqlite-core'
import tableNames from '../tableNames.json'
// Helper function to add common CRDT columns (haexTombstone and haexTimestamp)
export const withCrdtColumns = <T extends Record<string, SQLiteColumnBuilderBase>>(
columns: T,
columnNames: { haexTombstone: string; haexTimestamp: string },
) => ({
...columns,
haexTombstone: integer(columnNames.haexTombstone, { mode: 'boolean' }),
haexTimestamp: text(columnNames.haexTimestamp),
})
export const haexSettings = sqliteTable(tableNames.haex.settings.name, {
id: text()
.primaryKey()
@ -120,3 +131,27 @@ export const haexNotifications = sqliteTable(
)
export type InsertHaexNotifications = typeof haexNotifications.$inferInsert
export type SelectHaexNotifications = typeof haexNotifications.$inferSelect
export const haexDesktopItems = sqliteTable(
tableNames.haex.desktop_items.name,
withCrdtColumns(
{
id: text(tableNames.haex.desktop_items.columns.id)
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
itemType: text(tableNames.haex.desktop_items.columns.itemType, {
enum: ['extension', 'file', 'folder'],
}).notNull(),
referenceId: text(tableNames.haex.desktop_items.columns.referenceId).notNull(), // extensionId für extensions, filePath für files/folders
positionX: integer(tableNames.haex.desktop_items.columns.positionX)
.notNull()
.default(0),
positionY: integer(tableNames.haex.desktop_items.columns.positionY)
.notNull()
.default(0),
},
tableNames.haex.desktop_items.columns,
),
)
export type InsertHaexDesktopItems = typeof haexDesktopItems.$inferInsert
export type SelectHaexDesktopItems = typeof haexDesktopItems.$inferSelect

View File

@ -63,6 +63,18 @@
"haexTimestamp": "haex_timestamp"
}
},
"desktop_items": {
"name": "haex_desktop_items",
"columns": {
"id": "id",
"itemType": "item_type",
"referenceId": "reference_id",
"positionX": "position_x",
"positionY": "position_y",
"haexTombstone": "haex_tombstone",
"haexTimestamp": "haex_timestamp"
}
},
"passwords": {
"groups": "haex_passwords_groups",
"group_items": "haex_passwords_group_items",

Binary file not shown.

View File

@ -12,7 +12,6 @@ use serde_json::Value as JsonValue;
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement, TableFactor, TableObject};
use sqlparser::dialect::SQLiteDialect;
use sqlparser::parser::Parser;
use std::collections::HashMap;
/// Öffnet und initialisiert eine Datenbank mit Verschlüsselung
///
@ -121,7 +120,7 @@ pub fn execute(
sql: String,
params: Vec<JsonValue>,
connection: &DbConnection,
) -> Result<usize, DatabaseError> {
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
// Konvertiere Parameter
let params_converted: Vec<RusqliteValue> = params
.iter()
@ -130,19 +129,56 @@ pub fn execute(
let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect();
with_connection(connection, |conn| {
let affected_rows = conn.execute(&sql, &params_sql[..]).map_err(|e| {
// "Lazy Parsing": Extrahiere den Tabellennamen nur, wenn ein Fehler auftritt,
// um den Overhead bei erfolgreichen Operationen zu vermeiden.
let table_name = extract_primary_table_name_from_sql(&sql).unwrap_or(None);
// Check if the SQL contains RETURNING clause
let has_returning = sql.to_uppercase().contains("RETURNING");
DatabaseError::ExecutionError {
sql: sql.clone(),
if has_returning {
// Use prepare + query for RETURNING statements
let mut stmt = conn.prepare(&sql).map_err(|e| DatabaseError::PrepareError {
reason: e.to_string(),
table: table_name,
}
})?;
})?;
Ok(affected_rows)
let num_columns = stmt.column_count();
let mut rows = stmt
.query(&params_sql[..])
.map_err(|e| DatabaseError::QueryError {
reason: e.to_string(),
})?;
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
while let Some(row) = rows.next().map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Row iteration error: {}", e),
})? {
let mut row_values: Vec<JsonValue> = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let value_ref = row.get_ref(i).map_err(|e| {
DatabaseError::RowProcessingError {
reason: format!("Failed to get column {}: {}", i, e),
}
})?;
let json_val = convert_value_ref_to_json(value_ref)?;
row_values.push(json_val);
}
result_vec.push(row_values);
}
Ok(result_vec)
} else {
// For non-RETURNING statements, just execute and return empty array
conn.execute(&sql, &params_sql[..]).map_err(|e| {
let table_name = extract_primary_table_name_from_sql(&sql).unwrap_or(None);
DatabaseError::ExecutionError {
sql: sql.clone(),
reason: e.to_string(),
table: table_name,
}
})?;
Ok(vec![])
}
})
}
@ -150,7 +186,7 @@ pub fn select(
sql: String,
params: Vec<JsonValue>,
connection: &DbConnection,
) -> Result<Vec<HashMap<String, JsonValue>>, DatabaseError> {
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
// Validiere SQL-Statement
let statement = parse_single_statement(&sql)?;
@ -176,12 +212,7 @@ pub fn select(
reason: e.to_string(),
})?;
let column_names: Vec<String> = stmt
.column_names()
.into_iter()
.map(|s| s.to_string())
.collect();
let num_columns = column_names.len();
let num_columns = stmt.column_count();
let mut rows = stmt
.query(&params_sql[..])
@ -189,34 +220,24 @@ pub fn select(
reason: e.to_string(),
})?;
let mut result_vec: Vec<HashMap<String, JsonValue>> = Vec::new();
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
while let Some(row) = rows.next().map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Row iteration error: {}", e),
})? {
let mut row_map: HashMap<String, JsonValue> = HashMap::with_capacity(num_columns);
let mut row_values: Vec<JsonValue> = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let col_name = &column_names[i];
/* println!(
"--- Processing Column --- Index: {}, Name: '{}'",
i, col_name
); */
let value_ref = row
.get_ref(i)
.map_err(|e| DatabaseError::RowProcessingError {
reason: format!("Failed to get column {} ('{}'): {}", i, col_name, e),
reason: format!("Failed to get column {}: {}", i, e),
})?;
let json_val = convert_value_ref_to_json(value_ref)?;
//println!("Column: {} = {}", column_names[i], json_val);
row_map.insert(col_name.clone(), json_val);
row_values.push(json_val);
}
result_vec.push(row_map);
result_vec.push(row_values);
}
Ok(result_vec)

View File

@ -11,7 +11,6 @@ use crate::AppState;
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Mutex;
use std::time::UNIX_EPOCH;
@ -30,7 +29,7 @@ pub fn sql_select(
sql: String,
params: Vec<JsonValue>,
state: State<'_, AppState>,
) -> Result<Vec<HashMap<String, JsonValue>>, DatabaseError> {
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
core::select(sql, params, &state.db)
}
@ -39,7 +38,7 @@ pub fn sql_execute(
sql: String,
params: Vec<JsonValue>,
state: State<'_, AppState>,
) -> Result<usize, DatabaseError> {
) -> Result<Vec<Vec<JsonValue>>, DatabaseError> {
core::execute(sql, params, &state.db)
}

View File

@ -0,0 +1,119 @@
<template>
<UContextMenu :items="contextMenuItems">
<div
ref="draggableEl"
:style="style"
class="select-none cursor-grab active:cursor-grabbing"
@pointerdown="handlePointerDown"
@pointermove="handlePointerMove"
@pointerup="handlePointerUp"
@dblclick="handleOpen"
>
<div class="flex flex-col items-center gap-1 p-2">
<div
class="w-16 h-16 flex items-center justify-center bg-white/90 dark:bg-gray-800/90 rounded-lg shadow-lg hover:shadow-xl transition-shadow"
>
<img v-if="icon" :src="icon" :alt="label" class="w-12 h-12 object-contain" />
<Icon v-else name="i-heroicons-puzzle-piece-solid" class="w-12 h-12 text-gray-500" />
</div>
<span
class="text-xs text-center max-w-20 truncate bg-white/80 dark:bg-gray-800/80 px-2 py-1 rounded shadow"
>
{{ label }}
</span>
</div>
</div>
</UContextMenu>
</template>
<script setup lang="ts">
const props = defineProps<{
id: string
itemType: 'extension' | 'file' | 'folder'
referenceId: string
initialX: number
initialY: number
label: string
icon?: string
}>()
const emit = defineEmits<{
positionChanged: [id: string, x: number, y: number]
open: [itemType: string, referenceId: string]
uninstall: [itemType: string, referenceId: string]
dragStart: [id: string, itemType: string, referenceId: string]
dragEnd: []
}>()
const desktopStore = useDesktopStore()
const contextMenuItems = computed(() =>
desktopStore.getContextMenuItems(
props.id,
props.itemType,
props.referenceId,
handleOpen,
handleUninstall,
),
)
const draggableEl = ref<HTMLElement>()
const x = ref(props.initialX)
const y = ref(props.initialY)
const isDragging = ref(false)
const offsetX = ref(0)
const offsetY = ref(0)
const style = computed(() => ({
position: 'absolute' as const,
left: `${x.value}px`,
top: `${y.value}px`,
touchAction: 'none' as const,
}))
const handlePointerDown = (e: PointerEvent) => {
if (!draggableEl.value || !draggableEl.value.parentElement) return
isDragging.value = true
emit('dragStart', props.id, props.itemType, props.referenceId)
// Get parent offset to convert from viewport coordinates to parent-relative coordinates
const parentRect = draggableEl.value.parentElement.getBoundingClientRect()
// Calculate offset from mouse position to current element position (in parent coordinates)
offsetX.value = e.clientX - parentRect.left - x.value
offsetY.value = e.clientY - parentRect.top - y.value
draggableEl.value.setPointerCapture(e.pointerId)
}
const handlePointerMove = (e: PointerEvent) => {
if (!isDragging.value || !draggableEl.value?.parentElement) return
const parentRect = draggableEl.value.parentElement.getBoundingClientRect()
const newX = e.clientX - parentRect.left - offsetX.value
const newY = e.clientY - parentRect.top - offsetY.value
x.value = newX
y.value = newY
}
const handlePointerUp = (e: PointerEvent) => {
if (!isDragging.value) return
isDragging.value = false
if (draggableEl.value) {
draggableEl.value.releasePointerCapture(e.pointerId)
}
emit('dragEnd')
emit('positionChanged', props.id, x.value, y.value)
}
const handleOpen = () => {
emit('open', props.itemType, props.referenceId)
}
const handleUninstall = () => {
emit('uninstall', props.itemType, props.referenceId)
}
</script>

View File

@ -0,0 +1,250 @@
<template>
<div
class="w-full h-full relative overflow-hidden bg-gradient-to-br from-blue-50 to-blue-100 dark:from-gray-900 dark:to-gray-800"
>
<!-- Dropzones (only visible during drag) -->
<Transition name="slide-down">
<div
v-if="isDragging"
class="absolute top-0 left-0 right-0 flex gap-2 p-4 z-50"
>
<!-- Remove from Desktop Dropzone -->
<div
ref="removeDropzoneEl"
class="flex-1 h-20 flex items-center justify-center gap-2 rounded-lg border-2 border-dashed transition-all"
:class="
isOverRemoveZone
? 'bg-orange-500/20 border-orange-500 dark:bg-orange-400/20 dark:border-orange-400'
: 'border-orange-500/50 dark:border-orange-400/50'
"
>
<Icon
name="i-heroicons-x-mark"
class="w-6 h-6"
:class="
isOverRemoveZone
? 'text-orange-700 dark:text-orange-300'
: 'text-orange-600 dark:text-orange-400'
"
/>
<span
class="font-semibold"
:class="
isOverRemoveZone
? 'text-orange-700 dark:text-orange-300'
: 'text-orange-600 dark:text-orange-400'
"
>
Von Desktop entfernen
</span>
</div>
<!-- Uninstall Dropzone -->
<div
ref="uninstallDropzoneEl"
class="flex-1 h-20 flex items-center justify-center gap-2 rounded-lg border-2 border-dashed transition-all"
:class="
isOverUninstallZone
? 'bg-red-500/20 border-red-500 dark:bg-red-400/20 dark:border-red-400'
: 'border-red-500/50 dark:border-red-400/50'
"
>
<Icon
name="i-heroicons-trash"
class="w-6 h-6"
:class="
isOverUninstallZone
? 'text-red-700 dark:text-red-300'
: 'text-red-600 dark:text-red-400'
"
/>
<span
class="font-semibold"
:class="
isOverUninstallZone
? 'text-red-700 dark:text-red-300'
: 'text-red-600 dark:text-red-400'
"
>
Deinstallieren
</span>
</div>
</div>
</Transition>
<HaexDesktopIcon
v-for="item in desktopItemIcons"
:key="item.id"
:id="item.id"
:item-type="item.itemType"
:reference-id="item.referenceId"
:initial-x="item.positionX"
:initial-y="item.positionY"
:label="item.label"
:icon="item.icon"
@position-changed="handlePositionChanged"
@open="handleOpen"
@drag-start="handleDragStart"
@drag-end="handleDragEnd"
@uninstall="handleUninstall"
/>
</div>
</template>
<script setup lang="ts">
const desktopStore = useDesktopStore()
const extensionsStore = useExtensionsStore()
const router = useRouter()
const { desktopItems } = storeToRefs(desktopStore)
const { availableExtensions } = storeToRefs(extensionsStore)
// Drag state
const isDragging = ref(false)
const currentDraggedItemId = ref<string>()
const currentDraggedItemType = ref<string>()
const currentDraggedReferenceId = ref<string>()
// Dropzone refs
const removeDropzoneEl = ref<HTMLElement>()
const uninstallDropzoneEl = ref<HTMLElement>()
// Setup dropzones with VueUse
const { isOverDropZone: isOverRemoveZone } = useDropZone(removeDropzoneEl, {
onDrop: () => {
if (currentDraggedItemId.value) {
handleRemoveFromDesktop(currentDraggedItemId.value)
}
},
})
const { isOverDropZone: isOverUninstallZone } = useDropZone(uninstallDropzoneEl, {
onDrop: () => {
if (currentDraggedItemType.value && currentDraggedReferenceId.value) {
handleUninstall(currentDraggedItemType.value, currentDraggedReferenceId.value)
}
},
})
interface DesktopItemIcon extends IDesktopItem {
label: string
icon?: string
}
const desktopItemIcons = computed<DesktopItemIcon[]>(() => {
return desktopItems.value.map((item) => {
if (item.itemType === 'extension') {
const extension = availableExtensions.value.find(
(ext) => ext.id === item.referenceId,
)
return {
...item,
label: extension?.name || 'Unknown',
icon: extension?.icon || '',
}
}
if (item.itemType === 'file') {
// Für später: file handling
return {
...item,
label: item.referenceId,
icon: undefined,
}
}
if (item.itemType === 'folder') {
// Für später: folder handling
return {
...item,
label: item.referenceId,
icon: undefined,
}
}
return {
...item,
label: item.referenceId,
icon: undefined,
}
})
})
const handlePositionChanged = async (id: string, x: number, y: number) => {
try {
await desktopStore.updateDesktopItemPositionAsync(id, x, y)
} catch (error) {
console.error('Fehler beim Speichern der Position:', error)
}
}
const localePath = useLocalePath()
const handleOpen = (itemType: string, referenceId: string) => {
if (itemType === 'extension') {
router.push(
localePath({
name: 'extension',
params: { extensionId: referenceId },
})
)
}
// Für später: file und folder handling
}
const handleDragStart = (id: string, itemType: string, referenceId: string) => {
isDragging.value = true
currentDraggedItemId.value = id
currentDraggedItemType.value = itemType
currentDraggedReferenceId.value = referenceId
}
const handleDragEnd = () => {
isDragging.value = false
currentDraggedItemId.value = undefined
currentDraggedItemType.value = undefined
currentDraggedReferenceId.value = undefined
}
const handleUninstall = async (itemType: string, referenceId: string) => {
if (itemType === 'extension') {
try {
const extension = availableExtensions.value.find((ext) => ext.id === referenceId)
if (extension) {
await extensionsStore.removeExtensionAsync(
extension.publicKey,
extension.name,
extension.version,
)
// Reload extensions after uninstall
await extensionsStore.loadExtensionsAsync()
}
} catch (error) {
console.error('Fehler beim Deinstallieren:', error)
}
}
// Für später: file und folder handling
}
onMounted(async () => {
await desktopStore.loadDesktopItemsAsync()
})
</script>
<style scoped>
.slide-down-enter-active,
.slide-down-leave-active {
transition: all 0.3s ease;
}
.slide-down-enter-from {
opacity: 0;
transform: translateY(-100%);
}
.slide-down-leave-to {
opacity: 0;
transform: translateY(-100%);
}
</style>

View File

@ -67,6 +67,12 @@
</div>
</UCard>
<!-- Add to Desktop Option -->
<UCheckbox
v-model="addToDesktop"
:label="t('addToDesktop')"
/>
<!-- Permissions Section -->
<div class="flex flex-col gap-4">
<h4 class="text-lg font-semibold">
@ -140,6 +146,7 @@ const open = defineModel<boolean>('open', { default: false })
const preview = defineModel<ExtensionPreview | null>('preview', {
default: null,
})
const addToDesktop = ref(true)
const databasePermissions = computed({
get: () => preview.value?.editable_permissions?.database || [],
@ -217,7 +224,10 @@ const permissionAccordionItems = computed(() => {
return items
})
const emit = defineEmits(['deny', 'confirm'])
const emit = defineEmits<{
deny: []
confirm: [addToDesktop: boolean]
}>()
const onDeny = () => {
open.value = false
@ -226,7 +236,7 @@ const onDeny = () => {
const onConfirm = () => {
open.value = false
emit('confirm')
emit('confirm', addToDesktop.value)
}
</script>
@ -235,6 +245,7 @@ de:
title: Erweiterung installieren
version: Version
author: Autor
addToDesktop: Zum Desktop hinzufügen
signature:
valid: Signatur verifiziert
invalid: Signatur ungültig
@ -249,6 +260,7 @@ en:
title: Install Extension
version: Version
author: Author
addToDesktop: Add to Desktop
signature:
valid: Signature verified
invalid: Invalid signature

View File

@ -422,7 +422,7 @@ async function handleContextMethodAsync(request: ExtensionRequest) {
return {
theme: contextGetters.getTheme(),
locale: contextGetters.getLocale(),
platform: detectPlatform(),
platform: contextGetters.getPlatform(),
}
default:
@ -430,13 +430,6 @@ async function handleContextMethodAsync(request: ExtensionRequest) {
}
}
function detectPlatform(): 'desktop' | 'mobile' | 'tablet' {
const width = window.innerWidth
if (width < 768) return 'mobile'
if (width < 1024) return 'tablet'
return 'desktop'
}
// ==========================================
// Storage Methods
// ==========================================

View File

@ -1,6 +1,5 @@
<template>
<div class="">
<!-- class="" -->
<div class="flex flex-col w-full h-full overflow-hidden">
<UPageHeader
as="header"
:ui="{
@ -33,7 +32,7 @@
</template>
</UPageHeader>
<main class="overflow-scroll flex bg-elevated">
<main class="flex-1 overflow-hidden bg-elevated">
<NuxtPage />
</main>
</div>

View File

@ -1,5 +1,5 @@
<template>
<div class="bg-default isolate h-dvh">
<div class="bg-default isolate w-dvw h-dvh flex flex-col">
<slot />
</div>
</template>

View File

@ -100,7 +100,11 @@ const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
const { lastVaults } = storeToRefs(useLastVaultStore())
onMounted(async () => {
await syncLastVaultsAsync()
try {
await syncLastVaultsAsync()
} catch (error) {
console.error('ERROR: ', error)
}
})
const onSelectLocale = async (locale: Locale) => {

View File

@ -1,10 +1,5 @@
<template>
<div
:ui="{
root: ['h-full w-full bg-elevated lg:flex'],
center: ['h-full w-full'],
}"
>
<div class="w-full h-full overflow-y-auto">
<NuxtLayout name="app">
<NuxtPage />
</NuxtLayout>
@ -57,14 +52,18 @@ const { setDeviceIdIfNotExistsAsync, addDeviceNameAsync } = useDeviceStore()
const { deviceId } = storeToRefs(useDeviceStore())
onMounted(async () => {
await setDeviceIdIfNotExistsAsync()
await loadExtensionsAsync()
await readNotificationsAsync()
try {
await setDeviceIdIfNotExistsAsync()
await loadExtensionsAsync()
await readNotificationsAsync()
if (!(await isKnownDeviceAsync())) {
console.log('not known device')
newDeviceName.value = hostname.value ?? 'unknown'
showNewDeviceDialog.value = true
if (!(await isKnownDeviceAsync())) {
console.log('not known device')
newDeviceName.value = hostname.value ?? 'unknown'
showNewDeviceDialog.value = true
}
} catch (error) {
console.error('vault mount error:', error)
}
})

View File

@ -206,7 +206,7 @@ import {
} from '~/config/constants'
definePageMeta({
name: 'haexExtension',
name: 'extension',
})
const { t } = useI18n()

View File

@ -104,7 +104,7 @@
<HaexExtensionDialogInstall
v-model:open="showConfirmation"
:preview="preview"
@confirm="addExtensionAsync"
@confirm="(addToDesktop) => addExtensionAsync(addToDesktop)"
/>
<HaexExtensionDialogRemove
@ -130,6 +130,7 @@ definePageMeta({
const { t } = useI18n()
const extensionStore = useExtensionsStore()
const desktopStore = useDesktopStore()
const showConfirmation = ref(false)
const openOverwriteDialog = ref(false)
@ -388,18 +389,23 @@ const onSelectExtensionAsync = async () => {
}
}
const addExtensionAsync = async () => {
const addExtensionAsync = async (addToDesktop: boolean = false) => {
try {
console.log(
'preview.value?.editable_permissions',
preview.value?.editable_permissions,
)
await extensionStore.installAsync(
const extensionId = await extensionStore.installAsync(
extension.path,
preview.value?.editable_permissions,
)
await extensionStore.loadExtensionsAsync()
// Add to desktop if requested
if (addToDesktop && extensionId) {
await desktopStore.addDesktopItemAsync('extension', extensionId, 50, 50)
}
add({
color: 'success',
title: t('extension.success.title', {

View File

@ -1,11 +1,6 @@
<template>
<div class="w-full">
<div class="h-screen bg-amber-300 flex-1 flex-wrap">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div class="h-screen bg-teal-300 flex-1">
abbbbbbbbbbbbbbbbbbbbb availableThemes:{{ uiStore.availableThemes }}
</div>
<div class="w-full h-full flex items-center justify-center">
<HaexDesktop />
</div>
</template>
@ -13,6 +8,4 @@
definePageMeta({
name: 'vaultOverview',
})
const uiStore = useUiStore()
</script>

View File

@ -57,14 +57,14 @@ function interceptConsole(level: 'log' | 'info' | 'warn' | 'error' | 'debug') {
}
export default defineNuxtPlugin(() => {
// Install console interceptors immediately
interceptConsole('log')
interceptConsole('info')
interceptConsole('warn')
interceptConsole('error')
interceptConsole('debug')
// TEMPORARILY DISABLED - Console interceptor causes too many logs
// interceptConsole('log')
// interceptConsole('info')
// interceptConsole('warn')
// interceptConsole('error')
// interceptConsole('debug')
console.log('[HaexHub] Global console interceptor installed')
// console.log('[HaexHub] Global console interceptor installed')
return {
provide: {

170
src/stores/vault/desktop.ts Normal file
View File

@ -0,0 +1,170 @@
import { eq } from 'drizzle-orm'
import { haexDesktopItems } from '~~/src-tauri/database/schemas'
import type {
InsertHaexDesktopItems,
SelectHaexDesktopItems,
} from '~~/src-tauri/database/schemas'
export type DesktopItemType = 'extension' | 'file' | 'folder'
export interface IDesktopItem extends SelectHaexDesktopItems {
label?: string
icon?: string
}
export const useDesktopStore = defineStore('desktopStore', () => {
const { currentVault } = storeToRefs(useVaultStore())
const desktopItems = ref<IDesktopItem[]>([])
const loadDesktopItemsAsync = async () => {
if (!currentVault.value?.drizzle) {
console.error('Kein Vault geöffnet')
return
}
try {
const items = await currentVault.value.drizzle
.select()
.from(haexDesktopItems)
desktopItems.value = items
} catch (error) {
console.error('Fehler beim Laden der Desktop-Items:', error)
throw error
}
}
const addDesktopItemAsync = async (
itemType: DesktopItemType,
referenceId: string,
positionX: number = 0,
positionY: number = 0,
) => {
if (!currentVault.value?.drizzle) {
throw new Error('Kein Vault geöffnet')
}
try {
const newItem: InsertHaexDesktopItems = {
itemType: itemType,
referenceId: referenceId,
positionX: positionX,
positionY: positionY,
}
const result = await currentVault.value.drizzle
.insert(haexDesktopItems)
.values(newItem)
.returning()
if (result.length > 0 && result[0]) {
desktopItems.value.push(result[0])
return result[0]
}
} catch (error) {
console.error('Fehler beim Hinzufügen des Desktop-Items:', error)
throw error
}
}
const updateDesktopItemPositionAsync = async (
id: string,
positionX: number,
positionY: number,
) => {
if (!currentVault.value?.drizzle) {
throw new Error('Kein Vault geöffnet')
}
try {
const result = await currentVault.value.drizzle
.update(haexDesktopItems)
.set({
positionX: positionX,
positionY: positionY,
})
.where(eq(haexDesktopItems.id, id))
.returning()
if (result.length > 0 && result[0]) {
const index = desktopItems.value.findIndex((item) => item.id === id)
if (index !== -1) {
desktopItems.value[index] = result[0]
}
}
} catch (error) {
console.error('Fehler beim Aktualisieren der Position:', error)
throw error
}
}
const removeDesktopItemAsync = async (id: string) => {
if (!currentVault.value?.drizzle) {
throw new Error('Kein Vault geöffnet')
}
try {
// Soft delete using haexTombstone
await currentVault.value.drizzle
.delete(haexDesktopItems)
.where(eq(haexDesktopItems.id, id))
desktopItems.value = desktopItems.value.filter((item) => item.id !== id)
} catch (error) {
console.error('Fehler beim Entfernen des Desktop-Items:', error)
throw error
}
}
const getDesktopItemByReference = (
itemType: DesktopItemType,
referenceId: string,
) => {
return desktopItems.value.find(
(item) => item.itemType === itemType && item.referenceId === referenceId,
)
}
const getContextMenuItems = (
id: string,
itemType: DesktopItemType,
referenceId: string,
onOpen: () => void,
onUninstall: () => void,
) => {
return [
[
{
label: 'Öffnen',
icon: 'i-heroicons-arrow-top-right-on-square',
click: onOpen,
},
],
[
{
label: 'Von Desktop entfernen',
icon: 'i-heroicons-x-mark',
click: async () => {
await removeDesktopItemAsync(id)
},
},
{
label: 'Deinstallieren',
icon: 'i-heroicons-trash',
click: onUninstall,
},
],
]
}
return {
desktopItems,
loadDesktopItemsAsync,
addDesktopItemAsync,
updateDesktopItemPositionAsync,
removeDesktopItemAsync,
getDesktopItemByReference,
getContextMenuItems,
}
})

View File

@ -51,11 +51,12 @@ export const useDeviceStore = defineStore('vaultInstanceStore', () => {
const readDeviceNameAsync = async (id?: string) => {
const { readDeviceNameAsync } = useVaultSettingsStore()
const _id = id ?? deviceId.value
const _id = id || deviceId.value
console.log('readDeviceNameAsync id', _id)
if (!_id) return
deviceName.value = (await readDeviceNameAsync(_id))?.value ?? ''
return deviceName.value
}

View File

@ -3,7 +3,10 @@
import { drizzle } from 'drizzle-orm/sqlite-proxy'
import { invoke } from '@tauri-apps/api/core'
import { schema } from '@/../src-tauri/database/index'
import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy'
import type {
AsyncRemoteCallback,
SqliteRemoteDatabase,
} from 'drizzle-orm/sqlite-proxy'
interface IVault {
name: string
@ -55,40 +58,10 @@ export const useVaultStore = defineStore('vaultStore', () => {
...openVaults.value,
[vaultId]: {
name: fileName,
drizzle: drizzle<typeof schema>(
async (sql, params: unknown[], method) => {
let rows: any[] = []
let results: any[] = []
// If the query is a SELECT, use the select method
if (isSelectQuery(sql)) {
console.log('sql_select', sql, params, method)
rows = await invoke<unknown[]>('sql_select', {
sql,
params,
}).catch((e) => {
console.error('SQL select Error:', e, sql, params)
return []
})
} else {
console.log('sql_execute', sql, params, method)
// Otherwise, use the execute method
rows = await invoke<unknown[]>('sql_execute', {
sql,
params,
}).catch((e) => {
console.error('SQL execute Error:', e, sql, params)
return []
})
return { rows: [] }
}
results = method === 'all' ? rows : rows[0]
return { rows: results }
},
{ schema: schema, logger: true },
),
drizzle: drizzle<typeof schema>(drizzleCallback, {
schema: schema,
logger: false,
}),
},
}
@ -141,7 +114,34 @@ const getVaultIdAsync = async (path: string) => {
}
const isSelectQuery = (sql: string) => {
console.log('check isSelectQuery', sql)
const selectRegex = /^\s*SELECT\b/i
return selectRegex.test(sql)
}
const drizzleCallback = (async (
sql: string,
params: unknown[],
method: 'get' | 'run' | 'all' | 'values',
) => {
let rows: unknown[] = []
if (isSelectQuery(sql)) {
rows = await invoke<unknown[]>('sql_select', { sql, params }).catch((e) => {
console.error('SQL select Error:', e, sql, params)
return []
})
} else {
rows = await invoke<unknown[]>('sql_execute', { sql, params }).catch(
(e) => {
console.error('SQL execute Error:', e, sql, params)
return []
},
)
}
if (method === 'get') {
return { rows: rows.length > 0 ? [rows[0]] : [] }
} else {
return { rows }
}
}) satisfies AsyncRemoteCallback

View File

@ -128,7 +128,7 @@ export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => {
eq(schema.haexSettings.key, id),
),
})
console.log('readDeviceNameAsync', deviceName)
console.log('store: readDeviceNameAsync', deviceName)
return deviceName
}

View File

@ -2,3 +2,5 @@
- tabellen von erweiterungen müssen mit namensschema generiert werden
`${publicKey}_${extensionName}_${tableName}`
- im sdk und generell, muss ich das namensschema überdenken. das trennzeichen sollte vermutlich etwas anderes sein, als der "\_"