From f1867e7916019067b6f287471c52c536645f5867 Mon Sep 17 00:00:00 2001
From: LittleSheep
Date: Sat, 26 Jul 2025 03:11:42 +0800
Subject: [PATCH] :sparkles: File upload frontpage and download decryption
---
DysonNetwork.Drive/Client/bun.lock | 138 +--------
DysonNetwork.Drive/Client/package.json | 10 +-
DysonNetwork.Drive/Client/src/router/index.ts | 5 +
DysonNetwork.Drive/Client/src/stores/user.ts | 2 +-
DysonNetwork.Drive/Client/src/views/files.vue | 36 +++
DysonNetwork.Drive/Client/src/views/index.vue | 143 +++++++--
DysonNetwork.Drive/Client/src/views/secure.ts | 92 ++++++
DysonNetwork.Drive/Client/vite.config.ts | 4 +-
...83846_EnrichCloudPoolConfigure.Designer.cs | 277 ++++++++++++++++++
...20250725183846_EnrichCloudPoolConfigure.cs | 73 +++++
...0250725184107_NullableFileMeta.Designer.cs | 276 +++++++++++++++++
.../20250725184107_NullableFileMeta.cs | 36 +++
.../Migrations/AppDatabaseModelSnapshot.cs | 21 +-
DysonNetwork.Drive/Storage/CloudFile.cs | 4 +-
DysonNetwork.Drive/Storage/FilePool.cs | 7 +-
DysonNetwork.Drive/Storage/FileService.cs | 138 +++++----
DysonNetwork.Drive/Storage/TusService.cs | 11 +-
DysonNetwork.Drive/appsettings.json | 3 +-
DysonNetwork.Pass/Client/vite.config.ts | 4 +-
19 files changed, 1051 insertions(+), 229 deletions(-)
create mode 100644 DysonNetwork.Drive/Client/src/views/files.vue
create mode 100644 DysonNetwork.Drive/Client/src/views/secure.ts
create mode 100644 DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs
create mode 100644 DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.cs
create mode 100644 DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs
create mode 100644 DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.cs
diff --git a/DysonNetwork.Drive/Client/bun.lock b/DysonNetwork.Drive/Client/bun.lock
index 62b9b37..ff5412a 100644
--- a/DysonNetwork.Drive/Client/bun.lock
+++ b/DysonNetwork.Drive/Client/bun.lock
@@ -8,20 +8,12 @@
"@fontsource-variable/nunito": "^5.2.6",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@tailwindcss/vite": "^4.1.11",
- "@uppy/core": "^4.4.7",
- "@uppy/dashboard": "^4.3.4",
- "@uppy/drag-drop": "^4.1.3",
- "@uppy/drop-target": "^3.1.1",
- "@uppy/file-input": "^4.1.3",
- "@uppy/image-editor": "^3.3.3",
- "@uppy/progress-bar": "^4.2.1",
- "@uppy/tus": "^4.2.2",
- "@uppy/vue": "^2.3.0",
"@vueuse/core": "^13.5.0",
"aspnet-prerendering": "^3.0.1",
"cfturnstile-vue3": "^2.0.0",
"pinia": "^3.0.3",
"tailwindcss": "^4.1.11",
+ "tus-js-client": "^4.3.1",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
},
@@ -198,34 +190,6 @@
"@oxlint/win32-x64": ["@oxlint/win32-x64@1.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-x6r5yvM3wEty93Bx0NuNK+kutUyS/K55itkUrxdExoK6GcmVDboGGuhju9HyU2cM/IWLEWO8RHcXSyaxr9GR5g=="],
- "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
-
- "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
-
- "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="],
-
- "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="],
-
- "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="],
-
- "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="],
-
- "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="],
-
- "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="],
-
- "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="],
-
- "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="],
-
- "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="],
-
- "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="],
-
- "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="],
-
- "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
-
"@pkgr/core": ["@pkgr/core@0.2.7", "", {}, "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
@@ -266,8 +230,6 @@
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
- "@tailwindcss/cli": ["@tailwindcss/cli@4.1.11", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "enhanced-resolve": "^5.18.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.11" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ=="],
-
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
@@ -298,8 +260,6 @@
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
- "@transloadit/prettier-bytes": ["@transloadit/prettier-bytes@0.3.5", "", {}, "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA=="],
-
"@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
@@ -316,8 +276,6 @@
"@types/node": ["@types/node@22.16.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g=="],
- "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
-
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.37.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/type-utils": "8.37.0", "@typescript-eslint/utils": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA=="],
@@ -340,40 +298,6 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w=="],
- "@uppy/companion-client": ["@uppy/companion-client@4.4.2", "", { "dependencies": { "@uppy/utils": "^6.1.4", "namespace-emitter": "^2.0.1", "p-retry": "^6.1.0" }, "peerDependencies": { "@uppy/core": "^4.4.5" } }, "sha512-UZlHWItCGlZMlHxH4RgytUg43UZyuUX1JuvborNW1OzlLeZTvxL1dhjaq8pgjx3TlClkfpKLRRF8II0XPJgxzA=="],
-
- "@uppy/components": ["@uppy/components@0.2.0", "", { "dependencies": { "@tailwindcss/cli": "^4.0.6", "@webcam/core": "^1.0.1", "clsx": "^2.1.1", "dequal": "^2.0.3", "preact": "^10.5.13", "pretty-bytes": "^6.1.1", "tailwindcss": "^4.0.6" }, "peerDependencies": { "@uppy/audio": "^2.1.3", "@uppy/core": "^4.4.7", "@uppy/image-editor": "^3.3.3", "@uppy/remote-sources": "^2.3.4", "@uppy/screen-capture": "^4.3.1", "@uppy/webcam": "^4.2.1" }, "optionalPeers": ["@uppy/audio", "@uppy/image-editor", "@uppy/remote-sources", "@uppy/screen-capture", "@uppy/webcam"] }, "sha512-qE2SDkXcT4lZ2c/fFBhhEnUlCvz8NDrqo7uC9r+1dUbAxbyXRphLvCK7wyOeVPTDEeWnTPwM4RGMq7NyfFvIGA=="],
-
- "@uppy/core": ["@uppy/core@4.4.7", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/store-default": "^4.2.0", "@uppy/utils": "^6.1.5", "lodash": "^4.17.21", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", "nanoid": "^5.0.9", "preact": "^10.5.13" } }, "sha512-ZEdRiVnkHVITS7afBCWxQGNKOZ22DFXoDb4ZcLK2Srp5iBnxUbg1rV3sntHDNjZq7QHQAtZqHKAF8RxE6ZSNeg=="],
-
- "@uppy/dashboard": ["@uppy/dashboard@4.3.4", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/informer": "^4.2.1", "@uppy/provider-views": "^4.4.3", "@uppy/status-bar": "^4.1.3", "@uppy/thumbnail-generator": "^4.1.1", "@uppy/utils": "^6.1.4", "classnames": "^2.2.6", "lodash": "^4.17.21", "memoize-one": "^6.0.0", "nanoid": "^5.0.9", "preact": "^10.5.13", "shallow-equal": "^3.0.0" }, "peerDependencies": { "@uppy/core": "^4.4.5" } }, "sha512-SMPa5K3jZ2qNf110Hf8adN/cEAQLdpvXGjgl+R9c8AnUdpKE5f4XxaWSukdW6N7YYWmoBrLGesFvwRSPKZzCOw=="],
-
- "@uppy/drag-drop": ["@uppy/drag-drop@4.1.3", "", { "dependencies": { "@uppy/utils": "^6.1.4", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.5" } }, "sha512-oxWTIfIM6v28Krl1fv3wrixHshkNMrp0vGiDO8W2qOgP2zSJCVx9KTagj3BY2tn9iyy4A7yuQZXKPmmzgfPyaQ=="],
-
- "@uppy/drop-target": ["@uppy/drop-target@3.1.1", "", { "dependencies": { "@uppy/utils": "^6.1.1" }, "peerDependencies": { "@uppy/core": "^4.4.1" } }, "sha512-/2jnQ3DqfcWGjgoasLBLvwJ3fozavwSXFVULenDmPUI8YPjuxmEtOu61XnZ/OLhRnZo6Qm+kltSd+YUS0P/LNA=="],
-
- "@uppy/file-input": ["@uppy/file-input@4.1.3", "", { "dependencies": { "@uppy/utils": "^6.1.4", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.5" } }, "sha512-wiZeS46c49s8NdnbTYfamfMn4WTsvMtPRTDfBD5M7CocBWRmIfKQ/nisv/1Nhy8J1a/P0eIj9Tmf518SaepN5A=="],
-
- "@uppy/image-editor": ["@uppy/image-editor@3.3.3", "", { "dependencies": { "@uppy/utils": "^6.1.4", "cropperjs": "^1.6.2", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.5" } }, "sha512-1jfApC1nDYdvcOsWuRA+ZqlS5CxeW9HYZE8xOXCmnDNkmNYmemro0f6MU5tZrrPw8MncH6JOvzjuffPYQL0mzg=="],
-
- "@uppy/informer": ["@uppy/informer@4.2.1", "", { "dependencies": { "@uppy/utils": "^6.1.1", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.1" } }, "sha512-0en8Py47pl6RMDrgUfqFoF807W5kK5AKVJNT1SkTsLiGg5anmTIMuvmNG3k6LN4cn9P/rKyEHSdGcoBBUj9u7Q=="],
-
- "@uppy/progress-bar": ["@uppy/progress-bar@4.2.1", "", { "dependencies": { "@uppy/utils": "^6.1.1", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.1" } }, "sha512-5TrUYYt1e/Qy4L4GS7pHeH9I9/zYpp7SiJzC5BtYlku5J6yxZbdxpMPW1mBhQqW+ou/IByaVIGFIR6iSq6yo0w=="],
-
- "@uppy/provider-views": ["@uppy/provider-views@4.4.5", "", { "dependencies": { "@uppy/utils": "^6.1.5", "classnames": "^2.2.6", "nanoid": "^5.0.9", "p-queue": "^8.0.0", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.7" } }, "sha512-ncPRr+morAgl/i/Rm6+Ue2O52zBtPBUNnEsixP24IxC8c9dRnG+Q/n1xNKbxY6Bd1e2X+TJs+SolxL5sPfxEfQ=="],
-
- "@uppy/status-bar": ["@uppy/status-bar@4.1.3", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/utils": "^6.1.3", "classnames": "^2.2.6", "preact": "^10.5.13" }, "peerDependencies": { "@uppy/core": "^4.4.4" } }, "sha512-1YlbsoA9lTNL2b7nhehDri15XslVzGLG+J7HFAsxbE2cMHnOusuLCkm03oE9c72pOU9nG2qZV6yqdWBTwdxbNA=="],
-
- "@uppy/store-default": ["@uppy/store-default@4.2.0", "", {}, "sha512-PieFVa8yTvRHIqsNKfpO/yaJw5Ae/hT7uT58ryw7gvCBY5bHrNWxH5N0XFe8PFHMpLpLn8v3UXGx9ib9QkB6+Q=="],
-
- "@uppy/thumbnail-generator": ["@uppy/thumbnail-generator@4.1.1", "", { "dependencies": { "@uppy/utils": "^6.1.1", "exifr": "^7.0.0" }, "peerDependencies": { "@uppy/core": "^4.4.1" } }, "sha512-65znkGNgVTbVte51IKOhgxOpHGSwYj9Qik2jF2ZBocMbhBY4gPkWFwqMrKQBfddA9KbUa4jVe1psxhAQTzYgiA=="],
-
- "@uppy/tus": ["@uppy/tus@4.2.2", "", { "dependencies": { "@uppy/companion-client": "^4.4.1", "@uppy/utils": "^6.1.1", "tus-js-client": "^4.2.3" }, "peerDependencies": { "@uppy/core": "^4.4.1" } }, "sha512-fauUHqoLDtyRXwoaIyWM8ctuJ+SAXdjuM2eyoPYcGtpVaEGa+AS7IQkJkWz2RrWSdLCHL9O+fk6jKr+0PIDEpQ=="],
-
- "@uppy/utils": ["@uppy/utils@6.1.5", "", { "dependencies": { "lodash": "^4.17.21", "preact": "^10.5.13" } }, "sha512-R+3l4ir01I6MzZ80t5CSBoOGvV9mCqLZC9FoNYVTp/lsanQ3yMAZFGLN/x7JVGO7oi/m/ykmNR1jAH+JTtXdFg=="],
-
- "@uppy/vue": ["@uppy/vue@2.3.0", "", { "dependencies": { "@uppy/components": "^0.2.0", "preact": "^10.5.13", "shallow-equal": "^3.0.0" }, "peerDependencies": { "@uppy/core": "^4.4.7", "@uppy/dashboard": "^4.3.4", "@uppy/drag-drop": "^4.1.3", "@uppy/file-input": "^4.1.3", "@uppy/progress-bar": "^4.2.1", "@uppy/status-bar": "^4.1.3", "vue": ">=3.0.0" }, "optionalPeers": ["@uppy/dashboard", "@uppy/drag-drop", "@uppy/file-input", "@uppy/progress-bar", "@uppy/status-bar"] }, "sha512-eAzIOywEmEb3XRYaMoaCxOfWRhN3RxdMMjw7JrkfJ9f1E4zvtp/w9uPBJe3ZaoafWv84HgwDRXEtOuNeTlY2zA=="],
-
"@vicons/material": ["@vicons/material@0.13.0", "", {}, "sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.19" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ=="],
@@ -434,8 +358,6 @@
"@vueuse/shared": ["@vueuse/shared@13.5.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-K7GrQIxJ/ANtucxIXbQlUHdB0TPA8c+q5i+zbrjxuhJCnJ9GtBg75sBSnvmLSxHKPg2Yo8w62PWksl9kwH0Q8g=="],
- "@webcam/core": ["@webcam/core@1.0.1", "", {}, "sha512-N422fDE1iJ5pc5IiLh2NhvxQPFYTMLDbw+x0YtREGMDg4S05qvRgD8Au0N0/JoQUVvS7Vw+ns16/jYPUDgljtQ=="],
-
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -480,10 +402,6 @@
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
- "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
-
- "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
-
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
@@ -496,8 +414,6 @@
"copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
- "cropperjs": ["cropperjs@1.6.2", "", {}, "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA=="],
-
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"css-render": ["css-render@0.15.14", "", { "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" } }, "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg=="],
@@ -524,8 +440,6 @@
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
- "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
-
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"domain-context": ["domain-context@0.5.1", "", {}, "sha512-WyTWkXciNvYYaQzdnKJtjlVSXHivtt0E/vCv36Bkwh+Sk4NXkrQpHxZT5BHYmKRVgxWMol1wcdurZCzyTT6Euw=="],
@@ -572,14 +486,10 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
- "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
-
"evtd": ["evtd@0.2.4", "", {}, "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw=="],
"execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="],
- "exifr": ["exifr@7.1.3", "", {}, "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw=="],
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
@@ -650,13 +560,11 @@
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
- "is-network-error": ["is-network-error@1.1.0", "", {}, "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g=="],
-
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
- "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
@@ -748,16 +656,12 @@
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
- "memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
-
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
- "mime-match": ["mime-match@1.0.2", "", { "dependencies": { "wildcard": "^1.1.0" } }, "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg=="],
-
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -768,8 +672,6 @@
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
- "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
-
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -778,14 +680,10 @@
"naive-ui": ["naive-ui@2.42.0", "", { "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/katex": "^0.16.2", "@types/lodash": "^4.14.198", "@types/lodash-es": "^4.17.9", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.8", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.63" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-c7cXR2YgOjgtBadXHwiWL4Y0tpGLAI5W5QzzHksOi22iuHXoSGMAzdkVTGVPE/PM0MSGQ/JtUIzCx2Y0hU0vTQ=="],
- "namespace-emitter": ["namespace-emitter@2.0.1", "", {}, "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="],
-
- "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
- "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
-
"node-fetch": ["node-fetch@1.7.3", "", { "dependencies": { "encoding": "^0.1.11", "is-stream": "^1.0.1" } }, "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ=="],
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
@@ -808,12 +706,6 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
- "p-queue": ["p-queue@8.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw=="],
-
- "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="],
-
- "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
-
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
@@ -840,16 +732,12 @@
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
- "preact": ["preact@10.26.9", "", {}, "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA=="],
-
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
- "pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
-
"pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="],
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
@@ -866,7 +754,7 @@
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
- "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
@@ -884,8 +772,6 @@
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
- "shallow-equal": ["shallow-equal@3.1.0", "", {}, "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg=="],
-
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
@@ -980,8 +866,6 @@
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
- "wildcard": ["wildcard@1.1.2", "", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="],
-
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
@@ -1004,8 +888,6 @@
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
- "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
-
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="],
@@ -1024,6 +906,8 @@
"@vitejs/plugin-vue-jsx/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="],
+ "@vue/devtools-core/nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
+
"@vue/language-core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -1032,8 +916,12 @@
"css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="],
+ "execa/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
+
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "get-stream/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
+
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -1042,16 +930,10 @@
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
- "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "proper-lockfile/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
-
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
- "tus-js-client/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
-
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
diff --git a/DysonNetwork.Drive/Client/package.json b/DysonNetwork.Drive/Client/package.json
index 276d764..a67af57 100644
--- a/DysonNetwork.Drive/Client/package.json
+++ b/DysonNetwork.Drive/Client/package.json
@@ -19,20 +19,12 @@
"@fontsource-variable/nunito": "^5.2.6",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@tailwindcss/vite": "^4.1.11",
- "@uppy/core": "^4.4.7",
- "@uppy/dashboard": "^4.3.4",
- "@uppy/drag-drop": "^4.1.3",
- "@uppy/drop-target": "^3.1.1",
- "@uppy/file-input": "^4.1.3",
- "@uppy/image-editor": "^3.3.3",
- "@uppy/progress-bar": "^4.2.1",
- "@uppy/tus": "^4.2.2",
- "@uppy/vue": "^2.3.0",
"@vueuse/core": "^13.5.0",
"aspnet-prerendering": "^3.0.1",
"cfturnstile-vue3": "^2.0.0",
"pinia": "^3.0.3",
"tailwindcss": "^4.1.11",
+ "tus-js-client": "^4.3.1",
"vue": "^3.5.17",
"vue-router": "^4.5.1"
},
diff --git a/DysonNetwork.Drive/Client/src/router/index.ts b/DysonNetwork.Drive/Client/src/router/index.ts
index 492dcd9..db85fec 100644
--- a/DysonNetwork.Drive/Client/src/router/index.ts
+++ b/DysonNetwork.Drive/Client/src/router/index.ts
@@ -8,6 +8,11 @@ const router = createRouter({
path: '/',
name: 'index',
component: () => import('../views/index.vue')
+ },
+ {
+ path: '/files',
+ name: 'files',
+ component: () => import('../views/files.vue'),
}
]
})
diff --git a/DysonNetwork.Drive/Client/src/stores/user.ts b/DysonNetwork.Drive/Client/src/stores/user.ts
index 5f4e9d3..62f586f 100644
--- a/DysonNetwork.Drive/Client/src/stores/user.ts
+++ b/DysonNetwork.Drive/Client/src/stores/user.ts
@@ -15,7 +15,7 @@ export const useUserStore = defineStore('user', () => {
isLoading.value = true
error.value = null
try {
- const response = await fetch('/api/accounts/me', {
+ const response = await fetch('/cgi/id/accounts/me', {
credentials: 'include',
})
diff --git a/DysonNetwork.Drive/Client/src/views/files.vue b/DysonNetwork.Drive/Client/src/views/files.vue
new file mode 100644
index 0000000..d971dd2
--- /dev/null
+++ b/DysonNetwork.Drive/Client/src/views/files.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ Download
+
+
+
+
+
+
+
+
+
diff --git a/DysonNetwork.Drive/Client/src/views/index.vue b/DysonNetwork.Drive/Client/src/views/index.vue
index 233f4ef..360f7d2 100644
--- a/DysonNetwork.Drive/Client/src/views/index.vue
+++ b/DysonNetwork.Drive/Client/src/views/index.vue
@@ -14,29 +14,78 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Click or drag a file to this area to upload
+
+ Strictly prohibit from uploading sensitive information. For example, your bank card PIN
+ or your credit card expiry date.
+
+
+
+
+
+ Loading...
+
+ v{{ version.version }} @
+ {{ version.commit.substring(0, 6) }}
+ {{ version.updatedAt }}
+
+
-
+const modeAdvanced = ref(false)
+
+const filePass = ref('')
+
+function customRequest({
+ file,
+ data,
+ headers,
+ withCredentials,
+ action,
+ onFinish,
+ onError,
+ onProgress,
+}: UploadCustomRequestOptions) {
+ const upload = new tus.Upload(file.file, {
+ endpoint: '/api/tus',
+ retryDelays: [0, 3000, 5000, 10000, 20000],
+ metadata: {
+ filename: file.name,
+ filetype: file.type ?? 'application/octet-stream',
+ },
+ headers: {
+ 'X-FilePass': filePass.value,
+ ...headers,
+ },
+ onError: function (error) {
+ console.error('[DRIVE] Upload failed:', error)
+ onError()
+ },
+ onProgress: function (bytesUploaded, bytesTotal) {
+ onProgress({ percent: (bytesUploaded / bytesTotal) * 100 })
+ },
+ onSuccess: function (payload) {
+ const rawInfo = payload.lastResponse.getHeader('x-fileinfo')
+ const jsonInfo = JSON.parse(rawInfo as string)
+ console.log('[DRIVE] Upload successful: ', jsonInfo)
+ file.url = `/api/files/${jsonInfo.id}`
+ file.type = jsonInfo.mime_type
+ onFinish()
+ },
+ onBeforeRequest: function (req) {
+ const xhr = req.getUnderlyingObject()
+ xhr.withCredentials = withCredentials
+ },
+ })
+ upload.findPreviousUploads().then(function (previousUploads) {
+ if (previousUploads.length) {
+ upload.resumeFromPreviousUpload(previousUploads[0])
+ }
+ upload.start()
+ })
+}
+
+function createThumbnailUrl(_file: File | null, fileInfo: UploadSettledFileInfo): string | undefined {
+ if (!fileInfo) return undefined
+ return fileInfo.url ?? undefined
+}
+
diff --git a/DysonNetwork.Drive/Client/src/views/secure.ts b/DysonNetwork.Drive/Client/src/views/secure.ts
new file mode 100644
index 0000000..5a4109c
--- /dev/null
+++ b/DysonNetwork.Drive/Client/src/views/secure.ts
@@ -0,0 +1,92 @@
+export async function downloadAndDecryptFile(
+ url: string,
+ password: string,
+ onProgress?: (progress: number) => void
+): Promise {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error(`Failed to fetch: ${response.status}`);
+
+ const contentLength = +(response.headers.get('Content-Length') || 0);
+ const reader = response.body!.getReader();
+ const chunks: Uint8Array[] = [];
+ let received = 0;
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ if (value) {
+ chunks.push(value);
+ received += value.length;
+ if (contentLength && onProgress) {
+ onProgress(received / contentLength);
+ }
+ }
+ }
+
+ const fullBuffer = new Uint8Array(received);
+ let offset = 0;
+ for (const chunk of chunks) {
+ fullBuffer.set(chunk, offset);
+ offset += chunk.length;
+ }
+
+ const decryptedBytes = await decryptFile(fullBuffer, password);
+
+ // Create a blob and trigger a download
+ const blob = new Blob([decryptedBytes]);
+ const downloadUrl = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = downloadUrl;
+ a.download = 'decrypted_file'; // You may allow customization
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ URL.revokeObjectURL(downloadUrl);
+}
+
+export async function decryptFile(
+ fileBuffer: Uint8Array,
+ password: string
+): Promise {
+ const salt = fileBuffer.slice(0, 16);
+ const nonce = fileBuffer.slice(16, 28);
+ const tag = fileBuffer.slice(28, 44);
+ const ciphertext = fileBuffer.slice(44);
+
+ const enc = new TextEncoder();
+ const keyMaterial = await crypto.subtle.importKey(
+ 'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
+ );
+ const key = await crypto.subtle.deriveKey(
+ { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
+ keyMaterial,
+ { name: 'AES-GCM', length: 256 },
+ false,
+ ['decrypt']
+ );
+
+ const fullCiphertext = new Uint8Array(ciphertext.length + tag.length);
+ fullCiphertext.set(ciphertext);
+ fullCiphertext.set(tag, ciphertext.length);
+
+ let decrypted: ArrayBuffer;
+ try {
+ decrypted = await crypto.subtle.decrypt(
+ { name: 'AES-GCM', iv: nonce, tagLength: 128 },
+ key,
+ fullCiphertext
+ );
+ } catch {
+ throw new Error("Incorrect password or corrupted file.");
+ }
+
+ const magic = new TextEncoder().encode("DYSON1");
+ const decryptedBytes = new Uint8Array(decrypted);
+ for (let i = 0; i < magic.length; i++) {
+ if (decryptedBytes[i] !== magic[i]) {
+ throw new Error("Incorrect password or corrupted file.");
+ }
+ }
+
+ return decryptedBytes.slice(magic.length);
+}
diff --git a/DysonNetwork.Drive/Client/vite.config.ts b/DysonNetwork.Drive/Client/vite.config.ts
index e1a1618..78fbc8b 100644
--- a/DysonNetwork.Drive/Client/vite.config.ts
+++ b/DysonNetwork.Drive/Client/vite.config.ts
@@ -29,11 +29,11 @@ export default defineConfig({
server: {
proxy: {
'/api': {
- target: 'http://localhost:5216',
+ target: 'http://localhost:5090',
changeOrigin: true,
},
'/cgi': {
- target: 'http://localhost:5216',
+ target: 'http://localhost:5090',
changeOrigin: true,
}
},
diff --git a/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs b/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs
new file mode 100644
index 0000000..f098e3e
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs
@@ -0,0 +1,277 @@
+//
+using System;
+using System.Collections.Generic;
+using DysonNetwork.Drive;
+using DysonNetwork.Drive.Storage;
+using DysonNetwork.Shared.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ [DbContext(typeof(AppDatabase))]
+ [Migration("20250725183846_EnrichCloudPoolConfigure")]
+ partial class EnrichCloudPoolConfigure
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("id");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid")
+ .HasColumnName("account_id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Description")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("description");
+
+ b.Property>("FileMeta")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("file_meta");
+
+ b.Property("HasCompression")
+ .HasColumnType("boolean")
+ .HasColumnName("has_compression");
+
+ b.Property("HasThumbnail")
+ .HasColumnType("boolean")
+ .HasColumnName("has_thumbnail");
+
+ b.Property("Hash")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("hash");
+
+ b.Property("IsEncrypted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_encrypted");
+
+ b.Property("IsMarkedRecycle")
+ .HasColumnType("boolean")
+ .HasColumnName("is_marked_recycle");
+
+ b.Property("MimeType")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("mime_type");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("PoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("pool_id");
+
+ b.Property>("SensitiveMarks")
+ .HasColumnType("jsonb")
+ .HasColumnName("sensitive_marks");
+
+ b.Property("Size")
+ .HasColumnType("bigint")
+ .HasColumnName("size");
+
+ b.Property("StorageId")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("storage_id");
+
+ b.Property("StorageUrl")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("storage_url");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UploadedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("uploaded_at");
+
+ b.Property("UploadedTo")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("uploaded_to");
+
+ b.Property>("UserMeta")
+ .HasColumnType("jsonb")
+ .HasColumnName("user_meta");
+
+ b.HasKey("Id")
+ .HasName("pk_files");
+
+ b.HasIndex("PoolId")
+ .HasDatabaseName("ix_files_pool_id");
+
+ b.ToTable("files", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("ExpiredAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expired_at");
+
+ b.Property("FileId")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("file_id");
+
+ b.Property("ResourceId")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("resource_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("Usage")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("usage");
+
+ b.HasKey("Id")
+ .HasName("pk_file_references");
+
+ b.HasIndex("FileId")
+ .HasDatabaseName("ix_file_references_file_id");
+
+ b.ToTable("file_references", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.FilePool", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("AllowAnonymous")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_anonymous");
+
+ b.Property("AllowEncryption")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_encryption");
+
+ b.Property("BillingConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("billing_config");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("NoMetadata")
+ .HasColumnType("boolean")
+ .HasColumnName("no_metadata");
+
+ b.Property("NoOptimization")
+ .HasColumnType("boolean")
+ .HasColumnName("no_optimization");
+
+ b.Property("RequirePrivilege")
+ .HasColumnType("integer")
+ .HasColumnName("require_privilege");
+
+ b.Property("StorageConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("storage_config");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.HasKey("Id")
+ .HasName("pk_pools");
+
+ b.ToTable("pools", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.FilePool", "Pool")
+ .WithMany()
+ .HasForeignKey("PoolId")
+ .HasConstraintName("fk_files_pools_pool_id");
+
+ b.Navigation("Pool");
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
+ .WithMany()
+ .HasForeignKey("FileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_file_references_files_file_id");
+
+ b.Navigation("File");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.cs b/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.cs
new file mode 100644
index 0000000..fe6463b
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.cs
@@ -0,0 +1,73 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ ///
+ public partial class EnrichCloudPoolConfigure : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "allow_anonymous",
+ table: "pools",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "allow_encryption",
+ table: "pools",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "no_metadata",
+ table: "pools",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "no_optimization",
+ table: "pools",
+ type: "boolean",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "require_privilege",
+ table: "pools",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "allow_anonymous",
+ table: "pools");
+
+ migrationBuilder.DropColumn(
+ name: "allow_encryption",
+ table: "pools");
+
+ migrationBuilder.DropColumn(
+ name: "no_metadata",
+ table: "pools");
+
+ migrationBuilder.DropColumn(
+ name: "no_optimization",
+ table: "pools");
+
+ migrationBuilder.DropColumn(
+ name: "require_privilege",
+ table: "pools");
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs b/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs
new file mode 100644
index 0000000..8f18dd3
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs
@@ -0,0 +1,276 @@
+//
+using System;
+using System.Collections.Generic;
+using DysonNetwork.Drive;
+using DysonNetwork.Drive.Storage;
+using DysonNetwork.Shared.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ [DbContext(typeof(AppDatabase))]
+ [Migration("20250725184107_NullableFileMeta")]
+ partial class NullableFileMeta
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("id");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid")
+ .HasColumnName("account_id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Description")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("description");
+
+ b.Property>("FileMeta")
+ .HasColumnType("jsonb")
+ .HasColumnName("file_meta");
+
+ b.Property("HasCompression")
+ .HasColumnType("boolean")
+ .HasColumnName("has_compression");
+
+ b.Property("HasThumbnail")
+ .HasColumnType("boolean")
+ .HasColumnName("has_thumbnail");
+
+ b.Property("Hash")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("hash");
+
+ b.Property("IsEncrypted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_encrypted");
+
+ b.Property("IsMarkedRecycle")
+ .HasColumnType("boolean")
+ .HasColumnName("is_marked_recycle");
+
+ b.Property("MimeType")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("mime_type");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("PoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("pool_id");
+
+ b.Property>("SensitiveMarks")
+ .HasColumnType("jsonb")
+ .HasColumnName("sensitive_marks");
+
+ b.Property("Size")
+ .HasColumnType("bigint")
+ .HasColumnName("size");
+
+ b.Property("StorageId")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("storage_id");
+
+ b.Property("StorageUrl")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("storage_url");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UploadedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("uploaded_at");
+
+ b.Property("UploadedTo")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("uploaded_to");
+
+ b.Property>("UserMeta")
+ .HasColumnType("jsonb")
+ .HasColumnName("user_meta");
+
+ b.HasKey("Id")
+ .HasName("pk_files");
+
+ b.HasIndex("PoolId")
+ .HasDatabaseName("ix_files_pool_id");
+
+ b.ToTable("files", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("ExpiredAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expired_at");
+
+ b.Property("FileId")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("file_id");
+
+ b.Property("ResourceId")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("resource_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("Usage")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("usage");
+
+ b.HasKey("Id")
+ .HasName("pk_file_references");
+
+ b.HasIndex("FileId")
+ .HasDatabaseName("ix_file_references_file_id");
+
+ b.ToTable("file_references", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.FilePool", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("AllowAnonymous")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_anonymous");
+
+ b.Property("AllowEncryption")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_encryption");
+
+ b.Property("BillingConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("billing_config");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("NoMetadata")
+ .HasColumnType("boolean")
+ .HasColumnName("no_metadata");
+
+ b.Property("NoOptimization")
+ .HasColumnType("boolean")
+ .HasColumnName("no_optimization");
+
+ b.Property("RequirePrivilege")
+ .HasColumnType("integer")
+ .HasColumnName("require_privilege");
+
+ b.Property("StorageConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("storage_config");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.HasKey("Id")
+ .HasName("pk_pools");
+
+ b.ToTable("pools", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.FilePool", "Pool")
+ .WithMany()
+ .HasForeignKey("PoolId")
+ .HasConstraintName("fk_files_pools_pool_id");
+
+ b.Navigation("Pool");
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
+ .WithMany()
+ .HasForeignKey("FileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_file_references_files_file_id");
+
+ b.Navigation("File");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.cs b/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.cs
new file mode 100644
index 0000000..d71f8e6
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ ///
+ public partial class NullableFileMeta : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn>(
+ name: "file_meta",
+ table: "files",
+ type: "jsonb",
+ nullable: true,
+ oldClrType: typeof(Dictionary),
+ oldType: "jsonb");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn>(
+ name: "file_meta",
+ table: "files",
+ type: "jsonb",
+ nullable: false,
+ oldClrType: typeof(Dictionary),
+ oldType: "jsonb",
+ oldNullable: true);
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
index 713fa51..853ca45 100644
--- a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
+++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
@@ -52,7 +52,6 @@ namespace DysonNetwork.Drive.Migrations
.HasColumnName("description");
b.Property>("FileMeta")
- .IsRequired()
.HasColumnType("jsonb")
.HasColumnName("file_meta");
@@ -193,6 +192,14 @@ namespace DysonNetwork.Drive.Migrations
.HasColumnType("uuid")
.HasColumnName("id");
+ b.Property("AllowAnonymous")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_anonymous");
+
+ b.Property("AllowEncryption")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_encryption");
+
b.Property("BillingConfig")
.IsRequired()
.HasColumnType("jsonb")
@@ -212,6 +219,18 @@ namespace DysonNetwork.Drive.Migrations
.HasColumnType("character varying(1024)")
.HasColumnName("name");
+ b.Property("NoMetadata")
+ .HasColumnType("boolean")
+ .HasColumnName("no_metadata");
+
+ b.Property("NoOptimization")
+ .HasColumnType("boolean")
+ .HasColumnName("no_optimization");
+
+ b.Property("RequirePrivilege")
+ .HasColumnType("integer")
+ .HasColumnName("require_privilege");
+
b.Property("StorageConfig")
.IsRequired()
.HasColumnType("jsonb")
diff --git a/DysonNetwork.Drive/Storage/CloudFile.cs b/DysonNetwork.Drive/Storage/CloudFile.cs
index 70bad66..155e8d8 100644
--- a/DysonNetwork.Drive/Storage/CloudFile.cs
+++ b/DysonNetwork.Drive/Storage/CloudFile.cs
@@ -33,8 +33,8 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
[MaxLength(4096)] public string? Description { get; set; }
- [Column(TypeName = "jsonb")] public Dictionary FileMeta { get; set; } = null!;
- [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; } = null!;
+ [Column(TypeName = "jsonb")] public Dictionary? FileMeta { get; set; }
+ [Column(TypeName = "jsonb")] public Dictionary? UserMeta { get; set; }
[Column(TypeName = "jsonb")] public List? SensitiveMarks { get; set; } = [];
[MaxLength(256)] public string? MimeType { get; set; }
[MaxLength(256)] public string? Hash { get; set; }
diff --git a/DysonNetwork.Drive/Storage/FilePool.cs b/DysonNetwork.Drive/Storage/FilePool.cs
index 0a78005..d918c6b 100644
--- a/DysonNetwork.Drive/Storage/FilePool.cs
+++ b/DysonNetwork.Drive/Storage/FilePool.cs
@@ -30,6 +30,11 @@ public class FilePool : ModelBase, IIdentifiedResource
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
-
+ public bool NoOptimization { get; set; } = false;
+ public bool NoMetadata { get; set; } = false;
+ public bool AllowEncryption { get; set; } = true;
+ public bool AllowAnonymous { get; set; } = true;
+ public int RequirePrivilege { get; set; } = 0;
+
public string ResourceIdentifier => $"file-pool/{Id}";
}
\ No newline at end of file
diff --git a/DysonNetwork.Drive/Storage/FileService.cs b/DysonNetwork.Drive/Storage/FileService.cs
index ec965fb..2c28282 100644
--- a/DysonNetwork.Drive/Storage/FileService.cs
+++ b/DysonNetwork.Drive/Storage/FileService.cs
@@ -102,24 +102,30 @@ public class FileService(
public async Task ProcessNewFileAsync(
Account account,
string fileId,
+ string filePool,
Stream stream,
string fileName,
string? contentType,
string? encryptPassword
)
{
+ var pool = await GetPoolAsync(Guid.Parse(filePool));
+ if (pool is null) throw new InvalidOperationException("Pool not found");
+
var ogFilePath = Path.GetFullPath(Path.Join(configuration.GetValue("Tus:StorePath"), fileId));
var fileSize = stream.Length;
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
if (!string.IsNullOrWhiteSpace(encryptPassword))
{
+ if (!pool.AllowEncryption) throw new InvalidOperationException("Encryption is not allowed in this pool");
var encryptedPath = Path.Combine(Path.GetTempPath(), $"{fileId}.encrypted");
FileEncryptor.EncryptFile(ogFilePath, encryptedPath, encryptPassword);
File.Delete(ogFilePath); // Delete original unencrypted
File.Move(encryptedPath, ogFilePath); // Replace the original one with encrypted
+ contentType = "application/octet-stream";
}
-
+
var hash = await HashFileAsync(stream, fileSize: fileSize);
var file = new CloudFile
@@ -154,14 +160,15 @@ public class FileService(
}
// Extract metadata on the current thread for a faster initial response
- await ExtractMetadataAsync(file, ogFilePath, stream);
+ if (!pool.NoMetadata)
+ await ExtractMetadataAsync(file, ogFilePath, stream);
db.Files.Add(file);
await db.SaveChangesAsync();
// Offload optimization (image conversion, thumbnailing) and uploading to a background task
_ = Task.Run(() =>
- ProcessAndUploadInBackgroundAsync(file.Id, file.StorageId, contentType, ogFilePath, stream));
+ ProcessAndUploadInBackgroundAsync(file.Id, filePool, file.StorageId, contentType, ogFilePath, stream));
return file;
}
@@ -269,9 +276,18 @@ public class FileService(
///
/// Handles file optimization (image compression, video thumbnailing) and uploads to remote storage in the background.
///
- private async Task ProcessAndUploadInBackgroundAsync(string fileId, string storageId, string contentType,
- string originalFilePath, Stream stream)
+ private async Task ProcessAndUploadInBackgroundAsync(
+ string fileId,
+ string remoteId,
+ string storageId,
+ string contentType,
+ string originalFilePath,
+ Stream stream
+ )
{
+ var pool = await GetPoolAsync(Guid.Parse(remoteId));
+ if (pool is null) return;
+
await using var bgStream = stream; // Ensure stream is disposed at the end of this task
using var scope = scopeFactory.CreateScope();
var nfs = scope.ServiceProvider.GetRequiredService();
@@ -286,74 +302,76 @@ public class FileService(
{
logger.LogInformation("Processing file {FileId} in background...", fileId);
- switch (contentType.Split('/')[0])
- {
- case "image" when !AnimatedImageTypes.Contains(contentType):
- newMimeType = "image/webp";
- using (var vipsImage = Image.NewFromFile(originalFilePath))
- {
- var imageToWrite = vipsImage;
-
- if (vipsImage.Interpretation is Enums.Interpretation.Scrgb or Enums.Interpretation.Xyz)
+ if (!pool.NoOptimization)
+ switch (contentType.Split('/')[0])
+ {
+ case "image" when !AnimatedImageTypes.Contains(contentType):
+ newMimeType = "image/webp";
+ using (var vipsImage = Image.NewFromFile(originalFilePath))
{
- imageToWrite = vipsImage.Colourspace(Enums.Interpretation.Srgb);
+ var imageToWrite = vipsImage;
+
+ if (vipsImage.Interpretation is Enums.Interpretation.Scrgb or Enums.Interpretation.Xyz)
+ {
+ imageToWrite = vipsImage.Colourspace(Enums.Interpretation.Srgb);
+ }
+
+ var webpPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}.webp");
+ imageToWrite.Autorot().WriteToFile(webpPath,
+ new VOption { { "lossless", true }, { "strip", true } });
+ uploads.Add((webpPath, string.Empty, newMimeType, true));
+
+ if (imageToWrite.Width * imageToWrite.Height >= 1024 * 1024)
+ {
+ var scale = 1024.0 / Math.Max(imageToWrite.Width, imageToWrite.Height);
+ var compressedPath =
+ Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}-compressed.webp");
+ using var compressedImage = imageToWrite.Resize(scale);
+ compressedImage.Autorot().WriteToFile(compressedPath,
+ new VOption { { "Q", 80 }, { "strip", true } });
+ uploads.Add((compressedPath, ".compressed", newMimeType, true));
+ hasCompression = true;
+ }
+
+ if (!ReferenceEquals(imageToWrite, vipsImage))
+ {
+ imageToWrite.Dispose(); // Clean up manually created colourspace-converted image
+ }
}
- var webpPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}.webp");
- imageToWrite.Autorot().WriteToFile(webpPath,
- new VOption { { "lossless", true }, { "strip", true } });
- uploads.Add((webpPath, string.Empty, newMimeType, true));
+ break;
- if (imageToWrite.Width * imageToWrite.Height >= 1024 * 1024)
+ case "video":
+ uploads.Add((originalFilePath, string.Empty, contentType, false));
+ var thumbnailPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}.thumbnail.webp");
+ try
{
- var scale = 1024.0 / Math.Max(imageToWrite.Width, imageToWrite.Height);
- var compressedPath =
- Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}-compressed.webp");
- using var compressedImage = imageToWrite.Resize(scale);
- compressedImage.Autorot().WriteToFile(compressedPath,
- new VOption { { "Q", 80 }, { "strip", true } });
- uploads.Add((compressedPath, ".compressed", newMimeType, true));
- hasCompression = true;
+ var mediaInfo = await FFProbe.AnalyseAsync(originalFilePath);
+ var snapshotTime = mediaInfo.Duration > TimeSpan.FromSeconds(5)
+ ? TimeSpan.FromSeconds(5)
+ : TimeSpan.FromSeconds(1);
+ await FFMpeg.SnapshotAsync(originalFilePath, thumbnailPath, captureTime: snapshotTime);
+ uploads.Add((thumbnailPath, ".thumbnail.webp", "image/webp", true));
+ hasThumbnail = true;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to generate thumbnail for video {FileId}", fileId);
}
- if (!ReferenceEquals(imageToWrite, vipsImage))
- {
- imageToWrite.Dispose(); // Clean up manually created colourspace-converted image
- }
- }
+ break;
- break;
-
- case "video":
- uploads.Add((originalFilePath, string.Empty, contentType, false));
- var thumbnailPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{fileId}.thumbnail.webp");
- try
- {
- var mediaInfo = await FFProbe.AnalyseAsync(originalFilePath);
- var snapshotTime = mediaInfo.Duration > TimeSpan.FromSeconds(5)
- ? TimeSpan.FromSeconds(5)
- : TimeSpan.FromSeconds(1);
- await FFMpeg.SnapshotAsync(originalFilePath, thumbnailPath, captureTime: snapshotTime);
- uploads.Add((thumbnailPath, ".thumbnail.webp", "image/webp", true));
- hasThumbnail = true;
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Failed to generate thumbnail for video {FileId}", fileId);
- }
-
- break;
-
- default:
- uploads.Add((originalFilePath, string.Empty, contentType, false));
- break;
- }
+ default:
+ uploads.Add((originalFilePath, string.Empty, contentType, false));
+ break;
+ }
+ else uploads.Add((originalFilePath, string.Empty, contentType, false));
logger.LogInformation("Optimized file {FileId}, now uploading...", fileId);
if (uploads.Count > 0)
{
- var destPool = Guid.Parse(configuration.GetValue("Storage:PreferredRemote")!);
+ var destPool = Guid.Parse(remoteId!);
var uploadTasks = uploads.Select(item =>
nfs.UploadFileToRemoteAsync(storageId, destPool, item.FilePath, item.Suffix, item.ContentType,
item.SelfDestruct)
diff --git a/DysonNetwork.Drive/Storage/TusService.cs b/DysonNetwork.Drive/Storage/TusService.cs
index b5ea295..5475e76 100644
--- a/DysonNetwork.Drive/Storage/TusService.cs
+++ b/DysonNetwork.Drive/Storage/TusService.cs
@@ -44,6 +44,10 @@ public abstract class TusService
if (!allowed.HasPermission)
eventContext.FailRequest(HttpStatusCode.Forbidden);
}
+
+ var filePool = httpContext.Request.Headers["X-FilePool"].FirstOrDefault();
+ if (!string.IsNullOrEmpty(filePool) && !Guid.TryParse(filePool, out _))
+ eventContext.FailRequest(HttpStatusCode.BadRequest, "Invalid file pool id");
},
OnFileCompleteAsync = async eventContext =>
{
@@ -62,12 +66,17 @@ public abstract class TusService
var fileStream = await file.GetContentAsync(eventContext.CancellationToken);
+ var filePool = httpContext.Request.Headers["X-FilePool"].FirstOrDefault();
var encryptPassword = httpContext.Request.Headers["X-FilePass"].FirstOrDefault();
+ if (string.IsNullOrEmpty(filePool))
+ filePool = configuration["Storage:PreferredRemote"];
+
var fileService = services.GetRequiredService();
var info = await fileService.ProcessNewFileAsync(
user,
file.Id,
+ filePool,
fileStream,
fileName,
contentType,
@@ -89,7 +98,7 @@ public abstract class TusService
if (gatewayUrl is not null)
eventContext.SetUploadUrl(new Uri(gatewayUrl + "/drive/tus/" + eventContext.FileId));
return Task.CompletedTask;
- }
+ },
}
};
}
\ No newline at end of file
diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json
index 78b0850..e36b06f 100644
--- a/DysonNetwork.Drive/appsettings.json
+++ b/DysonNetwork.Drive/appsettings.json
@@ -1,7 +1,6 @@
{
"Debug": true,
"BaseUrl": "http://localhost:5071",
- "GatewayUrl": "http://localhost:5094",
"Logging": {
"LogLevel": {
"Default": "Information",
@@ -42,7 +41,7 @@
"StorePath": "Uploads"
},
"Storage": {
- "PreferredRemote": "minio",
+ "PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873",
"Remote": [
{
"Id": "minio",
diff --git a/DysonNetwork.Pass/Client/vite.config.ts b/DysonNetwork.Pass/Client/vite.config.ts
index 78fbc8b..e1a1618 100644
--- a/DysonNetwork.Pass/Client/vite.config.ts
+++ b/DysonNetwork.Pass/Client/vite.config.ts
@@ -29,11 +29,11 @@ export default defineConfig({
server: {
proxy: {
'/api': {
- target: 'http://localhost:5090',
+ target: 'http://localhost:5216',
changeOrigin: true,
},
'/cgi': {
- target: 'http://localhost:5090',
+ target: 'http://localhost:5216',
changeOrigin: true,
}
},