mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
zwischenstand
This commit is contained in:
82
README.md
82
README.md
@ -1,16 +1,80 @@
|
||||
# Tauri + Vue + TypeScript
|
||||
# HaexHub - The European "Everything App"
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
## Vision
|
||||
|
||||
## Recommended IDE Setup
|
||||
Today, we undoubtedly find ourselves in the computer age. Almost everyone owns at least one computer, often even more. Most probably have at least a smartphone and a standard PC. On each of these devices (Desktop PC, Laptop, Tablet, Smartphone) and systems (Windows, macOS, Linux (all flavors), Android, iOS), there are various programs and data, which can be highly individual and sensitive. Unfortunately, interoperability between these devices and systems often proves difficult, sometimes even impossible, for a multitude of reasons. On one hand, there are the system providers themselves (like Microsoft, Apple, Google), who often design their systems to make it as easy as possible for users to enter their ecosystems, but place many hurdles in the way when users wish to leave again. The golden cage, as we say in Germany, or walled garden. However, it's not just the system providers per se who make cross-device and cross-system work difficult. Another problem lies with the software manufacturers/providers. Since it is already challenging and above all resource-intensive (time, money, and technical know-how) to provide a good and "secure" product for one device class and/or system, it's not uncommon for a program to be developed (initially) for only one platform. So, there might be a program for Windows or Apple, but not for Linux, or only in one distribution/package format. Or there might be an app for iOS and/or Android, but not for the PC. This is partly due to the fact that it would simply be too complex to develop and, especially, maintain a product for multiple systems and devices (simultaneously). This effort is almost insurmountable, particularly for startups, small businesses, and individual open-source developers working on their passion projects in their spare time.
|
||||
Let's not even start talking about application distribution. For each platform, you end up with a separate build pipeline that builds, tests, signs, packages the application into the appropriate format (msi, exe, deb, flatpak, snap, AppImage, Apk, etc.), and delivers it to the corresponding store (AppStore, PlayStore, Windows Store, and the various repositories of Linux distributions). This is a huge cascade of tasks that especially causes problems for small companies (at least if you want to serve ALL platforms simultaneously).
|
||||
Wouldn't it be nice if there were a simple way for developers to develop and build their application just once and then be able to serve ALL\* devices and systems? To have your "entire computer" on your USB stick, everywhere, at any time? And no matter which computer in the world you're currently using, you have everything with you? All programs, files, passwords, etc., on every device (Desktop PC, Laptop, Tablet, Smartphone), every system (Windows, macOS, Linux (all flavors), Android, iOS), anytime. Yes, this might sound confusing and unreal at first, but the idea is fantastic. It would give users back more digital self-empowerment. Only when users can no longer be held captive in golden cages can they emancipate themselves from the tech giants. Only when users can decide for themselves at any time which data they want to share with whom, for what purpose, and for what period, are they truly masters and not just commodities of their data.
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
And HaexHub would be the path to achieve this.
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
\*In principle, the approach presented here allows an application to run on all devices and systems. However, some applications might still only be usable on certain devices or systems. For example, if an application absolutely requires an NFC device, which is typically not found on a desktop PC, then this application will probably only work on mobile devices. Or if an application requires system-specific interfaces or programs, such as the Registry on Windows or systemd on Linux, then this application will naturally only work where these dependencies are found. However, developers who create their applications without such dependencies can immediately serve all devices and systems.
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
|
||||
## Enter HaexHub
|
||||
|
||||
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
|
||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
||||
HaexHub provides a framework that makes it incredibly easy for the community and any developer to build extensions (web applications), which can then be easily integrated into HaexHub by users. Each extension is essentially a web application that can be loaded, executed, customized, and deleted at runtime. Each extension is confined within an IFrame, communicating with HaexHub via APIs using postMessage. HaexHub, in turn, checks these requests for the necessary permissions, executes or rejects the command, and returns a possible response to the caller – ideally, the correct result.
|
||||
Since these are purely web applications, they are initially subject to the same limitations as any other web application in any "normal" browser worldwide. Fun Fact: Extensions in HaexHub are even more restricted than that. While a "normal" web application can, for example, load additional resources (JavaScript, CSS, images, ads) (assuming CORS allows it), this is initially not possible with a HaexHub extension. Everything the extension needs to be able to do must be specified as a permission in a manifest and approved by the user before (potentially) dangerous actions are executed on the host. And loading external resources is already considered such a risk from Tauri's (and my) perspective, as it can severely compromise the user's privacy.
|
||||
With the appropriate permissions, however, an extension can do almost anything possible on a computer. Thus, unlike a "normal" web application, an extension can directly access the host's file system, execute other applications and commands, make/manipulate/block web requests, or access the SQLite database. To use these interfaces, each extension must declare the corresponding permissions in a manifest, which must then be approved by the user. Otherwise, no access to the host system is possible. Extensions can be added and removed at runtime. Since the extension runs in an IFrame, it cannot cause much damage without the appropriate permissions. It would be a pure web application where routing within the application is possible (WebHistoryHash). However, as soon as it tries to load external resources, regardless of whether they are local from the host or from any server on the World Wide Web, the extension is on its own without permission.
|
||||
Technically, for example, it would pose no problem to make the host system's shell available to extensions. This could give Visual Studio Code in the browser superpowers. While a web version of Visual Studio Code already exists, its usability is limited. For instance, it's not possible to directly access the shell or the file system, which significantly hinders file management. And since no commands or applications can be executed on the host, it's (unfortunately) practically useless for developers. Visual Studio Code as a HaexHub extension could be used like a native application. And thanks to HaexHub's permission concept, it can be controlled with fine granularity which extension is allowed to execute what and how, and what is not. An extension with such power over the host, which can be both advantageous and disadvantageous for the user, should naturally be handled with particular care. It would probably not be a good idea to grant this permission to any advertising and data tracking services.
|
||||
|
||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
||||
The framework itself provides a platform that will be available on all common devices (Desktop PC, Laptop, Tablet, Smartphone) and systems (Windows, macOS, Linux (all flavors), Android, iOS). All extensions can then be used on all supported devices and systems (provided there are no dependencies in the extension that are only available on specific devices or systems, like NFC, Google Pay, etc.).
|
||||
All user and extension data can be securely stored and used in the locally encrypted SQLite database. To enable comfortable use of the database across multiple devices and systems, there will be a synchronization server that allows the database to be synchronized conflict-free across devices and systems. This server can, of course, also be self-hosted, ensuring the user is never dependent on a single provider.
|
||||
Furthermore, the data can be encrypted beforehand, making it unreadable by third parties.
|
||||
|
||||
HaexHub is a cross-platform, local-first, open-source application that prioritizes user privacy, security, and digital sovereignty. The goal is for the user to have control over their data at all times and be able to independently decide what they want to disclose to whom. Additionally, they should be able to adjust this decision at any time.
|
||||
Through the possibility of extensions, HaexHub is also almost infinitely expandable. What Visual Studio Code is for text editors/IDEs, HaexHub will be for (web) applications and even has the potential to become the European counterpart to WeChat (the "everything app"). However, without a central authority controlling everything.
|
||||
|
||||
But first things first.
|
||||
|
||||
## Technical Foundations
|
||||
|
||||
The technical foundation of the project is Tauri. This framework makes it possible to provide native applications for all common devices (Desktops, Laptops, Tablets, Smartphones) and systems (Windows, Linux, macOS, Android, iOS) with the same codebase. Tauri is comparable to Electron (the technical basis for Visual Studio Code, for example), but the applications created with it are significantly smaller because Tauri uses the native rendering engine of the respective platform (WebView2 (Windows), WKWebView (macOS), WebKitGTK (Linux)) and does not bundle a (customized Chromium) browser, as is the case with Electron. Furthermore, Tauri offers significant advantages over Electron in terms of security and resource efficiency. There is also a sophisticated permission system, which effectively shields the frontend from the host. All access to the host system is only possible with the appropriate permission. This permission concept is also used for the (HaexHub) extensions, thereby ensuring the security of third-party extensions as well.
|
||||
|
||||
The project follows a strict local-first approach. This means that HaexHub can fundamentally be used without any form of online account or internet access. The extensions are also stored locally and can be used offline, provided, of course, that the extension itself can function without the internet. A messenger extension will likely make limited sense without internet access. An image viewer or text editor, however, should work fine without the internet.
|
||||
All user data can be persistently stored and used in a locally encrypted SQLite database, even across extensions, with the appropriate permissions, of course. Unlike many other applications that call themselves local-first, this project implements this approach more consistently. Most applications claiming to be local-first often aren't truly so. The data usually resides (unencrypted) on a backend server and is merely "cached" to varying degrees in the frontend. While this allows these applications to be used offline for a while, the usage is either restricted (read-only in Bitwarden, for example) or the persistence is temporary at best. Most approaches, like this project, use an SQLite (or similar) database in the frontend to achieve offline capability, but this is usually implemented in a browser via IndexedDB or OPFS. Examples include [powersync](https://www.powersync.com/) , [evolu](https://www.evolu.dev/), or [electricSql](https://electric-sql.com/). The problem here is that such persistence is never truly permanent, as the operating system and/or browser can decide when to free up storage. For instance, it's common for Apple to clear the storage of web applications that haven't been used for over a week. As long as the user's data is still present in the backend, this is only moderately tragic, as the "source of truth" residing there can be synchronized back to the frontend at any time. However, this always requires an online account and internet access. Furthermore, with these approaches, the user cannot simply copy their data onto a USB stick and take it with them to use on a completely different computer (perhaps where only intranet is available).
|
||||
Moreover, all these approaches are subject to the limitations of the respective browser. The limitation on persistent storage is particularly noteworthy here. All browsers have strict limits, which is why this approach is not suitable for all requirements. Since HaexHub stores data not in the browser, but in a real SQLite database on the hard drive, it is only subject to the hardware limitations of the host system (or USB stick/storage medium).
|
||||
|
||||
With HaexHub, all user and extension data can be permanently stored in the local and encrypted database without requiring an online account. However, to make the user's data conveniently and securely available on multiple devices, there will be a synchronization service to synchronize the database state across the user's various devices and systems. The user can, of course, also host this service themselves on their (local) systems or servers. The database state is thus temporarily stored on a (third-party) server and can be synchronized from there with other instances of the local SQLite database. To further enhance data security, the user can also encrypt the data before sending it to the backend, making it unreadable by third parties. This will likely be enabled by default, but it can also be turned off, as there are legitimate use cases where it might be disadvantageous or undesirable. Particularly in corporate or government environments, it could be problematic if all user (employee) data were stored encrypted on the company servers. If the employee becomes unavailable (resignation, accident, death) and their database password (or the encryption key stored in the database) is unknown, there would be no way to access this data.
|
||||
Since this use case should also be considered, backend encryption will be optional.
|
||||
|
||||
As HaexHub is ultimately a kind of distributed and federated system, there is no (single) authority that could control everything. Unless the user truly has only one instance of their database (perhaps on a USB stick) and always carries it with them. Part of HaexHub's charm, however, is that the user can have multiple instances of their SQLite database on multiple devices and systems without having to worry about how the correct data (source of truth) gets from A to B and B to A.
|
||||
To make this possible and to synchronize even conflicting data states of the SQLite database, HaexHub uses Conflict-free Replicated Data Types (CRDTs). This will make it possible to merge multiple conflicting data states, even if they are encrypted.
|
||||
|
||||
## Extensions
|
||||
|
||||
The real highlight of HaexHub, however, lies in its extensions. All end-user functionality will ultimately be provided through extensions. There will be (official/core) extensions and third-party extensions. One of the first (official) extensions will be a password manager, for example, but a file synchronization service is also planned.
|
||||
Each extension is essentially just a web application\* loaded into an IFrame. This keeps all extensions well isolated (sandboxed) from the main application (HaexHub) and the user's host system, ensuring the user's security and privacy. Of course, as with any application, a degree of trust must be placed in the extension developer that they are genuinely only doing what they claim to do. HaexHub is ingenious, but it can't perform magic.
|
||||
Each extension must declare the permissions it requires in a manifest, which must then be accepted by the user. This ensures that each extension can only access the resources (file system, web requests, database access, etc.) for which it has the appropriate permissions.
|
||||
|
||||
In principle, any (existing) web application could be integrated and run within HaexHub. Technically, each extension is just a web application, but with significantly more capabilities. Traditional web applications are restricted by the (justified) limitations of a browser. For example, a web application cannot simply access the host system's file system or manipulate network traffic. And for good reasons. With HaexHub, however, these limitations do not exist. A (HaexHub) extension can indeed access the file system if it has the corresponding permission. This opens up almost unlimited application possibilities, making the term "everything app" seem not so far-fetched. In a future iteration, a browser and later a payment option (GNU Taler?!) are planned to be added, so it could truly become a fully-fledged counterpart to WeChat. However, these aspects are not considered in the first iteration of the application.
|
||||
By providing extensions, HaexHub can truly be enhanced arbitrarily. Extension developers could use simple tools (Vite application) to immediately provide their functionality for all devices and systems and utilize the provided ecosystem, without the developer having to deal with the peculiarities of each system for development and distribution. (Provided, of course, they don't rely on dependencies that only exist on specific systems or devices).
|
||||
Extensions can also access the data of other extensions (e.g., via the SQLite database) and build upon it (with appropriate permission, naturally).
|
||||
I want to outline this with a concrete example. The first official extension will be a password manager.
|
||||
This will be a Nuxt/Vue application. The password manager's manifest will request permission to create a few tables and to read from and write to them. The extension then provides a nice UI for creating and managing login credentials, similar to existing password managers. Each entry can also be tagged, which could later be used by other extensions.
|
||||
For example, entries tagged "E-Mail" could be created, which could then be used by an email client extension to automatically connect to mail servers.
|
||||
Any other extension could access specific entries in the password database (or other extensions' data) to easily provide its service.
|
||||
But of course, each extension can also create its own tables as needed for its specific use case.
|
||||
HaexHub takes care of secure storage and, if configured, conflict-free synchronization.
|
||||
Each user can expand their HaexHub with the individual functionality they need. And since all settings for these extensions can be stored in the SQLite database, they can be easily and seamlessly synchronized and used across multiple devices. The user only needs to set up their extensions once on one device and can then use them on all other devices and systems without further action.
|
||||
|
||||
Another example of an extension would be file synchronization, which will also be a core extension.
|
||||
This extension allows users to easily synchronize their files across different devices and systems. It can be configured on each device which files and folders should be synchronized and how. For instance, one might want to upload pictures and videos from their smartphone to an S3 bucket/Google Drive/Dropbox and their desktop PC. However, one probably doesn't want all pictures from the S3 bucket/Google Drive/Dropbox/Desktop to be synchronized back to the smartphone. All these configurations will again be stored in the SQLite database and, where possible, synchronized across all devices and systems.
|
||||
|
||||
Further examples of extensions include calendars, (collaborative) document management, contacts, messengers, and in the distant future, a browser and payment service (GNU Taler perhaps?!).
|
||||
|
||||
\*Fundamentally, any bundler (Vite, Webpack, Rollup, etc.) and any frontend framework (Vue, React, Angular, Svelte, plain HTML) should be usable. The crucial part is that it's a JS bundle. However, initially, the focus will primarily be on Vite and Vue to demonstrate the general feasibility first.
|
||||
|
||||
## Preperation
|
||||
|
||||
install:
|
||||
|
||||
- [nodejs](https://nodejs.org/en/download)
|
||||
- [tauri](https://v2.tauri.app/start/prerequisites/)
|
||||
- [rust](https://v2.tauri.app/start/prerequisites/#rust)
|
||||
|
||||
- port 3003 needs to be open/free or you need to adjust it in `nuxt.config.ts` AND `src-tauri/tauri.conf.json`
|
||||
|
||||
- `git clone https://github.com/haexhub/haex-vault.git`
|
||||
- `cd haex-vault`
|
||||
- `pnpm i`
|
||||
- `pnpm tauri dev`
|
||||
|
||||
@ -10,6 +10,7 @@ export default defineNuxtConfig({
|
||||
"@vueuse/nuxt",
|
||||
"@nuxt/icon",
|
||||
"nuxt-snackbar",
|
||||
"@nuxt/image",
|
||||
],
|
||||
|
||||
imports: {
|
||||
@ -77,4 +78,4 @@ export default defineNuxtConfig({
|
||||
plugins: () => [wasm(), topLevelAwait()],
|
||||
}, */
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -16,6 +16,7 @@
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.4",
|
||||
"@nuxt/icon": "1.11.0",
|
||||
"@nuxt/image": "1.10.0",
|
||||
"@nuxtjs/i18n": "^9.5.3",
|
||||
"@pinia/nuxt": "^0.10.1",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
|
||||
391
pnpm-lock.yaml
generated
391
pnpm-lock.yaml
generated
@ -14,6 +14,9 @@ importers:
|
||||
'@nuxt/icon':
|
||||
specifier: 1.11.0
|
||||
version: 1.11.0(magicast@0.3.5)(vite@6.3.3(@types/node@22.15.3)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.1))(vue@3.5.13(typescript@5.6.3))
|
||||
'@nuxt/image':
|
||||
specifier: 1.10.0
|
||||
version: 1.10.0(@netlify/blobs@8.2.0)(db0@0.3.2(@libsql/client@0.15.4)(drizzle-orm@0.41.0(@libsql/client@0.15.4)(gel@2.0.2)))(ioredis@5.6.1)(magicast@0.3.5)
|
||||
'@nuxtjs/i18n':
|
||||
specifier: ^9.5.3
|
||||
version: 9.5.3(@vue/compiler-dom@3.5.13)(eslint@9.23.0(jiti@2.4.2))(magicast@0.3.5)(rollup@4.40.1)(vue@3.5.13(typescript@5.6.3))
|
||||
@ -888,6 +891,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@fastify/accept-negotiator@1.1.0':
|
||||
resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@fastify/busboy@3.1.1':
|
||||
resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==}
|
||||
|
||||
@ -1193,6 +1200,10 @@ packages:
|
||||
'@nuxt/icon@1.11.0':
|
||||
resolution: {integrity: sha512-j82YbT7/Z02W/6jhiMoXHdtpSsCBfAoI3EkJ5Axi0C30ALiqvmrmfwd+CG7dftyncj51goBi1YMb6I4vNHK9nA==}
|
||||
|
||||
'@nuxt/image@1.10.0':
|
||||
resolution: {integrity: sha512-/B58GeEmme7bkmQUrXzEw8P9sJb9BkMaYZqLDtq8ZdDLEddE3P4nVya8RQPB+p4b7EdqWajpPqdy1A2ZPLev/A==}
|
||||
engines: {node: '>=18.20.6'}
|
||||
|
||||
'@nuxt/kit@3.17.0':
|
||||
resolution: {integrity: sha512-+aS+Enqqo2qSbyl0APPPxX8BPYsaRcZ8dFRbpCOfK38lv2ckoHKCWNkT8L/7q2w+1pjNZaxlUoW9Mku1vdEb/A==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
@ -2119,6 +2130,33 @@ packages:
|
||||
bare-events@2.5.4:
|
||||
resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
|
||||
|
||||
bare-fs@4.1.4:
|
||||
resolution: {integrity: sha512-r8+26Voz8dGX3AYpJdFb1ZPaUSM8XOLCZvy+YGpRTmwPHIxA7Z3Jov/oMPtV7hfRQbOnH8qGlLTzQAbgtdNN0Q==}
|
||||
engines: {bare: '>=1.16.0'}
|
||||
peerDependencies:
|
||||
bare-buffer: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-buffer:
|
||||
optional: true
|
||||
|
||||
bare-os@3.6.1:
|
||||
resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==}
|
||||
engines: {bare: '>=1.14.0'}
|
||||
|
||||
bare-path@3.0.0:
|
||||
resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
|
||||
|
||||
bare-stream@2.6.5:
|
||||
resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==}
|
||||
peerDependencies:
|
||||
bare-buffer: '*'
|
||||
bare-events: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-buffer:
|
||||
optional: true
|
||||
bare-events:
|
||||
optional: true
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@ -2132,6 +2170,9 @@ packages:
|
||||
birpc@2.3.0:
|
||||
resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
@ -2160,6 +2201,9 @@ packages:
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
@ -2224,6 +2268,9 @@ packages:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
|
||||
chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -2274,6 +2321,10 @@ packages:
|
||||
color@3.2.1:
|
||||
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||
|
||||
color@4.2.3:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
||||
colord@2.9.3:
|
||||
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
||||
|
||||
@ -2417,6 +2468,9 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
cssfilter@0.0.10:
|
||||
resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}
|
||||
|
||||
cssnano-preset-default@7.0.6:
|
||||
resolution: {integrity: sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==}
|
||||
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
|
||||
@ -2496,6 +2550,10 @@ packages:
|
||||
decache@4.6.2:
|
||||
resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==}
|
||||
|
||||
decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
dedupe@4.0.3:
|
||||
resolution: {integrity: sha512-WMo1AIq5MSstJ5DhVVccdue22MEpaxzf0tdznjXgWrfei9F3aNYP7xhbKgi1U8JlvTU4RyhUSLI63jt7PUipBQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
@ -2503,6 +2561,10 @@ packages:
|
||||
deep-equal@1.0.1:
|
||||
resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
|
||||
|
||||
deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
@ -2919,6 +2981,10 @@ packages:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
expand-template@2.0.3:
|
||||
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
exsolve@1.0.5:
|
||||
resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==}
|
||||
|
||||
@ -3037,6 +3103,9 @@ packages:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
|
||||
fs-extra@9.1.0:
|
||||
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -3118,6 +3187,9 @@ packages:
|
||||
git-url-parse@16.1.0:
|
||||
resolution: {integrity: sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==}
|
||||
|
||||
github-from-package@0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@ -3290,6 +3362,9 @@ packages:
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
ini@4.1.1:
|
||||
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
@ -3298,6 +3373,10 @@ packages:
|
||||
resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
|
||||
ipx@2.1.0:
|
||||
resolution: {integrity: sha512-AVnPGXJ8L41vjd11Z4akIF2yd14636Klxul3tBySxHA6PKfCOQPxBDkCFK5zcWh0z/keR6toh1eg8qzdBVUgdA==}
|
||||
hasBin: true
|
||||
|
||||
iron-webcrypto@1.2.1:
|
||||
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
||||
|
||||
@ -3544,7 +3623,6 @@ packages:
|
||||
|
||||
libsql@0.5.8:
|
||||
resolution: {integrity: sha512-+OopMI1wM/NvAJTHf3O3+beHd1YfKLnSVsOGBl3/7UBDZ4ydVadkbBk5Hjjs9d3ALC5rBaftMY59AvwyC8MzPw==}
|
||||
cpu: [x64, arm64, wasm32]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
lilconfig@3.1.3:
|
||||
@ -3688,6 +3766,10 @@ packages:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
mimic-response@3.1.0:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
@ -3725,6 +3807,9 @@ packages:
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
mkdirp-classic@0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
|
||||
mkdirp@1.0.4:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
@ -3772,6 +3857,9 @@ packages:
|
||||
nanotar@0.2.0:
|
||||
resolution: {integrity: sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==}
|
||||
|
||||
napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
@ -3796,6 +3884,13 @@ packages:
|
||||
xml2js:
|
||||
optional: true
|
||||
|
||||
node-abi@3.75.0:
|
||||
resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
node-addon-api@6.1.0:
|
||||
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
@ -4340,6 +4435,11 @@ packages:
|
||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
precinct@11.0.5:
|
||||
resolution: {integrity: sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w==}
|
||||
engines: {node: ^14.14.0 || >=16.0.0}
|
||||
@ -4403,6 +4503,10 @@ packages:
|
||||
rc9@2.1.2:
|
||||
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
@ -4569,6 +4673,10 @@ packages:
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
sharp@0.32.6:
|
||||
resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -4604,6 +4712,12 @@ packages:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
|
||||
simple-get@4.0.1:
|
||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||
|
||||
simple-git@3.27.0:
|
||||
resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==}
|
||||
|
||||
@ -4705,6 +4819,10 @@ packages:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@ -4767,6 +4885,16 @@ packages:
|
||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-fs@2.1.2:
|
||||
resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==}
|
||||
|
||||
tar-fs@3.0.8:
|
||||
resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==}
|
||||
|
||||
tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
|
||||
@ -4870,6 +4998,9 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@ -5269,6 +5400,11 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
xss@1.0.15:
|
||||
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -5903,6 +6039,9 @@ snapshots:
|
||||
'@eslint/core': 0.13.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@fastify/accept-negotiator@1.1.0':
|
||||
optional: true
|
||||
|
||||
'@fastify/busboy@3.1.1': {}
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
@ -6400,6 +6539,42 @@ snapshots:
|
||||
- vite
|
||||
- vue
|
||||
|
||||
'@nuxt/image@1.10.0(@netlify/blobs@8.2.0)(db0@0.3.2(@libsql/client@0.15.4)(drizzle-orm@0.41.0(@libsql/client@0.15.4)(gel@2.0.2)))(ioredis@5.6.1)(magicast@0.3.5)':
|
||||
dependencies:
|
||||
'@nuxt/kit': 3.17.0(magicast@0.3.5)
|
||||
consola: 3.4.2
|
||||
defu: 6.1.4
|
||||
h3: 1.15.2
|
||||
image-meta: 0.2.1
|
||||
knitwork: 1.2.0
|
||||
ohash: 2.0.11
|
||||
pathe: 2.0.3
|
||||
std-env: 3.9.0
|
||||
ufo: 1.6.1
|
||||
optionalDependencies:
|
||||
ipx: 2.1.0(@netlify/blobs@8.2.0)(db0@0.3.2(@libsql/client@0.15.4)(drizzle-orm@0.41.0(@libsql/client@0.15.4)(gel@2.0.2)))(ioredis@5.6.1)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- bare-buffer
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- magicast
|
||||
- uploadthing
|
||||
|
||||
'@nuxt/kit@3.17.0(magicast@0.3.5)':
|
||||
dependencies:
|
||||
c12: 3.0.3(magicast@0.3.5)
|
||||
@ -7427,6 +7602,28 @@ snapshots:
|
||||
bare-events@2.5.4:
|
||||
optional: true
|
||||
|
||||
bare-fs@4.1.4:
|
||||
dependencies:
|
||||
bare-events: 2.5.4
|
||||
bare-path: 3.0.0
|
||||
bare-stream: 2.6.5(bare-events@2.5.4)
|
||||
optional: true
|
||||
|
||||
bare-os@3.6.1:
|
||||
optional: true
|
||||
|
||||
bare-path@3.0.0:
|
||||
dependencies:
|
||||
bare-os: 3.6.1
|
||||
optional: true
|
||||
|
||||
bare-stream@2.6.5(bare-events@2.5.4):
|
||||
dependencies:
|
||||
streamx: 2.22.0
|
||||
optionalDependencies:
|
||||
bare-events: 2.5.4
|
||||
optional: true
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
@ -7437,6 +7634,13 @@ snapshots:
|
||||
|
||||
birpc@2.3.0: {}
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
optional: true
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
@ -7465,6 +7669,12 @@ snapshots:
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
optional: true
|
||||
|
||||
buffer@6.0.3:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
@ -7546,6 +7756,9 @@ snapshots:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
chownr@1.1.4:
|
||||
optional: true
|
||||
|
||||
chownr@2.0.0: {}
|
||||
|
||||
chownr@3.0.0: {}
|
||||
@ -7594,6 +7807,12 @@ snapshots:
|
||||
color-convert: 1.9.3
|
||||
color-string: 1.9.1
|
||||
|
||||
color@4.2.3:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
color-string: 1.9.1
|
||||
optional: true
|
||||
|
||||
colord@2.9.3: {}
|
||||
|
||||
colorspace@1.1.4:
|
||||
@ -7720,6 +7939,9 @@ snapshots:
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
cssfilter@0.0.10:
|
||||
optional: true
|
||||
|
||||
cssnano-preset-default@7.0.6(postcss@8.5.3):
|
||||
dependencies:
|
||||
browserslist: 4.24.4
|
||||
@ -7793,10 +8015,18 @@ snapshots:
|
||||
dependencies:
|
||||
callsite: 1.0.0
|
||||
|
||||
decompress-response@6.0.0:
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
optional: true
|
||||
|
||||
dedupe@4.0.3: {}
|
||||
|
||||
deep-equal@1.0.1: {}
|
||||
|
||||
deep-extend@0.6.0:
|
||||
optional: true
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@ -8218,6 +8448,9 @@ snapshots:
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
expand-template@2.0.3:
|
||||
optional: true
|
||||
|
||||
exsolve@1.0.5: {}
|
||||
|
||||
externality@1.0.2:
|
||||
@ -8340,6 +8573,9 @@ snapshots:
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
fs-constants@1.0.0:
|
||||
optional: true
|
||||
|
||||
fs-extra@9.1.0:
|
||||
dependencies:
|
||||
at-least-node: 1.0.0
|
||||
@ -8442,6 +8678,9 @@ snapshots:
|
||||
dependencies:
|
||||
git-up: 8.1.1
|
||||
|
||||
github-from-package@0.0.0:
|
||||
optional: true
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@ -8634,6 +8873,9 @@ snapshots:
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@1.3.8:
|
||||
optional: true
|
||||
|
||||
ini@4.1.1: {}
|
||||
|
||||
ioredis@5.6.1:
|
||||
@ -8650,6 +8892,46 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ipx@2.1.0(@netlify/blobs@8.2.0)(db0@0.3.2(@libsql/client@0.15.4)(drizzle-orm@0.41.0(@libsql/client@0.15.4)(gel@2.0.2)))(ioredis@5.6.1):
|
||||
dependencies:
|
||||
'@fastify/accept-negotiator': 1.1.0
|
||||
citty: 0.1.6
|
||||
consola: 3.4.2
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
etag: 1.8.1
|
||||
h3: 1.15.2
|
||||
image-meta: 0.2.1
|
||||
listhen: 1.9.0
|
||||
ofetch: 1.4.1
|
||||
pathe: 1.1.2
|
||||
sharp: 0.32.6
|
||||
svgo: 3.3.2
|
||||
ufo: 1.6.1
|
||||
unstorage: 1.16.0(@netlify/blobs@8.2.0)(db0@0.3.2(@libsql/client@0.15.4)(drizzle-orm@0.41.0(@libsql/client@0.15.4)(gel@2.0.2)))(ioredis@5.6.1)
|
||||
xss: 1.0.15
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- bare-buffer
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- uploadthing
|
||||
optional: true
|
||||
|
||||
iron-webcrypto@1.2.1: {}
|
||||
|
||||
is-arrayish@0.3.2: {}
|
||||
@ -9030,6 +9312,9 @@ snapshots:
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
mimic-response@3.1.0:
|
||||
optional: true
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
@ -9063,6 +9348,9 @@ snapshots:
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
mkdirp-classic@0.5.3:
|
||||
optional: true
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mkdirp@3.0.1: {}
|
||||
@ -9099,6 +9387,9 @@ snapshots:
|
||||
|
||||
nanotar@0.2.0: {}
|
||||
|
||||
napi-build-utils@2.0.0:
|
||||
optional: true
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
negotiator@0.6.3: {}
|
||||
@ -9214,6 +9505,14 @@ snapshots:
|
||||
- supports-color
|
||||
- uploadthing
|
||||
|
||||
node-abi@3.75.0:
|
||||
dependencies:
|
||||
semver: 7.7.1
|
||||
optional: true
|
||||
|
||||
node-addon-api@6.1.0:
|
||||
optional: true
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
@ -9857,6 +10156,22 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.0.4
|
||||
expand-template: 2.0.3
|
||||
github-from-package: 0.0.0
|
||||
minimist: 1.2.8
|
||||
mkdirp-classic: 0.5.3
|
||||
napi-build-utils: 2.0.0
|
||||
node-abi: 3.75.0
|
||||
pump: 3.0.2
|
||||
rc: 1.2.8
|
||||
simple-get: 4.0.1
|
||||
tar-fs: 2.1.2
|
||||
tunnel-agent: 0.6.0
|
||||
optional: true
|
||||
|
||||
precinct@11.0.5:
|
||||
dependencies:
|
||||
'@dependents/detective-less': 4.1.0
|
||||
@ -9921,6 +10236,14 @@ snapshots:
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
ini: 1.3.8
|
||||
minimist: 1.2.8
|
||||
strip-json-comments: 2.0.1
|
||||
optional: true
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
@ -10120,6 +10443,20 @@ snapshots:
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
sharp@0.32.6:
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
detect-libc: 2.0.4
|
||||
node-addon-api: 6.1.0
|
||||
prebuild-install: 7.1.3
|
||||
semver: 7.7.1
|
||||
simple-get: 4.0.1
|
||||
tar-fs: 3.0.8
|
||||
tunnel-agent: 0.6.0
|
||||
transitivePeerDependencies:
|
||||
- bare-buffer
|
||||
optional: true
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
@ -10160,6 +10497,16 @@ snapshots:
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
simple-concat@1.0.1:
|
||||
optional: true
|
||||
|
||||
simple-get@4.0.1:
|
||||
dependencies:
|
||||
decompress-response: 6.0.0
|
||||
once: 1.4.0
|
||||
simple-concat: 1.0.1
|
||||
optional: true
|
||||
|
||||
simple-git@3.27.0:
|
||||
dependencies:
|
||||
'@kwsites/file-exists': 1.1.1
|
||||
@ -10260,6 +10607,9 @@ snapshots:
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
optional: true
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strip-literal@3.0.0:
|
||||
@ -10351,6 +10701,34 @@ snapshots:
|
||||
|
||||
tapable@2.2.1: {}
|
||||
|
||||
tar-fs@2.1.2:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
mkdirp-classic: 0.5.3
|
||||
pump: 3.0.2
|
||||
tar-stream: 2.2.0
|
||||
optional: true
|
||||
|
||||
tar-fs@3.0.8:
|
||||
dependencies:
|
||||
pump: 3.0.2
|
||||
tar-stream: 3.1.7
|
||||
optionalDependencies:
|
||||
bare-fs: 4.1.4
|
||||
bare-path: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- bare-buffer
|
||||
optional: true
|
||||
|
||||
tar-stream@2.2.0:
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
end-of-stream: 1.4.4
|
||||
fs-constants: 1.0.0
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
optional: true
|
||||
|
||||
tar-stream@3.1.7:
|
||||
dependencies:
|
||||
b4a: 1.6.7
|
||||
@ -10448,6 +10826,11 @@ snapshots:
|
||||
tslib: 1.14.1
|
||||
typescript: 5.6.3
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
optional: true
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@ -10825,6 +11208,12 @@ snapshots:
|
||||
|
||||
ws@8.18.1: {}
|
||||
|
||||
xss@1.0.15:
|
||||
dependencies:
|
||||
commander: 2.20.3
|
||||
cssfilter: 0.0.10
|
||||
optional: true
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
37
src-tauri/Cargo.lock
generated
37
src-tauri/Cargo.lock
generated
@ -1090,6 +1090,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
@ -1528,6 +1534,10 @@ dependencies = [
|
||||
name = "haex-hub"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"fs_extra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -1639,6 +1649,12 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@ -2203,6 +2219,16 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.5"
|
||||
@ -3792,9 +3818,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.55.0"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4521174166bac1ff04fe16ef4524c70144cd29682a45978978ca3d7f4e0be11"
|
||||
checksum = "e68feb51ffa54fc841e086f58da543facfe3d7ae2a60d69b0a8cbbd30d16ae8d"
|
||||
dependencies = [
|
||||
"log",
|
||||
"recursive",
|
||||
@ -4031,6 +4057,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"http-range",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
@ -4745,6 +4772,12 @@ dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
||||
@ -27,8 +27,12 @@ rusqlite = { version = "0.35.0", features = [
|
||||
tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sqlparser = { version = "0.55.0", features = [] }
|
||||
tauri = { version = "2.5", features = [] }
|
||||
base64 = "0.22"
|
||||
mime_guess = "2.0"
|
||||
mime = "0.3"
|
||||
fs_extra = "1.3.0"
|
||||
sqlparser = { version = "0.56.0", features = [] }
|
||||
tauri = { version = "2.5", features = ["protocol-asset", "custom-protocol"] }
|
||||
tauri-plugin-dialog = "2.2"
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-opener = "2.2"
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"dialog:default",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-resource-read-recursive",
|
||||
"fs:default",
|
||||
"http:allow-fetch-send",
|
||||
"http:allow-fetch",
|
||||
"http:default",
|
||||
"opener:allow-open-url",
|
||||
"opener:default",
|
||||
"os:default",
|
||||
"store:default",
|
||||
"core:window:allow-create",
|
||||
"core:window:default",
|
||||
"core:window:allow-get-all-windows",
|
||||
"core:window:allow-show",
|
||||
"core:webview:allow-create-webview",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:webview:default",
|
||||
"core:webview:allow-webview-show"
|
||||
]
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"dialog:default",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-resource-read-recursive",
|
||||
"fs:default",
|
||||
"fs:allow-resource-write-recursive",
|
||||
"http:allow-fetch-send",
|
||||
"http:allow-fetch",
|
||||
"http:default",
|
||||
"opener:allow-open-url",
|
||||
"opener:default",
|
||||
"os:default",
|
||||
"store:default",
|
||||
"core:window:allow-create",
|
||||
"core:window:default",
|
||||
"core:window:allow-get-all-windows",
|
||||
"core:window:allow-show",
|
||||
"core:webview:allow-create-webview",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:webview:default",
|
||||
"core:webview:allow-webview-show"
|
||||
]
|
||||
}
|
||||
|
||||
23
src-tauri/database/index.ts
Normal file
23
src-tauri/database/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { drizzle } from "drizzle-orm/sqlite-proxy"; // Adapter für Query Building ohne direkte Verbindung
|
||||
import * as schema from "./schemas/vault"; // Importiere alles aus deiner Schema-Datei
|
||||
|
||||
// sqlite-proxy benötigt eine (dummy) Ausführungsfunktion als Argument.
|
||||
// Diese wird in unserem Tauri-Workflow nie aufgerufen, da wir nur .toSQL() verwenden.
|
||||
// Sie muss aber vorhanden sein, um drizzle() aufrufen zu können.
|
||||
const dummyExecutor = async (
|
||||
sql: string,
|
||||
params: any[],
|
||||
method: "all" | "run" | "get" | "values"
|
||||
) => {
|
||||
console.warn(
|
||||
`Frontend Drizzle Executor wurde aufgerufen (Methode: ${method}). Das sollte im Tauri-Invoke-Workflow nicht passieren!`
|
||||
);
|
||||
// Wir geben leere Ergebnisse zurück, um die Typen zufriedenzustellen, falls es doch aufgerufen wird.
|
||||
return { rows: [] }; // Für 'run' (z.B. bei INSERT/UPDATE)
|
||||
};
|
||||
|
||||
// Erstelle die Drizzle-Instanz für den SQLite-Dialekt
|
||||
// Übergib den dummyExecutor und das importierte Schema
|
||||
export const db = drizzle(dummyExecutor, { schema });
|
||||
|
||||
// Exportiere auch alle Schema-Definitionen weiter, damit man alles aus einer Datei importieren kann
|
||||
@ -0,0 +1,7 @@
|
||||
CREATE TABLE `testTable` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`author` text,
|
||||
`test` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `haex_extensions` ADD `icon` text;
|
||||
223
src-tauri/database/migrations/meta/0001_snapshot.json
Normal file
223
src-tauri/database/migrations/meta/0001_snapshot.json
Normal file
@ -0,0 +1,223 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "6fb5396b-9f87-4fb5-87a2-22d4eecaa11e",
|
||||
"prevId": "fc5a7c9d-4846-4120-a762-cc2ea00504b9",
|
||||
"tables": {
|
||||
"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
|
||||
},
|
||||
"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
|
||||
},
|
||||
"url": {
|
||||
"name": "url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"haex_extensions_permissions": {
|
||||
"name": "haex_extensions_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": {
|
||||
"name": "resource",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"operation": {
|
||||
"name": "operation",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"haex_extensions_permissions_extension_id_resource_operation_path_unique": {
|
||||
"name": "haex_extensions_permissions_extension_id_resource_operation_path_unique",
|
||||
"columns": [
|
||||
"extension_id",
|
||||
"resource",
|
||||
"operation",
|
||||
"path"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"haex_extensions_permissions_extension_id_haex_extensions_id_fk": {
|
||||
"name": "haex_extensions_permissions_extension_id_haex_extensions_id_fk",
|
||||
"tableFrom": "haex_extensions_permissions",
|
||||
"tableTo": "haex_extensions",
|
||||
"columnsFrom": [
|
||||
"extension_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"value_text": {
|
||||
"name": "value_text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value_json": {
|
||||
"name": "value_json",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value_number": {
|
||||
"name": "value_number",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"testTable": {
|
||||
"name": "testTable",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"test": {
|
||||
"name": "test",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,13 @@
|
||||
"when": 1742903332283,
|
||||
"tag": "0000_zippy_scourge",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1746281577722,
|
||||
"tag": "0001_wealthy_thaddeus_ross",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,51 +1,47 @@
|
||||
import {
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
type AnySQLiteColumn,
|
||||
unique,
|
||||
numeric,
|
||||
integer,
|
||||
numeric,
|
||||
sqliteTable,
|
||||
text,
|
||||
type AnySQLiteColumn,
|
||||
unique,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
|
||||
export function generateCreateStatements() {
|
||||
const tables = [haexSettings, haexExtensions, testTable, haexExtensionsPermissions];
|
||||
}
|
||||
|
||||
export const haexSettings = sqliteTable("haex_settings", {
|
||||
id: text().primaryKey(),
|
||||
key: text(),
|
||||
value_text: text(),
|
||||
value_json: text({ mode: "json" }),
|
||||
value_number: numeric(),
|
||||
id: text().primaryKey(),
|
||||
key: text(),
|
||||
value_text: text(),
|
||||
value_json: text({ mode: "json" }),
|
||||
value_number: numeric(),
|
||||
});
|
||||
|
||||
export const haexExtensions = sqliteTable("haex_extensions", {
|
||||
id: text().primaryKey(),
|
||||
author: text(),
|
||||
enabled: integer(),
|
||||
name: text(),
|
||||
url: text(),
|
||||
version: text(),
|
||||
id: text().primaryKey(),
|
||||
author: text(),
|
||||
enabled: integer({ mode: "boolean" }),
|
||||
icon: text(),
|
||||
name: text(),
|
||||
url: text(),
|
||||
version: text(),
|
||||
});
|
||||
|
||||
export const testTable = sqliteTable("testTable", {
|
||||
id: text().primaryKey(),
|
||||
author: text(),
|
||||
test: text(),
|
||||
id: text().primaryKey(),
|
||||
author: text(),
|
||||
test: text(),
|
||||
});
|
||||
|
||||
export const haexExtensionsPermissions = sqliteTable(
|
||||
"haex_extensions_permissions",
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
extensionId: text("extension_id").references((): AnySQLiteColumn => haexExtensions.id),
|
||||
resource: text({ enum: ["fs", "http", "database"] }),
|
||||
operation: text({ enum: ["read", "write", "create"] }),
|
||||
path: text(),
|
||||
},
|
||||
(table) => [unique().on(table.extensionId, table.resource, table.operation, table.path)]
|
||||
"haex_extensions_permissions",
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
extensionId: text("extension_id").references((): AnySQLiteColumn => haexExtensions.id),
|
||||
resource: text({ enum: ["fs", "http", "database"] }),
|
||||
operation: text({ enum: ["read", "write", "create"] }),
|
||||
path: text(),
|
||||
},
|
||||
(table) => [unique().on(table.extensionId, table.resource, table.operation, table.path)]
|
||||
);
|
||||
console.log("table", haexExtensionsPermissions.getSQL());
|
||||
|
||||
export type InsertHaexSettings = typeof haexSettings.$inferInsert;
|
||||
export type SelectHaexSettings = typeof haexSettings.$inferSelect;
|
||||
|
||||
Binary file not shown.
@ -1,13 +1,74 @@
|
||||
// database/core.rs
|
||||
use crate::database::DbConnection;
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
use serde_json::json;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use rusqlite::{
|
||||
types::{Value as RusqliteValue, ValueRef},
|
||||
Connection, OpenFlags, ToSql,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use tauri::State;
|
||||
// --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht ---
|
||||
// Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes.
|
||||
// Eine einfachere Variante ist oft, direkt rusqlite::types::Value zu erstellen.
|
||||
// Hier ein Beispiel, das owned Values erstellt (braucht evtl. Anpassung je nach rusqlite-Version/Nutzung)
|
||||
fn json_to_rusqlite_value(json_val: &JsonValue) -> Result<RusqliteValue, String> {
|
||||
match json_val {
|
||||
JsonValue::Null => Ok(RusqliteValue::Null),
|
||||
JsonValue::Bool(b) => Ok(RusqliteValue::Integer(*b as i64)), // SQLite hat keinen BOOLEAN
|
||||
JsonValue::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
Ok(RusqliteValue::Integer(i))
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Ok(RusqliteValue::Real(f))
|
||||
} else {
|
||||
Err("Ungültiger Zahlenwert".to_string())
|
||||
}
|
||||
}
|
||||
JsonValue::String(s) => Ok(RusqliteValue::Text(s.clone())),
|
||||
JsonValue::Array(_) | JsonValue::Object(_) => {
|
||||
// SQLite kann Arrays/Objects nicht direkt speichern (außer als TEXT/BLOB)
|
||||
// Konvertiere sie zu JSON-Strings, wenn das gewünscht ist
|
||||
Ok(RusqliteValue::Text(
|
||||
serde_json::to_string(json_val).map_err(|e| e.to_string())?,
|
||||
))
|
||||
// Oder gib einen Fehler zurück, wenn Arrays/Objekte nicht erlaubt sind
|
||||
// Err("Arrays oder Objekte werden nicht direkt als Parameter unterstützt".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tauri Command für INSERT/UPDATE/DELETE ---
|
||||
#[tauri::command]
|
||||
pub async fn execute(
|
||||
sql: String,
|
||||
params: Vec<JsonValue>,
|
||||
state: &State<'_, DbConnection>,
|
||||
) -> Result<usize, String> {
|
||||
// Gibt Anzahl betroffener Zeilen zurück
|
||||
|
||||
let params_converted: Vec<RusqliteValue> = params
|
||||
.iter()
|
||||
.map(json_to_rusqlite_value)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect();
|
||||
|
||||
let db_lock = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Mutex Lock Fehler: {}", e))?;
|
||||
let conn = db_lock.as_ref().ok_or("Keine Datenbankverbindung")?;
|
||||
|
||||
let affected_rows = conn
|
||||
.execute(&sql, ¶ms_sql[..])
|
||||
.map_err(|e| format!("SQL Execute Fehler: {}", e))?;
|
||||
|
||||
Ok(affected_rows)
|
||||
}
|
||||
|
||||
/// Führt SQL-Schreiboperationen (INSERT, UPDATE, DELETE, CREATE) ohne Berechtigungsprüfung aus
|
||||
pub async fn execute(
|
||||
/* pub async fn execute(
|
||||
sql: &str,
|
||||
params: &[String],
|
||||
state: &State<'_, DbConnection>,
|
||||
@ -26,14 +87,107 @@ pub async fn execute(
|
||||
"last_insert_id": last_id
|
||||
}))
|
||||
.map_err(|e| format!("JSON-Serialisierungsfehler: {}", e))?)
|
||||
} */
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn select(
|
||||
sql: String,
|
||||
params: Vec<JsonValue>, // Parameter als JSON Values empfangen
|
||||
state: &State<'_, DbConnection>,
|
||||
) -> Result<Vec<Vec<JsonValue>>, String> {
|
||||
// Ergebnis als Vec<RowObject>
|
||||
|
||||
// Konvertiere JSON Params zu rusqlite Values für die Abfrage
|
||||
// Wir sammeln sie als owned Values, da `params_from_iter` Referenzen braucht,
|
||||
// was mit lokalen Konvertierungen schwierig ist.
|
||||
let params_converted: Vec<RusqliteValue> = params
|
||||
.iter()
|
||||
.map(json_to_rusqlite_value)
|
||||
.collect::<Result<Vec<_>, _>>()?; // Sammle Ergebnisse, gibt Fehler weiter
|
||||
|
||||
// Konvertiere zu Slice von ToSql-Referenzen (erfordert, dass die Values leben)
|
||||
let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect();
|
||||
|
||||
// Zugriff auf die Verbindung (blockierend, okay für SQLite in vielen Fällen)
|
||||
let db_lock = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Mutex Lock Fehler: {}", e))?;
|
||||
let conn = db_lock.as_ref().ok_or("Keine Datenbankverbindung")?;
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare(&sql)
|
||||
.map_err(|e| format!("SQL Prepare Fehler: {}", e))?;
|
||||
let column_names: Vec<String> = stmt
|
||||
.column_names()
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let num_columns = column_names.len();
|
||||
|
||||
let mut rows = stmt
|
||||
.query(¶ms_sql[..])
|
||||
.map_err(|e| format!("SQL Query Fehler: {}", e))?;
|
||||
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
|
||||
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
|
||||
while let Some(row) = rows.next().map_err(|e| format!("Row Next Fehler: {}", e))? {
|
||||
//let mut row_map = HashMap::new();
|
||||
let mut row_data: 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| format!("Get Ref Fehler Spalte {}: {}", i, e))?;
|
||||
|
||||
// Wandle rusqlite ValueRef zurück zu serde_json Value
|
||||
let json_val = match value_ref {
|
||||
ValueRef::Null => JsonValue::Null,
|
||||
ValueRef::Integer(i) => JsonValue::Number(i.into()),
|
||||
ValueRef::Real(f) => JsonValue::Number(
|
||||
serde_json::Number::from_f64(f).unwrap_or(serde_json::Number::from(0)),
|
||||
), // Fallback für NaN/Infinity
|
||||
ValueRef::Text(t) => {
|
||||
let s = String::from_utf8_lossy(t).to_string();
|
||||
// Versuche, als JSON zu parsen, falls es ursprünglich ein Array/Objekt war
|
||||
//serde_json::from_str(&s).unwrap_or(JsonValue::String(s))
|
||||
JsonValue::String(s)
|
||||
}
|
||||
ValueRef::Blob(b) => {
|
||||
// BLOBs z.B. als Base64-String zurückgeben
|
||||
JsonValue::String(STANDARD.encode(b))
|
||||
}
|
||||
};
|
||||
println!(
|
||||
"new row: name: {} with value: {}",
|
||||
column_names[i].clone(),
|
||||
json_val,
|
||||
);
|
||||
row_data.push(json_val);
|
||||
//row_map.insert(column_names[i].clone(), json_val);
|
||||
}
|
||||
//result_vec.push(row_map);
|
||||
result_vec.push(row_data);
|
||||
}
|
||||
|
||||
Ok(result_vec)
|
||||
}
|
||||
|
||||
/// Führt SQL-Leseoperationen (SELECT) ohne Berechtigungsprüfung aus
|
||||
pub async fn select(
|
||||
/* pub async fn select(
|
||||
sql: &str,
|
||||
params: &[String],
|
||||
state: &State<'_, DbConnection>,
|
||||
) -> Result<Vec<Vec<String>>, String> {
|
||||
) -> Result<Vec<Vec<Option<String>>>, String> {
|
||||
let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?;
|
||||
let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?;
|
||||
|
||||
@ -52,16 +206,20 @@ pub async fn select(
|
||||
{
|
||||
let mut row_data = Vec::new();
|
||||
for i in 0..columns {
|
||||
let value: String = row
|
||||
let value = row
|
||||
.get(i)
|
||||
.map_err(|e| format!("Datentypfehler in Spalte {}: {}", i, e))?;
|
||||
row_data.push(value);
|
||||
}
|
||||
/* println!(
|
||||
"Select Row Data: {}",
|
||||
&row_data.clone().join("").to_string()
|
||||
); */
|
||||
result.push(row_data);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
} */
|
||||
|
||||
/// Öffnet und initialisiert eine Datenbank mit Verschlüsselung
|
||||
pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connection, String> {
|
||||
@ -75,6 +233,9 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connectio
|
||||
conn.pragma_update(None, "key", key)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
conn.execute_batch("SELECT count(*) from haex_extensions")
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
// database/mod.rs
|
||||
pub mod core;
|
||||
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
use rusqlite::Connection;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
|
||||
|
||||
pub struct DbConnection(pub Mutex<Option<Connection>>);
|
||||
|
||||
// Öffentliche Funktionen für direkten Datenbankzugriff
|
||||
#[tauri::command]
|
||||
pub async fn sql_select(
|
||||
sql: String,
|
||||
params: Vec<String>,
|
||||
params: Vec<JsonValue>,
|
||||
state: State<'_, DbConnection>,
|
||||
) -> Result<Vec<Vec<String>>, String> {
|
||||
core::select(&sql, ¶ms, &state).await
|
||||
) -> Result<Vec<Vec<JsonValue>>, String> {
|
||||
core::select(sql, params, &state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sql_execute(
|
||||
sql: String,
|
||||
params: Vec<String>,
|
||||
params: Vec<JsonValue>,
|
||||
state: State<'_, DbConnection>,
|
||||
) -> Result<String, String> {
|
||||
core::execute(&sql, ¶ms, &state).await
|
||||
) -> Result<usize, String> {
|
||||
core::execute(sql, params, &state).await
|
||||
}
|
||||
|
||||
/// Erstellt eine verschlüsselte Datenbank
|
||||
@ -58,7 +58,62 @@ pub fn create_encrypted_database(
|
||||
}
|
||||
}
|
||||
|
||||
// Neue Datenbank erstellen
|
||||
//core::copy_file(&resource_path, &path)?;
|
||||
|
||||
println!(
|
||||
"Öffne unverschlüsselte Datenbank: {}",
|
||||
resource_path.as_path().display()
|
||||
);
|
||||
|
||||
let conn = Connection::open(&resource_path).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Öffnen der kopierten Datenbank: {}",
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
//let conn = Connection::open(&resource_path)?;
|
||||
|
||||
println!("Hänge neue, verschlüsselte Datenbank an unter '{}'", &path);
|
||||
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
|
||||
conn.execute("ATTACH DATABASE ?1 AS encrypted KEY ?2;", [&path, &key])
|
||||
.map_err(|e| format!("Fehler bei ATTACH DATABASE: {}", e.to_string()))?;
|
||||
|
||||
println!(
|
||||
"Exportiere Daten von 'main' nach 'encrypted' mit password {} ...",
|
||||
&key
|
||||
);
|
||||
|
||||
match conn.query_row("SELECT sqlcipher_export('encrypted');", [], |_row| Ok(())) {
|
||||
Ok(_) => {
|
||||
println!(">>> sqlcipher_export erfolgreich ausgeführt (Rückgabewert ignoriert).");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("!!! FEHLER während sqlcipher_export: {}", e);
|
||||
conn.execute("DETACH DATABASE encrypted;", []).ok(); // Versuche zu detachen
|
||||
return Err(e.to_string()); // Gib den Fehler zurück
|
||||
}
|
||||
}
|
||||
// sqlcipher_export('Alias') kopiert Schema und Daten von 'main' zur Alias-DB
|
||||
/* conn.execute("SELECT sqlcipher_export('encrypted');", [])
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Fehler bei SELECT sqlcipher_export('encrypted'): {}",
|
||||
e.to_string()
|
||||
)
|
||||
})?; */
|
||||
|
||||
println!("Löse die verschlüsselte Datenbank vom Handle...");
|
||||
conn.execute("DETACH DATABASE encrypted;", [])
|
||||
.map_err(|e| format!("Fehler bei DETACH DATABASE: {}", e.to_string()))?;
|
||||
|
||||
println!("Datenbank erfolgreich nach '{}' verschlüsselt.", &path);
|
||||
println!(
|
||||
"Die Originaldatei '{}' ist unverändert.",
|
||||
resource_path.as_path().display()
|
||||
);
|
||||
|
||||
/* // Neue Datenbank erstellen
|
||||
let conn = Connection::open_with_flags(
|
||||
&path,
|
||||
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
|
||||
@ -77,7 +132,7 @@ pub fn create_encrypted_database(
|
||||
"Fehler beim Testen der verschlüsselten Datenbank: {}",
|
||||
e.to_string()
|
||||
));
|
||||
}
|
||||
} */
|
||||
|
||||
// 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN
|
||||
println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'...");
|
||||
@ -88,7 +143,7 @@ pub fn create_encrypted_database(
|
||||
Ok(version) => {
|
||||
println!("SQLCipher ist aktiv! Version: {}", version);
|
||||
|
||||
// Fahre mit normalen Operationen fort
|
||||
/* // Fahre mit normalen Operationen fort
|
||||
println!("Erstelle Tabelle 'benutzer'...");
|
||||
conn.execute(
|
||||
"CREATE TABLE benutzer (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
|
||||
@ -100,7 +155,7 @@ pub fn create_encrypted_database(
|
||||
.map_err(|e| {
|
||||
format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string())
|
||||
})?;
|
||||
println!("Benutzer hinzugefügt.");
|
||||
println!("Benutzer hinzugefügt."); */
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!");
|
||||
|
||||
181
src-tauri/src/extension/core.rs
Normal file
181
src-tauri/src/extension/core.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use mime;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tauri::{
|
||||
http::{Request, Response, Uri},
|
||||
AppHandle, Error as TauriError, Manager, Runtime,
|
||||
};
|
||||
|
||||
pub fn copy_directory(source: String, destination: String) -> Result<(), String> {
|
||||
println!(
|
||||
"Kopiere Verzeichnis von '{}' nach '{}'",
|
||||
source, destination
|
||||
);
|
||||
|
||||
let source_path = PathBuf::from(&source);
|
||||
let destination_path = PathBuf::from(&destination);
|
||||
|
||||
if !source_path.exists() || !source_path.is_dir() {
|
||||
return Err(format!(
|
||||
"Quellverzeichnis '{}' nicht gefunden oder ist kein Verzeichnis.",
|
||||
source
|
||||
));
|
||||
}
|
||||
|
||||
// Optionen für fs_extra::dir::copy
|
||||
let mut options = fs_extra::dir::CopyOptions::new();
|
||||
options.overwrite = true; // Überschreibe Zieldateien, falls sie existieren
|
||||
options.copy_inside = true; // Kopiere den *Inhalt* des Quellordners in den Zielordner
|
||||
// options.content_only = true; // Alternative: nur Inhalt kopieren, Zielordner muss existieren
|
||||
options.buffer_size = 64000; // Standard-Puffergröße, kann angepasst werden
|
||||
|
||||
// Führe die Kopieroperation aus
|
||||
match fs_extra::dir::copy(&source_path, &destination_path, &options) {
|
||||
Ok(bytes_copied) => {
|
||||
println!("Verzeichnis erfolgreich kopiert ({} bytes)", bytes_copied);
|
||||
Ok(()) // Erfolg signalisieren
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Fehler beim Kopieren des Verzeichnisses: {}", e);
|
||||
Err(format!("Fehler beim Kopieren: {}", e.to_string())) // Fehler als String zurückgeben
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
extension_id: &str,
|
||||
requested_asset_path: &str,
|
||||
) -> Result<PathBuf, String> {
|
||||
// 1. Validiere die Extension ID
|
||||
if extension_id.is_empty()
|
||||
|| !extension_id
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '-')
|
||||
{
|
||||
return Err(format!("Ungültige Extension ID: {}", extension_id));
|
||||
}
|
||||
|
||||
// 2. Bestimme das Basisverzeichnis für alle Erweiterungen (Resource Directory)
|
||||
let base_extensions_dir = app_handle
|
||||
.path()
|
||||
.resource_dir() // Korrekt für Ressourcen
|
||||
// Wenn du stattdessen App Local Data willst: .app_local_data_dir()
|
||||
.map_err(|e: TauriError| format!("Basis-Verzeichnis nicht gefunden: {}", e))?
|
||||
.join("extensions");
|
||||
|
||||
// 3. Verzeichnis für die spezifische Erweiterung
|
||||
let specific_extension_dir = base_extensions_dir.join(extension_id);
|
||||
|
||||
// 4. Bereinige den angeforderten Asset-Pfad
|
||||
let clean_relative_path = requested_asset_path
|
||||
.replace('\\', "/")
|
||||
.trim_start_matches('/')
|
||||
.split('/')
|
||||
.filter(|&part| !part.is_empty() && part != "." && part != "..")
|
||||
.collect::<PathBuf>();
|
||||
|
||||
if clean_relative_path.as_os_str().is_empty() && requested_asset_path != "/" {
|
||||
return Err("Leerer oder ungültiger Asset-Pfad".to_string());
|
||||
}
|
||||
|
||||
// 5. Setze den finalen Pfad zusammen
|
||||
let final_path = specific_extension_dir.join(clean_relative_path);
|
||||
|
||||
// 6. SICHERHEITSCHECK (wie vorher)
|
||||
match final_path.canonicalize() {
|
||||
Ok(canonical_path) => {
|
||||
let canonical_base = specific_extension_dir.canonicalize().map_err(|e| {
|
||||
format!(
|
||||
"Kann Basis-Pfad '{}' nicht kanonisieren: {}",
|
||||
specific_extension_dir.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
if canonical_path.starts_with(&canonical_base) {
|
||||
Ok(canonical_path)
|
||||
} else {
|
||||
eprintln!( /* ... Sicherheitswarnung ... */ );
|
||||
Err("Ungültiger oder nicht erlaubter Asset-Pfad (kanonisch)".to_string())
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Fehler bei canonicalize (z.B. Pfad existiert nicht)
|
||||
if final_path.starts_with(&specific_extension_dir) {
|
||||
Ok(final_path) // Nicht-kanonisierten Pfad zurückgeben
|
||||
} else {
|
||||
eprintln!( /* ... Sicherheitswarnung ... */ );
|
||||
Err("Ungültiger oder nicht erlaubter Asset-Pfad (nicht kanonisiert)".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_extension_protocol<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
request: &Request<Vec<u8>>,
|
||||
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||
let uri_ref = request.uri(); // uri_ref ist &Uri
|
||||
println!("Protokoll Handler für: {}", uri_ref);
|
||||
|
||||
let uri_string = uri_ref.to_string(); // Konvertiere zu String
|
||||
let parsed_uri = Uri::from_str(&uri_string)?; // Parse aus &str
|
||||
|
||||
let extension_id = parsed_uri
|
||||
.host()
|
||||
.ok_or("Kein Host (Extension ID) in URI gefunden")?
|
||||
.to_string();
|
||||
|
||||
let requested_asset_path = parsed_uri.path();
|
||||
|
||||
let asset_path_to_load = if requested_asset_path == "/" || requested_asset_path.is_empty() {
|
||||
"index.html"
|
||||
} else {
|
||||
requested_asset_path
|
||||
};
|
||||
|
||||
// Sicheren Dateisystempfad auflösen (nutzt jetzt AppHandle)
|
||||
let absolute_secure_path =
|
||||
resolve_secure_extension_asset_path(app_handle, &extension_id, asset_path_to_load)?;
|
||||
|
||||
// Datei lesen und Response erstellen (Code wie vorher)
|
||||
match fs::read(&absolute_secure_path) {
|
||||
Ok(content) => {
|
||||
let mime_type = mime_guess::from_path(&absolute_secure_path)
|
||||
.first_or(mime::APPLICATION_OCTET_STREAM)
|
||||
.to_string();
|
||||
println!(
|
||||
"Liefere {} ({}) für Extension '{}'",
|
||||
absolute_secure_path.display(),
|
||||
mime_type,
|
||||
extension_id
|
||||
);
|
||||
// *** KORREKTUR: Verwende Response::builder() ***
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", mime_type) // Setze Header über .header()
|
||||
.body(content) // body() gibt Result<Response<Vec<u8>>, Error> zurück
|
||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Fehler beim Lesen der Datei {}: {}",
|
||||
absolute_secure_path.display(),
|
||||
e
|
||||
);
|
||||
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
|
||||
404
|
||||
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
403
|
||||
} else {
|
||||
500
|
||||
};
|
||||
// *** KORREKTUR: Verwende Response::builder() auch für Fehler ***
|
||||
Response::builder()
|
||||
.status(status_code)
|
||||
.body(Vec::new()) // Leerer Body für Fehler
|
||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
mod permissions;
|
||||
|
||||
use crate::database;
|
||||
use crate::database::DbConnection;
|
||||
use crate::models::ExtensionState;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
// Extension-bezogene Funktionen mit extension_-Präfix
|
||||
/// Lädt eine Extension aus einer Manifest-Datei
|
||||
/* #[tauri::command]
|
||||
@ -25,11 +27,11 @@ pub async fn extension_sql_select(
|
||||
app: AppHandle,
|
||||
extension_id: String,
|
||||
sql: String,
|
||||
params: Vec<String>,
|
||||
params: Vec<JsonValue>,
|
||||
state: State<'_, DbConnection>,
|
||||
) -> Result<Vec<Vec<String>>, String> {
|
||||
) -> Result<Vec<Vec<JsonValue>>, String> {
|
||||
permissions::check_read_permission(&app, &extension_id, &sql).await?;
|
||||
database::core::select(&sql, ¶ms, &state).await
|
||||
database::core::select(sql, params, &state).await
|
||||
}
|
||||
|
||||
/// Führt SQL-Schreiboperationen mit Berechtigungsprüfung aus
|
||||
@ -38,9 +40,9 @@ pub async fn extension_sql_execute(
|
||||
app: AppHandle,
|
||||
extension_id: String,
|
||||
sql: String,
|
||||
params: Vec<String>,
|
||||
params: Vec<JsonValue>,
|
||||
state: State<'_, DbConnection>,
|
||||
) -> Result<String, String> {
|
||||
) -> Result<usize, String> {
|
||||
permissions::check_write_permission(&app, &extension_id, &sql).await?;
|
||||
database::core::execute(&sql, ¶ms, &state).await
|
||||
database::core::execute(sql, params, &state).await
|
||||
}
|
||||
|
||||
@ -1 +1,8 @@
|
||||
pub mod core;
|
||||
pub mod database;
|
||||
use tauri;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn copy_directory(source: String, destination: String) -> Result<(), String> {
|
||||
core::copy_directory(source, destination)
|
||||
}
|
||||
|
||||
@ -9,7 +9,33 @@ use std::sync::Mutex;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let protocol_name = "haex-extension";
|
||||
|
||||
tauri::Builder::default()
|
||||
/* .register_uri_scheme_protocol(protocol_name, move |app_handle, request| {
|
||||
// Extrahiere den Request aus dem Kontext
|
||||
//let request = context.request();
|
||||
// Rufe die Handler-Logik auf
|
||||
match extension::core::handle_extension_protocol(0, &request) {
|
||||
Ok(response) => response, // Gib die erfolgreiche Response zurück
|
||||
Err(e) => {
|
||||
// Logge den Fehler
|
||||
eprintln!("Fehler im Protokoll-Handler für '{}': {}", request.uri(), e);
|
||||
// Gib eine generische 500er Fehler-Response zurück
|
||||
Response::builder()
|
||||
.status(500)
|
||||
.mimetype("text/plain") // Einfacher Text für die Fehlermeldung
|
||||
.body(format!("Internal Server Error: {}", e).into_bytes()) // Body als Vec<u8>
|
||||
.unwrap() // .body() kann hier nicht fehlschlagen
|
||||
}
|
||||
}
|
||||
}) */
|
||||
/* .setup(move |app| {
|
||||
// Der .setup Hook ist jetzt nur noch für andere Initialisierungen da
|
||||
// Der AppHandle ist hier nicht mehr nötig für die Protokoll-Registrierung
|
||||
println!("App Setup abgeschlossen.");
|
||||
Ok(())
|
||||
}) */
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.manage(DbConnection(Mutex::new(None)))
|
||||
.manage(ExtensionState::default())
|
||||
@ -26,7 +52,7 @@ pub fn run() {
|
||||
database::sql_select,
|
||||
extension::database::extension_sql_execute,
|
||||
extension::database::extension_sql_select,
|
||||
//browser::create_tab
|
||||
extension::copy_directory //browser::create_tab
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@ -18,7 +18,11 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["$RESOURCE/extensions/**"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
||||
102
src/components/haex/extension/manifest/confirm.vue
Normal file
102
src/components/haex/extension/manifest/confirm.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #extension>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<!-- {{ t("question", { extension: manifest?.name }) }}
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span> zu HaexHub hinzufügen? -->
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<HaexExtensionManifestPermissionsFilesystem
|
||||
v-if="manifest?.permissions?.filesystem"
|
||||
:filesystem="manifest?.permissions?.filesystem"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsDatabase
|
||||
v-if="manifest?.permissions?.database"
|
||||
:database="manifest?.permissions?.database"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsHttp
|
||||
v-if="manifest?.permissions?.http"
|
||||
:http="manifest?.permissions?.http"
|
||||
/>
|
||||
<!-- <VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("filesystem.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.filesystem }}
|
||||
</div>
|
||||
</VaultCard>
|
||||
|
||||
<VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("http.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.http }}
|
||||
</div>
|
||||
</VaultCard> -->
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton @click="onDeny" class="btn-error btn-outline">{{ t("deny") }} </UiButton>
|
||||
<UiButton @click="onConfirm" class="btn-success btn-outline">{{ t("confirm") }}</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
defineProps<{ manifest?: IHaexHubExtensionManifest }>();
|
||||
|
||||
const emit = defineEmits(["deny", "confirm"]);
|
||||
|
||||
const onDeny = () => {
|
||||
open.value = false;
|
||||
console.log("onDeny open", open.value);
|
||||
emit("deny");
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
console.log("onConfirm open", open.value);
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung hinzufügen",
|
||||
"question": "Möchtest du die Erweiterung {extension} hinzufügen?",
|
||||
"confirm": "Bestätigen",
|
||||
"deny": "Ablehnen",
|
||||
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
},
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
},
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("database.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="database?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="read in database?.read">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
|
||||
<div v-if="database?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in database?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ database: IHaexHubExtensionManifest["permissions"]["database"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("filesystem.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="filesystem?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="read in filesystem?.read"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
|
||||
<div v-if="filesystem?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in filesystem?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ filesystem: IHaexHubExtensionManifest["permissions"]["filesystem"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
43
src/components/haex/extension/manifest/permissions/http.vue
Normal file
43
src/components/haex/extension/manifest/permissions/http.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("http.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="http?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.access") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="access in http">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ access }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ http: IHaexHubExtensionManifest["permissions"]["http"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"access": "Zugriff"
|
||||
},
|
||||
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="text-base-content/50 px-4 py-2 text-md font-medium">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
43
src/components/ui/accordion/index.vue
Normal file
43
src/components/ui/accordion/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="accordion divide-neutral/20 divide-y">
|
||||
<div class="accordion-item active" :id="itemId" ref="accordionRef">
|
||||
<button
|
||||
class="accordion-toggle inline-flex items-center gap-x-4 text-start"
|
||||
:aria-controls="collapseId"
|
||||
aria-expanded="true"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="icon-[tabler--chevron-right] accordion-item-active:rotate-90 size-5 shrink-0 transition-transform duration-300 rtl:rotate-180"
|
||||
></span>
|
||||
<slot name="title" />
|
||||
</button>
|
||||
<div
|
||||
:id="collapseId"
|
||||
class="accordion-content w-full overflow-hidden transition-[height] duration-300"
|
||||
:aria-labelledby="itemId"
|
||||
role="region"
|
||||
>
|
||||
<div class="px-5 pb-4">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HSAccordion } from "flyonui/flyonui";
|
||||
|
||||
const itemId = useId();
|
||||
const collapseId = useId();
|
||||
|
||||
const accordionRef = useTemplateRef("accordionRef");
|
||||
const accordion = ref<HSAccordion>();
|
||||
|
||||
onMounted(() => {
|
||||
if (accordionRef.value) {
|
||||
accordion.value = new HSAccordion(accordionRef.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<slot
|
||||
name="trigger"
|
||||
:id
|
||||
>
|
||||
</slot>
|
||||
<slot name="trigger" :id> </slot>
|
||||
|
||||
<div
|
||||
:id
|
||||
@ -17,10 +13,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<slot name="title">
|
||||
<h3
|
||||
v-if="title"
|
||||
class="modal-title text-base sm:text-lg"
|
||||
>
|
||||
<h3 v-if="title" class="modal-title text-base sm:text-lg">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</slot>
|
||||
@ -32,10 +25,7 @@
|
||||
@click="open = false"
|
||||
tabindex="1"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="18"
|
||||
/>
|
||||
<Icon name="mdi:close" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-sm sm:text-base py-1">
|
||||
@ -50,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HSOverlay } from 'flyonui/flyonui';
|
||||
import { HSOverlay } from "flyonui/flyonui";
|
||||
|
||||
export interface IDom {
|
||||
class?: String;
|
||||
@ -63,38 +53,39 @@ defineProps({
|
||||
trigger: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: '',
|
||||
text: '',
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
|
||||
description: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: '',
|
||||
text: '',
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
|
||||
const { t } = useI18n();
|
||||
const modalRef = useTemplateRef('modalRef');
|
||||
const modalRef = useTemplateRef("modalRef");
|
||||
const modal = ref<HSOverlay>();
|
||||
|
||||
watch(open, async () => {
|
||||
console.log("open modal", open.value);
|
||||
if (open.value) {
|
||||
//console.log('open modal', modal.value?.open);
|
||||
await modal.value?.open();
|
||||
} else {
|
||||
await modal.value?.close(true);
|
||||
const res = await modal.value?.close(true);
|
||||
console.log("close dialog", res);
|
||||
}
|
||||
});
|
||||
|
||||
@ -102,8 +93,8 @@ onMounted(() => {
|
||||
if (!modalRef.value) return;
|
||||
modal.value = new HSOverlay(modalRef.value, { isClosePrev: true });
|
||||
|
||||
modal.value.on('close', () => {
|
||||
console.log('close it from event', open.value);
|
||||
modal.value.on("close", () => {
|
||||
console.log("close it from event", open.value);
|
||||
open.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
49
src/components/ui/sidebar/link/extension.vue
Normal file
49
src/components/ui/sidebar/link/extension.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<li
|
||||
@click="triggerNavigate"
|
||||
class="hover:text-primary rounded"
|
||||
:class="{ ['bg-base-300']: isActive }"
|
||||
>
|
||||
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
|
||||
<NuxtLinkLocale
|
||||
:to="{ name: 'haexExtension', params: { extensionId: props.id } }"
|
||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||
ref="link"
|
||||
>
|
||||
<div v-html="icon" class="shrink-0 size-6" />
|
||||
<!-- <Icon mode="svg" :name="icon" class="shrink-0 size-6" /> -->
|
||||
</NuxtLinkLocale>
|
||||
</UiTooltip>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ISidebarItem } from "#imports";
|
||||
|
||||
const props = defineProps<ISidebarItem>();
|
||||
console.log("image", props.icon);
|
||||
const router = useRouter();
|
||||
|
||||
const isActive = computed(() => {
|
||||
if (props.to?.name === "haexExtension") {
|
||||
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
||||
} else {
|
||||
return props.to?.name === router.currentRoute.value.meta.name;
|
||||
}
|
||||
});
|
||||
|
||||
const link = useTemplateRef("link");
|
||||
|
||||
const triggerNavigate = () => link.value?.$el.click();
|
||||
|
||||
/* computed(() => {
|
||||
const found = useRouter()
|
||||
.getRoutes()
|
||||
.find((route) => route.name === useLocaleRoute()(props.to)?.name);
|
||||
console.log('found route', found, useRoute());
|
||||
return (
|
||||
found?.name === useRoute().name ||
|
||||
found?.children.some((child) => child.name === useRoute().name)
|
||||
);
|
||||
}); */
|
||||
</script>
|
||||
@ -71,7 +71,8 @@ const { add } = useSnackbar();
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
isOpen.value = false;
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
console.log("handleError", error, typeof error);
|
||||
add({ type: "error", text: "Passwort falsch" });
|
||||
//console.error(error);
|
||||
};
|
||||
|
||||
@ -100,6 +101,7 @@ const onLoadDatabase = async () => {
|
||||
};
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
const onOpenDatabase = async () => {
|
||||
try {
|
||||
check.value = true;
|
||||
@ -120,7 +122,10 @@ const onOpenDatabase = async () => {
|
||||
});
|
||||
|
||||
if (!vaultId) {
|
||||
add({ type: "error", text: "Vault konnte nicht geöffnet werden" });
|
||||
add({
|
||||
type: "error",
|
||||
text: "Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,27 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-base-100 w-full mx-auto shadow h-full overflow-hidden pt-[7.5rem]"
|
||||
>
|
||||
<div class="card">
|
||||
<slot name="image" />
|
||||
|
||||
<div class="card-header">
|
||||
<h5 class="card-title" v-if="$slots.title">
|
||||
<slot name="title" />
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="bg-base-100 w-full mx-auto shadow h-full overflow-hidden pt-[7.5rem]">
|
||||
<div
|
||||
class="fixed top-0 right-0 z-10 transition-all duration-700 w-full font-semibold text-lg h-[7.5rem]"
|
||||
:class="{ 'pl-96': show }"
|
||||
>
|
||||
<div
|
||||
class="justify-center items-center flex flex-wrap border-b rounded-b border-secondary h-full"
|
||||
:class="{ 'pl-12': !show }"
|
||||
>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
@ -17,26 +30,25 @@
|
||||
<div class="h-full overflow-scroll bg-base-200">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { show } = storeToRefs(useSidebarStore());
|
||||
const emit = defineEmits(['close', 'submit']);
|
||||
const emit = defineEmits(["close", "submit"]);
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
watchEffect(async () => {
|
||||
if (escape.value) {
|
||||
await nextTick();
|
||||
emit('close');
|
||||
emit("close");
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
if (enter.value) {
|
||||
await nextTick();
|
||||
emit('submit');
|
||||
emit("submit");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { H3Error } from 'h3';
|
||||
import { H3Error } from "h3";
|
||||
|
||||
export const bytesToBase64DataUrlAsync = async (
|
||||
bytes: Uint8Array,
|
||||
type = 'application/octet-stream'
|
||||
type = "application/octet-stream"
|
||||
) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = Object.assign(new FileReader(), {
|
||||
onload: () => resolve(reader.result),
|
||||
onerror: () => reject(reader.error),
|
||||
});
|
||||
reader.readAsDataURL(new File([new Blob([bytes])], '', { type }));
|
||||
reader.readAsDataURL(new File([new Blob([bytes])], "", { type }));
|
||||
});
|
||||
};
|
||||
|
||||
export const blobToImageAsync = (blob: Blob): Promise<HTMLImageElement> => {
|
||||
return new Promise((resolve) => {
|
||||
console.log('transform blob', blob);
|
||||
console.log("transform blob", blob);
|
||||
const url = URL.createObjectURL(blob);
|
||||
let img = new Image();
|
||||
img.onload = () => {
|
||||
@ -34,7 +34,7 @@ export const deepToRaw = <T extends Record<string, any>>(sourceObj: T): T => {
|
||||
if (isRef(input) || isReactive(input) || isProxy(input)) {
|
||||
return objectIterator(toRaw(input));
|
||||
}
|
||||
if (input && typeof input === 'object') {
|
||||
if (input && typeof input === "object") {
|
||||
return Object.keys(input).reduce((acc, key) => {
|
||||
acc[key as keyof typeof acc] = objectIterator(input[key]);
|
||||
return acc;
|
||||
@ -48,10 +48,9 @@ export const deepToRaw = <T extends Record<string, any>>(sourceObj: T): T => {
|
||||
|
||||
export const readableFileSize = (sizeInByte: number | string = 0) => {
|
||||
if (!sizeInByte) {
|
||||
return '0 KB';
|
||||
return "0 KB";
|
||||
}
|
||||
const size =
|
||||
typeof sizeInByte === 'string' ? parseInt(sizeInByte) : sizeInByte;
|
||||
const size = typeof sizeInByte === "string" ? parseInt(sizeInByte) : sizeInByte;
|
||||
const sizeInKb = size / 1024;
|
||||
const sizeInMb = sizeInKb / 1024;
|
||||
const sizeInGb = sizeInMb / 1024;
|
||||
@ -64,20 +63,17 @@ export const readableFileSize = (sizeInByte: number | string = 0) => {
|
||||
return `${sizeInKb.toFixed(2)} KB`;
|
||||
};
|
||||
|
||||
import type { LocationQueryValue, RouteLocationRawI18n } from 'vue-router';
|
||||
import type { LocationQueryValue, RouteLocationRawI18n } from "vue-router";
|
||||
|
||||
export const getSingleRouteParam = (
|
||||
param: string | string[] | LocationQueryValue | LocationQueryValue[]
|
||||
): string => {
|
||||
const _param = Array.isArray(param) ? param.at(0) ?? '' : param ?? '';
|
||||
const _param = Array.isArray(param) ? param.at(0) ?? "" : param ?? "";
|
||||
//console.log('found param', _param, param);
|
||||
return _param;
|
||||
return decodeURIComponent(_param);
|
||||
};
|
||||
|
||||
export const isRouteActive = (
|
||||
to: RouteLocationRawI18n,
|
||||
exact: boolean = false
|
||||
) =>
|
||||
export const isRouteActive = (to: RouteLocationRawI18n, exact: boolean = false) =>
|
||||
computed(() => {
|
||||
const found = useRouter()
|
||||
.getRoutes()
|
||||
@ -86,9 +82,7 @@ export const isRouteActive = (
|
||||
return exact
|
||||
? found?.name === useRouter().currentRoute.value.name
|
||||
: found?.name === useRouter().currentRoute.value.name ||
|
||||
found?.children.some(
|
||||
(child) => child.name === useRouter().currentRoute.value.name
|
||||
);
|
||||
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
||||
});
|
||||
|
||||
export const isKey = <T extends object>(x: T, k: PropertyKey): k is keyof T => {
|
||||
|
||||
@ -1,32 +1,27 @@
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col">
|
||||
<div class="w-full h-full flex flex-col min-w-min">
|
||||
<nav
|
||||
class="navbar bg-base-100 max-sm:rounded-box max-sm:shadow sm:border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text btn-square me-2"
|
||||
class="btn btn-text btn-square me-2 z-50"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
data-overlay="#sidebar"
|
||||
@click="toogleSidebar"
|
||||
ref="sidebarToogleRef"
|
||||
>
|
||||
<Icon name="mage:dash-menu" size="28" />
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="btn btn-text max-sm:btn-square me-2"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
data-overlay="#sidebar"
|
||||
>
|
||||
<span class="icon-[tabler--menu-2] size-5"></span>
|
||||
</button> -->
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
<a class="link text-base-content link-neutral text-xl font-semibold no-underline" href="#">
|
||||
<UiTextGradient>Haex Hub</UiTextGradient>
|
||||
</a>
|
||||
<NuxtLinkLocale
|
||||
class="link text-base-content link-neutral text-xl font-semibold no-underline"
|
||||
:to="{ name: 'vaultOverview' }"
|
||||
>
|
||||
<UiTextGradient class="text-nowrap">Haex Hub</UiTextGradient>
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
<div class="navbar-end flex items-center gap-4 me-4">
|
||||
<div
|
||||
@ -158,53 +153,35 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="overlay sm:shadow-none overlay-open:translate-x-0 drawer drawer-start hidden sm:absolute max-w-14 sm:flex sm:translate-x-0 sm:pt-12 z-10"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="drawer-body px-0 pt-4">
|
||||
<ul class="menu p-0">
|
||||
<!-- <li
|
||||
|
||||
|
||||
<div class="flex h-full">
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="sm:shadow-none drawer max-w-14 transition-all"
|
||||
:class="[!isVisible ? 'w-0' : 'w-14']"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="drawer-body px-0">
|
||||
<ul class="menu p-0">
|
||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<UiSidebarLinkExtension
|
||||
v-bind="item"
|
||||
v-for="item in availableExtensions"
|
||||
:key="item.id"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
> -->
|
||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<!-- <UiTooltip
|
||||
:tooltip="item.tooltip || item.name"
|
||||
direction="right-start"
|
||||
>
|
||||
<NuxtLinkLocale
|
||||
:class="{ ['bg-base-300']: isActive(item.id).value }"
|
||||
:to="{
|
||||
name: 'extension',
|
||||
params: { extensionId: item.id, vaultId: 'test' },
|
||||
}"
|
||||
class="flex items-center justify-start hover:text-primary cursor-pointer tooltip-toogle"
|
||||
>
|
||||
<Icon
|
||||
:name="item.icon"
|
||||
class="size-6"
|
||||
/>
|
||||
<i
|
||||
:class="item.icon"
|
||||
class="shrink-0 size-8"
|
||||
/>
|
||||
</NuxtLinkLocale>
|
||||
</UiTooltip> -->
|
||||
<!-- </li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="overflow-hidden transition-all relative w-full">
|
||||
<div class="h-full overflow-scroll sm:pl-14">
|
||||
<slot />
|
||||
<div class="overflow-hidden transition-all relative w-full">
|
||||
<div
|
||||
class="h-full overflow-scroll transition-all pl-0"
|
||||
:class="[isVisible ? 'sm:pl-14 ' : ' pl-0']"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <main class="sm:pl-14">
|
||||
<NuxtPage />
|
||||
</main> -->
|
||||
@ -212,14 +189,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
import { NuxtLinkLocale } from "#components";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { menu, isVisible } = storeToRefs(useSidebarStore());
|
||||
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
|
||||
onClickOutside(sidebarToogleRef, () => {
|
||||
if (currentScreenSize.value === "xs") {
|
||||
isVisible.value = false;
|
||||
}
|
||||
});
|
||||
const { notifications } = storeToRefs(useNotificationStore());
|
||||
|
||||
const { menu } = storeToRefs(useSidebarStore());
|
||||
const { isActive } = useExtensionsStore();
|
||||
const { closeAsync } = useVaultStore();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
const onExtensionSelectAsync = async (id: string) => {};
|
||||
const { availableExtensions } = storeToRefs(useExtensionsStore());
|
||||
const toogleSidebar = () => {
|
||||
isVisible.value = !isVisible.value;
|
||||
};
|
||||
|
||||
const onVaultCloseAsync = async () => {
|
||||
await closeAsync();
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div class="text-white">
|
||||
<NuxtLayout name="app">
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
<div class="text-white h-full">
|
||||
<NuxtLayout name="app">
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { createTable } = useVaultStore();
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
{{ iframeSrc }}
|
||||
<div class="w-full h-full">
|
||||
<iframe class="w-full h-full" @load="" ref="iFrameRef"> </iframe>
|
||||
<p>{{ t("loading") }}</p>
|
||||
<iframe
|
||||
v-if="iframeSrc"
|
||||
class="w-full h-full"
|
||||
@load=""
|
||||
ref="iFrameRef"
|
||||
:src="iframeSrc"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
>
|
||||
</iframe>
|
||||
<p v-else>{{ t("loading") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -10,8 +19,21 @@ definePageMeta({
|
||||
name: "haexExtension",
|
||||
});
|
||||
|
||||
const iframeRef = useTemplateRef("iFrameRef");
|
||||
const { t } = useI18n();
|
||||
const iframeRef = useTemplateRef("iFrameRef");
|
||||
|
||||
const { extensionEntry: iframeSrc, currentExtension } = storeToRefs(useExtensionsStore());
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
watch(iframeSrc, () => console.log("iframeSrc", iframeSrc.value), { immediate: true });
|
||||
|
||||
onMounted(async () => {
|
||||
const minfest = await extensionStore.readManifestFileAsync(
|
||||
currentExtension.value!.id,
|
||||
currentExtension.value!.version
|
||||
);
|
||||
console.log("manifest", minfest, extensionStore.extensionEntry);
|
||||
});
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -1,9 +1,92 @@
|
||||
<template>
|
||||
<div>add extension</div>
|
||||
<div class="flex flex-col">
|
||||
<h1>{{ t("title") }}</h1>
|
||||
<UiButton @click="loadExtensionManifestAsync">
|
||||
{{ t("extension.add") }}
|
||||
</UiButton>
|
||||
|
||||
<HaexExtensionManifestConfirm
|
||||
:manifest="extension.manifest!"
|
||||
v-model:open="showConfirmation"
|
||||
@confirm="addExtensionAsync"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
|
||||
definePageMeta({
|
||||
name: "haexExtensionAdd",
|
||||
name: "extensionOverview",
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
const showConfirmation = ref(false);
|
||||
|
||||
const extension = reactive<{
|
||||
manifest: IHaexHubExtensionManifest | null | undefined;
|
||||
path: string | null;
|
||||
}>({
|
||||
manifest: null,
|
||||
path: "",
|
||||
});
|
||||
|
||||
const loadExtensionManifestAsync = async () => {
|
||||
try {
|
||||
extension.path = await open({ directory: true, recursive: true });
|
||||
if (!extension.path) return;
|
||||
|
||||
const manifestFile = JSON.parse(
|
||||
await readTextFile(await join(extension.path, "manifest.json"))
|
||||
);
|
||||
|
||||
if (!extensionStore.checkManifest(manifestFile))
|
||||
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`);
|
||||
|
||||
extension.manifest = manifestFile;
|
||||
showConfirmation.value = true;
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const { add } = useSnackbar();
|
||||
|
||||
const addExtensionAsync = async () => {
|
||||
try {
|
||||
await extensionStore.installAsync(extension.path);
|
||||
await extensionStore.loadExtensionsAsync();
|
||||
console.log("Modul erfolgreich geladen");
|
||||
add({
|
||||
type: "success",
|
||||
title: t("extension.success.title", { extension: extension.manifest?.name }),
|
||||
text: t("extension.success.text"),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung installieren",
|
||||
"extension": {
|
||||
"add": "Erweiterung hinzufügen",
|
||||
"success": {
|
||||
"title": "{extension} hinzugefügt",
|
||||
"text": "Die Erweiterung wurde erfolgreich hinzugefügt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"title": "Install extension"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
<template>
|
||||
<div class="h-screen bg-blue-200"></div>
|
||||
<div class="bg-blue-200 h-full">aaaaa</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
name: "vaultOverview",
|
||||
name: "vaultOverview",
|
||||
});
|
||||
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
onMounted(async () => {
|
||||
await extensionStore.loadExtensionsAsync();
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -2,19 +2,29 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import { join, resourceDir } from "@tauri-apps/api/path";
|
||||
import { readTextFile, readDir } from "@tauri-apps/plugin-fs";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { haexExtensions } from "~~/src-tauri/database/schemas/vault";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const manifestFileName = "manifest.json";
|
||||
const logoFileName = "logo.svg";
|
||||
|
||||
export interface IHaexHubExtensionLink {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
version: string;
|
||||
manifest?: IHaexHubExtensionManifest;
|
||||
}
|
||||
|
||||
export interface IHaexHubExtensionManifest {
|
||||
name: string;
|
||||
id: string;
|
||||
entry: string;
|
||||
author: string;
|
||||
url: string;
|
||||
version: string;
|
||||
icon: string;
|
||||
permissions: {
|
||||
database?: {
|
||||
read?: string[];
|
||||
@ -30,80 +40,142 @@ export interface IHaexHubExtensionManifest {
|
||||
}
|
||||
|
||||
export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
},
|
||||
|
||||
{
|
||||
id: "extensions",
|
||||
name: "sidebar.extensions",
|
||||
icon: "gg:extension",
|
||||
},
|
||||
|
||||
{
|
||||
id: "settings",
|
||||
name: "sidebar.settings",
|
||||
icon: "ph:gear-six",
|
||||
},
|
||||
]);
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
|
||||
|
||||
const currentRoute = useRouter().currentRoute;
|
||||
|
||||
const isActive = (id: string) =>
|
||||
computed(
|
||||
() =>
|
||||
currentRoute.value.name === "extension" &&
|
||||
currentRoute.value.params.extensionId === id
|
||||
() => currentRoute.value.name === "extension" && currentRoute.value.params.extensionId === id
|
||||
);
|
||||
|
||||
const currentExtension = computed(() => {
|
||||
if (currentRoute.value.name !== "haexExtension") return;
|
||||
|
||||
const extensionId = getSingleRouteParam(
|
||||
currentRoute.value.params.extensionId
|
||||
);
|
||||
console.log("computed currentExtension", currentRoute.value.params);
|
||||
if (currentRoute.value.meta.name !== "haexExtension") return;
|
||||
|
||||
const extensionId = getSingleRouteParam(currentRoute.value.params.extensionId);
|
||||
console.log("extensionId from param", extensionId);
|
||||
if (!extensionId) return;
|
||||
|
||||
return availableExtensions.value.find(
|
||||
(extension) => extension.id === extensionId
|
||||
);
|
||||
const extension = availableExtensions.value.find((extension) => extension.id === extensionId);
|
||||
console.log("currentExtension", extension);
|
||||
return extension;
|
||||
});
|
||||
|
||||
const checkExtensionDirectoryAsync = async (extensionDirectory: string) => {
|
||||
const getExtensionPathAsync = async (extensionId?: string, version?: string) => {
|
||||
if (!extensionId || !version) return "";
|
||||
return await join(await resourceDir(), "extensions", extensionId, version);
|
||||
};
|
||||
|
||||
const checkSourceExtensionDirectoryAsync = async (extensionDirectory: string) => {
|
||||
try {
|
||||
const dir = await readDir(extensionDirectory);
|
||||
const manifest = dir.find(
|
||||
(entry) => entry.name === manifestFileName && entry.isFile
|
||||
);
|
||||
const manifest = dir.find((entry) => entry.name === manifestFileName && entry.isFile);
|
||||
if (!manifest) throw new Error("Kein Manifest für Erweiterung gefunden");
|
||||
|
||||
const logo = dir.find((item) => item.isFile && item.name === logoFileName);
|
||||
if (!logo) throw new Error("Logo fehlt");
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Keine Leseberechtigung für Ordner ${extensionDirectory}`
|
||||
);
|
||||
throw new Error(`Keine Leseberechtigung für Ordner ${extensionDirectory}`);
|
||||
}
|
||||
};
|
||||
|
||||
const installAsync = async (
|
||||
extensionDirectory: string | null,
|
||||
global: boolean = true
|
||||
): Promise<void> => {
|
||||
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
||||
const errors = [];
|
||||
|
||||
if (typeof manifestFile !== "object" || manifestFile === null) {
|
||||
errors.push("Manifest ist falsch");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!("id" in manifestFile) || typeof manifestFile.id !== "string")
|
||||
errors.push("Keine ID vergeben");
|
||||
|
||||
if (!("name" in manifestFile) || typeof manifestFile.name !== "string")
|
||||
errors.push("Name fehlt");
|
||||
|
||||
if (!("entry" in manifestFile) || typeof manifestFile.entry !== "string")
|
||||
errors.push("Entry fehlerhaft");
|
||||
|
||||
if (!("author" in manifestFile) || typeof manifestFile.author !== "string")
|
||||
errors.push("Author fehlt");
|
||||
|
||||
if (!("url" in manifestFile) || typeof manifestFile.url !== "string") errors.push("Url fehlt");
|
||||
|
||||
if (!("version" in manifestFile) || typeof manifestFile.version !== "string")
|
||||
errors.push("Version fehlt");
|
||||
|
||||
if (
|
||||
!("permissions" in manifestFile) ||
|
||||
typeof manifestFile.permissions !== "object" ||
|
||||
manifestFile.permissions === null
|
||||
) {
|
||||
errors.push("Berechtigungen fehlen");
|
||||
}
|
||||
|
||||
if (errors.length) throw errors;
|
||||
|
||||
/* const permissions = manifestFile.permissions as Partial<IHaexHubExtensionManifest["permissions"]>;
|
||||
if (
|
||||
("database" in permissions &&
|
||||
(typeof permissions.database !== "object" || permissions.database === null)) ||
|
||||
("filesystem" in permissions && typeof permissions.filesystem !== "object") ||
|
||||
permissions.filesystem === null
|
||||
) {
|
||||
return false;
|
||||
} */
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const readManifestFileAsync = async (extensionId: string, version: string | number) => {
|
||||
try {
|
||||
if (!extensionDirectory)
|
||||
throw new Error("Kein Ordner für Erweiterung angegeben");
|
||||
const checkDirectory = await checkExtensionDirectoryAsync(
|
||||
extensionDirectory
|
||||
);
|
||||
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
||||
const manifestPath = await join(extensionPath, manifestFileName);
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
|
||||
const manifestPath = await join(extensionDirectory, "manifest.json");
|
||||
const manifest = await JSON.parse(await readTextFile(manifestPath));
|
||||
/*
|
||||
TODO implement await checkManifets(manifest);
|
||||
*/
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
console.error("ERROR readManifestFileAsync", error);
|
||||
}
|
||||
};
|
||||
|
||||
console.log("manifest", manifest);
|
||||
return;
|
||||
const installAsync = async (extensionDirectory: string | null, global: boolean = true) => {
|
||||
try {
|
||||
if (!extensionDirectory) throw new Error("Kein Ordner für Erweiterung angegeben");
|
||||
const manifestPath = await join(extensionDirectory, manifestFileName);
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
|
||||
const destination = await getExtensionPathAsync(manifest.id, manifest.version);
|
||||
|
||||
await checkSourceExtensionDirectoryAsync(extensionDirectory);
|
||||
|
||||
await invoke("copy_directory", { source: extensionDirectory, destination });
|
||||
|
||||
const logoFilePath = await join(destination, "logo.svg");
|
||||
const logoSvg = await readTextFile(logoFilePath);
|
||||
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
const res = await currentVault.value?.drizzle.insert(haexExtensions).values({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
author: manifest.author,
|
||||
enabled: true,
|
||||
url: manifest.url,
|
||||
version: manifest.version,
|
||||
icon: logoSvg,
|
||||
});
|
||||
|
||||
console.log("insert extensions", res);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
/*
|
||||
@ -213,9 +285,158 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
}
|
||||
};
|
||||
|
||||
const extensionEntry = computedAsync(
|
||||
async () => {
|
||||
console.log("extensionEntry start", currentExtension.value);
|
||||
const regex = /((href|src)=["'])([^"']+)(["'])/g;
|
||||
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
||||
console.log("extension id or entry missing", currentExtension.value);
|
||||
return "no mani: " + currentExtension.value;
|
||||
}
|
||||
//return "wadahadedzdz";
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
currentExtension.value?.id,
|
||||
currentExtension.value?.version
|
||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
|
||||
console.log("extensionEntry extensionPath", extensionPath);
|
||||
const manifest = await readManifestFileAsync(
|
||||
currentExtension.value.id,
|
||||
currentExtension.value.version
|
||||
);
|
||||
|
||||
if (!manifest) return "no manifest readable";
|
||||
|
||||
const entryPath = await join(extensionPath, manifest.entry);
|
||||
|
||||
return `asset://localhost/${entryPath}`;
|
||||
let entryHtml = await readTextFile(entryPath);
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
const replacements = [];
|
||||
let match;
|
||||
while ((match = regex.exec(entryHtml)) !== null) {
|
||||
const [fullMatch, prefix, attr, resource, suffix] = match;
|
||||
if (!resource.startsWith("http")) {
|
||||
replacements.push({ match: fullMatch, resource, prefix, suffix });
|
||||
}
|
||||
}
|
||||
|
||||
for (const { match, resource, prefix, suffix } of replacements) {
|
||||
const fileContent = await readTextFile(await join(extensionPath, resource));
|
||||
const blob = new Blob([fileContent], { type: getMimeType(resource) });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
console.log("blob", resource, blobUrl);
|
||||
entryHtml = entryHtml.replace(match, `${prefix}${blobUrl}${suffix}`);
|
||||
}
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
|
||||
const blob = new Blob([entryHtml], { type: "text/html" });
|
||||
const iframeSrc = URL.createObjectURL(blob);
|
||||
|
||||
console.log("iframeSrc", iframeSrc);
|
||||
|
||||
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
||||
console.log("final path", path); */
|
||||
//manifest.entry = iframeSrc;
|
||||
return iframeSrc;
|
||||
/* await join(
|
||||
path, //`file:/${extensionDirectory}`,
|
||||
manifest.entry
|
||||
); */
|
||||
// Modul-Datei laden
|
||||
//const modulePathFull = await join(basePath, manifest.main);
|
||||
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
||||
manifestPath,
|
||||
}); */
|
||||
/* const iframe = document.createElement('iframe');
|
||||
iframe.src = manifest.entry;
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '100%';
|
||||
iframe.style.border = 'none'; */
|
||||
/* const addonApi = {
|
||||
db_execute: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_execute', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
db_select: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_select', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
}; */
|
||||
/* iframe.onload = () => {
|
||||
iframe.contentWindow?.postMessage(
|
||||
{ type: 'init', payload: addonApi },
|
||||
'*'
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source === iframe.contentWindow) {
|
||||
const { type } = event.data;
|
||||
if (type === 'ready') {
|
||||
console.log(`Plugin ${manifest.name} ist bereit`);
|
||||
}
|
||||
}
|
||||
}); */
|
||||
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
|
||||
|
||||
console.log(`Plugin ${manifest.name} geladen.`); */
|
||||
},
|
||||
null,
|
||||
{ lazy: true }
|
||||
);
|
||||
|
||||
const loadExtensionsAsync = async () => {
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
|
||||
/* const query = db
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
//.where(sql`${haexExtensions.enabled} = "1"`);
|
||||
.where(eq(haexExtensions.enabled, true)); */
|
||||
const extensions = await currentVault.value?.drizzle
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
.where(eq(haexExtensions.enabled, true));
|
||||
|
||||
//const manifest = readTextFile(manifestFileName)
|
||||
//const { sql, params } = query.toSQL();
|
||||
//const extensions = await invoke<any>("sql_select", { sql, params });
|
||||
console.log("loadExtensionsAsync ", extensions);
|
||||
availableExtensions.value =
|
||||
extensions?.map((extension) => ({
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
icon: extension.icon ?? "",
|
||||
tooltip: extension.name ?? "",
|
||||
version: extension.version ?? "",
|
||||
})) ?? [];
|
||||
};
|
||||
|
||||
return {
|
||||
availableExtensions,
|
||||
checkManifest,
|
||||
currentExtension,
|
||||
extensionEntry,
|
||||
installAsync,
|
||||
isActive,
|
||||
loadExtensionsAsync,
|
||||
readManifestFileAsync,
|
||||
};
|
||||
});
|
||||
|
||||
const getMimeType = (file: string) => {
|
||||
if (file.endsWith(".css")) return "text/css";
|
||||
if (file.endsWith(".js")) return "text/javascript";
|
||||
return "text/plain";
|
||||
};
|
||||
|
||||
@ -2,31 +2,33 @@ import { getSingleRouteParam } from "~/composables/helper";
|
||||
import type { RouteLocationRaw, RouteLocationAsRelativeGeneric } from "vue-router";
|
||||
|
||||
export interface ISidebarItem {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
}
|
||||
|
||||
export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
const menu = ref<ISidebarItem[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
to: { name: "haexBrowser" },
|
||||
},
|
||||
const isVisible = ref(true);
|
||||
|
||||
{
|
||||
id: "haex-extensions-add",
|
||||
name: "Haex Extensions",
|
||||
icon: "gg:extension",
|
||||
to: { name: "haexExtensionAdd" },
|
||||
},
|
||||
]);
|
||||
const menu = ref<ISidebarItem[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
to: { name: "haexBrowser" },
|
||||
},
|
||||
|
||||
/* const loadAsync = async (id: string) => {
|
||||
{
|
||||
id: "haex-extensions-add",
|
||||
name: "Haex Extensions",
|
||||
icon: "gg:extension",
|
||||
to: { name: "extensionOverview" },
|
||||
},
|
||||
]);
|
||||
|
||||
/* const loadAsync = async (id: string) => {
|
||||
extensions.value.some(async (extension) => {
|
||||
if (extension.id === id) {
|
||||
await navigateTo(
|
||||
@ -37,8 +39,9 @@ export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
});
|
||||
}; */
|
||||
|
||||
return {
|
||||
menu,
|
||||
//loadAsync,
|
||||
};
|
||||
return {
|
||||
menu,
|
||||
isVisible,
|
||||
//loadAsync,
|
||||
};
|
||||
});
|
||||
|
||||
@ -57,85 +57,83 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
);
|
||||
|
||||
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
||||
const sqlitePath = path?.startsWith("sqlite:") ? path : `sqlite:${path}`;
|
||||
try {
|
||||
console.log("try to open db", path, password);
|
||||
|
||||
console.log("try to open db", path, password);
|
||||
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
path,
|
||||
key: password,
|
||||
});
|
||||
|
||||
console.log("open vault from store", result);
|
||||
if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
||||
//const db = await Database.load(sqlitePath);
|
||||
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
console.log("opened db fileName", fileName, vaultId);
|
||||
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
[vaultId]: {
|
||||
//database: db,
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
path,
|
||||
password,
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params, method) => {
|
||||
let rows: any = [];
|
||||
let results = [];
|
||||
key: password,
|
||||
});
|
||||
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
rows = await invoke("db_select", { sql, params }).catch((e) => {
|
||||
console.error("SQL Error:", e);
|
||||
return [];
|
||||
});
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await invoke("db_execute", { sql, params }).catch((e) => {
|
||||
console.error("SQL Error:", e);
|
||||
return [];
|
||||
});
|
||||
return { rows: [] };
|
||||
}
|
||||
console.log("open vault from store", result);
|
||||
//const db = await Database.load(sqlitePath);
|
||||
|
||||
rows = rows.map((row: any) => {
|
||||
return Object.values(row);
|
||||
});
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
console.log("opened db fileName", fileName, vaultId);
|
||||
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0];
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
[vaultId]: {
|
||||
//database: db,
|
||||
path,
|
||||
password,
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params: unknown[], method) => {
|
||||
let rows: any = [];
|
||||
let results = [];
|
||||
|
||||
return { rows: results };
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
{ schema: schema, logger: true }
|
||||
),
|
||||
},
|
||||
};
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
console.log("sql_select", sql, params);
|
||||
rows = await invoke("sql_select", { sql, params }).catch((e) => {
|
||||
console.error("SQL select Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
console.log("select", rows);
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await invoke("sql_execute", { sql, params }).catch((e) => {
|
||||
console.error("SQL execute Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
return { rows: [] };
|
||||
}
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
await addVaultAsync({ path });
|
||||
/* rows = rows.map((row: any) => {
|
||||
return Object.values(row);
|
||||
}); */
|
||||
|
||||
return vaultId;
|
||||
};
|
||||
console.log("select after map", rows);
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0];
|
||||
|
||||
const createTable = () => {
|
||||
console.log("ddd", schema.testTable.getSQL().queryChunks);
|
||||
return { rows: results };
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
{ schema: schema, logger: true }
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
schema.testTable.getSQL().queryChunks.forEach((chunk) => {
|
||||
const res = currentVault.value?.drizzle.run(chunk);
|
||||
console.log("create table", res);
|
||||
});
|
||||
//if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
await addVaultAsync({ path });
|
||||
|
||||
return vaultId;
|
||||
} catch (error) {
|
||||
console.error("Error openAsync ", error);
|
||||
return false;
|
||||
//if (error === "file is not a database") throw new Error("Passwort falsch");
|
||||
}
|
||||
};
|
||||
|
||||
const testDatabaseReadAsync = async () => {
|
||||
try {
|
||||
currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
||||
return true;
|
||||
return currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
@ -186,7 +184,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
openVaults,
|
||||
refreshDatabaseAsync,
|
||||
read_only,
|
||||
createTable,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user