Compare commits
2 Commits
081f3f609e
...
f1867e7916
Author | SHA1 | Date | |
---|---|---|---|
f1867e7916 | |||
0486c0d0e5 |
@@ -13,6 +13,7 @@
|
|||||||
"cfturnstile-vue3": "^2.0.0",
|
"cfturnstile-vue3": "^2.0.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
|
"tus-js-client": "^4.3.1",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
},
|
},
|
||||||
@@ -387,6 +388,8 @@
|
|||||||
|
|
||||||
"browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="],
|
"browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
@@ -403,6 +406,8 @@
|
|||||||
|
|
||||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"combine-errors": ["combine-errors@3.0.3", "", { "dependencies": { "custom-error-instance": "2.1.1", "lodash.uniqby": "4.5.0" } }, "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q=="],
|
||||||
|
|
||||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||||
|
|
||||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
@@ -417,6 +422,8 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"custom-error-instance": ["custom-error-instance@2.1.1", "", {}, "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg=="],
|
||||||
|
|
||||||
"date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
|
"date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
|
||||||
|
|
||||||
"date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="],
|
"date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="],
|
||||||
@@ -557,7 +564,7 @@
|
|||||||
|
|
||||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
"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=="],
|
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||||
|
|
||||||
@@ -571,6 +578,8 @@
|
|||||||
|
|
||||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||||
|
|
||||||
|
"js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="],
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||||
@@ -625,8 +634,24 @@
|
|||||||
|
|
||||||
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
|
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
|
||||||
|
|
||||||
|
"lodash._baseiteratee": ["lodash._baseiteratee@4.7.0", "", { "dependencies": { "lodash._stringtopath": "~4.8.0" } }, "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ=="],
|
||||||
|
|
||||||
|
"lodash._basetostring": ["lodash._basetostring@4.12.0", "", {}, "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw=="],
|
||||||
|
|
||||||
|
"lodash._baseuniq": ["lodash._baseuniq@4.6.0", "", { "dependencies": { "lodash._createset": "~4.0.0", "lodash._root": "~3.0.0" } }, "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A=="],
|
||||||
|
|
||||||
|
"lodash._createset": ["lodash._createset@4.0.3", "", {}, "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA=="],
|
||||||
|
|
||||||
|
"lodash._root": ["lodash._root@3.0.1", "", {}, "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ=="],
|
||||||
|
|
||||||
|
"lodash._stringtopath": ["lodash._stringtopath@4.8.0", "", { "dependencies": { "lodash._basetostring": "~4.12.0" } }, "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ=="],
|
||||||
|
|
||||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||||
|
|
||||||
|
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
||||||
|
|
||||||
|
"lodash.uniqby": ["lodash.uniqby@4.5.0", "", { "dependencies": { "lodash._baseiteratee": "~4.7.0", "lodash._baseuniq": "~4.6.0" } }, "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
@@ -715,14 +740,22 @@
|
|||||||
|
|
||||||
"pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="],
|
"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=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
||||||
|
|
||||||
|
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
|
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||||
@@ -781,6 +814,8 @@
|
|||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tus-js-client": ["tus-js-client@4.3.1", "", { "dependencies": { "buffer-from": "^1.1.2", "combine-errors": "^3.0.3", "is-stream": "^2.0.0", "js-base64": "^3.7.2", "lodash.throttle": "^4.1.1", "proper-lockfile": "^4.1.2", "url-parse": "^1.5.7" } }, "sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg=="],
|
||||||
|
|
||||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
@@ -797,6 +832,8 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="],
|
"vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="],
|
||||||
@@ -879,8 +916,12 @@
|
|||||||
|
|
||||||
"css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="],
|
"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=="],
|
"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=="],
|
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
@@ -889,6 +930,8 @@
|
|||||||
|
|
||||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||||
|
|
||||||
|
"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=="],
|
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Solarpass</title>
|
<title>Solar Network Drive</title>
|
||||||
<app-data />
|
<app-data />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"cfturnstile-vue3": "^2.0.0",
|
"cfturnstile-vue3": "^2.0.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
|
"tus-js-client": "^4.3.1",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
|
@@ -27,18 +27,14 @@ import { NLayout, NLayoutHeader, NLayoutContent, NButton, NDropdown, NIcon } fro
|
|||||||
import {
|
import {
|
||||||
LogInOutlined,
|
LogInOutlined,
|
||||||
PersonAddAlt1Outlined,
|
PersonAddAlt1Outlined,
|
||||||
LogOutOutlined,
|
|
||||||
PersonOutlineRound,
|
PersonOutlineRound,
|
||||||
} from '@vicons/material'
|
} from '@vicons/material'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useServicesStore } from '@/stores/services'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
// Initialize user state on component mount
|
|
||||||
userStore.initialize()
|
|
||||||
|
|
||||||
const hideUserMenu = computed(() => {
|
const hideUserMenu = computed(() => {
|
||||||
return ['captcha', 'spells', 'login', 'create-account'].includes(route.name as string)
|
return ['captcha', 'spells', 'login', 'create-account'].includes(route.name as string)
|
||||||
@@ -71,31 +67,22 @@ const userOptions = computed(() => [
|
|||||||
h(NIcon, null, {
|
h(NIcon, null, {
|
||||||
default: () => h(PersonOutlineRound),
|
default: () => h(PersonOutlineRound),
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
{
|
|
||||||
label: 'Logout',
|
|
||||||
key: 'logout',
|
|
||||||
icon: () =>
|
|
||||||
h(NIcon, null, {
|
|
||||||
default: () => h(LogOutOutlined),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const servicesStore = useServicesStore()
|
||||||
|
|
||||||
function handleGuestMenuSelect(key: string) {
|
function handleGuestMenuSelect(key: string) {
|
||||||
if (key === 'login') {
|
if (key === 'login') {
|
||||||
router.push('/login')
|
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'login')!, '_blank')
|
||||||
} else if (key === 'create-account') {
|
} else if (key === 'create-account') {
|
||||||
router.push('/create-account')
|
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'create-account')!, '_blank')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserMenuSelect(key: string) {
|
function handleUserMenuSelect(key: string) {
|
||||||
if (key === 'logout') {
|
if (key === 'profile') {
|
||||||
userStore.logout()
|
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'accounts/me')!, '_blank')
|
||||||
router.push('/login')
|
|
||||||
} else if (key === 'profile') {
|
|
||||||
router.push('/accounts/me') // Assuming you have a profile page
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -6,6 +6,7 @@ import { NGlobalStyle, NConfigProvider, NMessageProvider, lightTheme, darkTheme
|
|||||||
import { usePreferredDark } from '@vueuse/core'
|
import { usePreferredDark } from '@vueuse/core'
|
||||||
import { useUserStore } from './stores/user'
|
import { useUserStore } from './stores/user'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
import { useServicesStore } from './stores/services'
|
||||||
|
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
common: {
|
common: {
|
||||||
@@ -20,9 +21,13 @@ const themeOverrides = {
|
|||||||
const isDark = usePreferredDark()
|
const isDark = usePreferredDark()
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const servicesStore = useServicesStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
userStore.initialize()
|
||||||
|
|
||||||
userStore.fetchUser()
|
userStore.fetchUser()
|
||||||
|
servicesStore.fetchServices()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -8,6 +8,11 @@ const router = createRouter({
|
|||||||
path: '/',
|
path: '/',
|
||||||
name: 'index',
|
name: 'index',
|
||||||
component: () => import('../views/index.vue')
|
component: () => import('../views/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/files',
|
||||||
|
name: 'files',
|
||||||
|
component: () => import('../views/files.vue'),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -1,3 +1,27 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export const useServicesStore = defineStore('services', () => {})
|
export const useServicesStore = defineStore('services', () => {
|
||||||
|
const services = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
async function fetchServices() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/cgi/.well-known/services')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
services.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch services:', error)
|
||||||
|
services.value = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSerivceUrl(serviceName: string, ...parts: string[]): string | null {
|
||||||
|
let baseUrl = services.value[serviceName] || null
|
||||||
|
return baseUrl ? `${baseUrl}/${parts.join('/')}` : null
|
||||||
|
}
|
||||||
|
|
||||||
|
return { services, fetchServices, getSerivceUrl }
|
||||||
|
})
|
||||||
|
@@ -15,7 +15,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/accounts/me', {
|
const response = await fetch('/cgi/id/accounts/me', {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -43,8 +43,24 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
// router.push('/login')
|
// router.push('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize() {
|
function initialize() {
|
||||||
await fetchUser()
|
const allowedOrigin = import.meta.env.DEV ? window.location.origin : 'https://id.solian.app'
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
// IMPORTANT: Always check the origin of the message for security!
|
||||||
|
// This prevents malicious scripts from sending fake login status updates.
|
||||||
|
// Ensure event.origin exactly matches your identity service's origin.
|
||||||
|
if (event.origin !== allowedOrigin) {
|
||||||
|
console.warn(`[SYNC] Message received from unexpected origin: ${event.origin}. Ignoring.`)
|
||||||
|
return // Ignore messages from unknown origins
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the message is the type we're expecting
|
||||||
|
if (event.data && event.data.type === 'DY:LOGIN_STATUS_CHANGE') {
|
||||||
|
const { loggedIn } = event.data
|
||||||
|
console.log(`[SYNC] Received login status change: ${loggedIn}`)
|
||||||
|
fetchUser() // Re-fetch user data on login status change
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
36
DysonNetwork.Drive/Client/src/views/files.vue
Normal file
36
DysonNetwork.Drive/Client/src/views/files.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<section class="h-full relative flex items-center justify-center">
|
||||||
|
<n-card class="max-w-lg" title="Download file">
|
||||||
|
<div class="flex flex-col gap-3" v-if="!progress">
|
||||||
|
<n-input placeholder="File ID" v-model:value="fileId" />
|
||||||
|
<n-input placeholder="Password" v-model:value="filePass" type="password" />
|
||||||
|
<n-button @click="downloadFile">Download</n-button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<n-progress :percentage="progress" />
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NCard, NInput, NButton, NProgress, useMessage } from 'naive-ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { downloadAndDecryptFile } from './secure'
|
||||||
|
|
||||||
|
const filePass = ref<string>('')
|
||||||
|
const fileId = ref<string>('')
|
||||||
|
|
||||||
|
const progress = ref<number | undefined>(0)
|
||||||
|
|
||||||
|
const messageDisplay = useMessage()
|
||||||
|
|
||||||
|
function downloadFile() {
|
||||||
|
downloadAndDecryptFile('/api/files/' + fileId.value, filePass.value, (p: number) => {
|
||||||
|
progress.value = p * 100
|
||||||
|
}).catch((err) => {
|
||||||
|
messageDisplay.error('Download failed: ' + err.message, { closable: true, duration: 10000 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
@@ -1,10 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="h-full relative flex items-center justify-center">
|
<section class="h-full relative flex items-center justify-center">
|
||||||
<n-card class="max-w-lg" title="About">
|
<n-card class="max-w-lg" title="About" v-if="!userStore.user">
|
||||||
<p>Welcome to the <b>Solar Drive</b></p>
|
<p>Welcome to the <b>Solar Drive</b></p>
|
||||||
<p>
|
<p>We help you upload, collect, and share files with ease in mind.</p>
|
||||||
We help you upload, collect, and share files with ease in mind.
|
<p>To continue, login first.</p>
|
||||||
|
|
||||||
|
<p class="mt-4 opacity-75 text-xs">
|
||||||
|
<span v-if="version == null">Loading...</span>
|
||||||
|
<span v-else>
|
||||||
|
v{{ version.version }} @
|
||||||
|
{{ version.commit.substring(0, 6) }}
|
||||||
|
{{ version.updatedAt }}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
</n-card>
|
||||||
|
<n-card class="max-w-2xl" title="Upload to Solar Network" v-else>
|
||||||
|
<template #header-extra>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<p>Advance Mode</p>
|
||||||
|
<n-switch v-model:value="modeAdvanced" size="small" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="mb-3" v-if="modeAdvanced">
|
||||||
|
<n-input
|
||||||
|
v-model:value="filePass"
|
||||||
|
placeholder="Enter password to protect the file"
|
||||||
|
clearable
|
||||||
|
size="large"
|
||||||
|
type="password"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-upload
|
||||||
|
multiple
|
||||||
|
directory-dnd
|
||||||
|
with-credentials
|
||||||
|
show-preview-button
|
||||||
|
list-type="image"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
:create-thumbnail-url="createThumbnailUrl"
|
||||||
|
>
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div style="margin-bottom: 12px">
|
||||||
|
<n-icon size="48" :depth="3">
|
||||||
|
<upload-outlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<n-text style="font-size: 16px"> Click or drag a file to this area to upload </n-text>
|
||||||
|
<n-p depth="3" style="margin: 8px 0 0 0">
|
||||||
|
Strictly prohibit from uploading sensitive information. For example, your bank card PIN
|
||||||
|
or your credit card expiry date.
|
||||||
|
</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
|
||||||
<p class="mt-4 opacity-75 text-xs">
|
<p class="mt-4 opacity-75 text-xs">
|
||||||
<span v-if="version == null">Loading...</span>
|
<span v-if="version == null">Loading...</span>
|
||||||
@@ -19,8 +69,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NCard } from 'naive-ui'
|
import {
|
||||||
|
NCard,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
NIcon,
|
||||||
|
NText,
|
||||||
|
NP,
|
||||||
|
NInput,
|
||||||
|
NSwitch,
|
||||||
|
type UploadCustomRequestOptions,
|
||||||
|
type UploadSettledFileInfo,
|
||||||
|
} from 'naive-ui'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { UploadOutlined } from '@vicons/material'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
|
import * as tus from 'tus-js-client'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const version = ref<any>(null)
|
const version = ref<any>(null)
|
||||||
|
|
||||||
@@ -30,8 +97,62 @@ async function fetchVersion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => fetchVersion())
|
onMounted(() => fetchVersion())
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
const modeAdvanced = ref(false)
|
||||||
/* Add any specific styles here if needed, though Tailwind should handle most. */
|
|
||||||
</style>
|
const filePass = ref<string>('')
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
92
DysonNetwork.Drive/Client/src/views/secure.ts
Normal file
92
DysonNetwork.Drive/Client/src/views/secure.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
export async function downloadAndDecryptFile(
|
||||||
|
url: string,
|
||||||
|
password: string,
|
||||||
|
onProgress?: (progress: number) => void
|
||||||
|
): Promise<void> {
|
||||||
|
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<Uint8Array> {
|
||||||
|
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);
|
||||||
|
}
|
@@ -29,11 +29,11 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:5216',
|
target: 'http://localhost:5090',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
'/cgi': {
|
'/cgi': {
|
||||||
target: 'http://localhost:5216',
|
target: 'http://localhost:5090',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
257
DysonNetwork.Drive/Migrations/20250725170254_AddCloudFileEncrypt.Designer.cs
generated
Normal file
257
DysonNetwork.Drive/Migrations/20250725170254_AddCloudFileEncrypt.Designer.cs
generated
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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("20250725170254_AddCloudFileEncrypt")]
|
||||||
|
partial class AddCloudFileEncrypt
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<string>("Id")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("file_meta");
|
||||||
|
|
||||||
|
b.Property<bool>("HasCompression")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_compression");
|
||||||
|
|
||||||
|
b.Property<bool>("HasThumbnail")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_thumbnail");
|
||||||
|
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEncrypted")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_encrypted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMarkedRecycle")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_marked_recycle");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("mime_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PoolId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("pool_id");
|
||||||
|
|
||||||
|
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("sensitive_marks");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size");
|
||||||
|
|
||||||
|
b.Property<string>("StorageId")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("storage_id");
|
||||||
|
|
||||||
|
b.Property<string>("StorageUrl")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("storage_url");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("UploadedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("uploaded_at");
|
||||||
|
|
||||||
|
b.Property<string>("UploadedTo")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("uploaded_to");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("file_id");
|
||||||
|
|
||||||
|
b.Property<string>("ResourceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("resource_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<BillingConfig>("BillingConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("billing_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("storage_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddCloudFileEncrypt : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "is_encrypted",
|
||||||
|
table: "files",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "is_encrypted",
|
||||||
|
table: "files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
277
DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs
generated
Normal file
277
DysonNetwork.Drive/Migrations/20250725183846_EnrichCloudPoolConfigure.Designer.cs
generated
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<string>("Id")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("file_meta");
|
||||||
|
|
||||||
|
b.Property<bool>("HasCompression")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_compression");
|
||||||
|
|
||||||
|
b.Property<bool>("HasThumbnail")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_thumbnail");
|
||||||
|
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEncrypted")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_encrypted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMarkedRecycle")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_marked_recycle");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("mime_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PoolId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("pool_id");
|
||||||
|
|
||||||
|
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("sensitive_marks");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size");
|
||||||
|
|
||||||
|
b.Property<string>("StorageId")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("storage_id");
|
||||||
|
|
||||||
|
b.Property<string>("StorageUrl")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("storage_url");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("UploadedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("uploaded_at");
|
||||||
|
|
||||||
|
b.Property<string>("UploadedTo")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("uploaded_to");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("file_id");
|
||||||
|
|
||||||
|
b.Property<string>("ResourceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("resource_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowAnonymous")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_anonymous");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowEncryption")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_encryption");
|
||||||
|
|
||||||
|
b.Property<BillingConfig>("BillingConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("billing_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<bool>("NoMetadata")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_metadata");
|
||||||
|
|
||||||
|
b.Property<bool>("NoOptimization")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_optimization");
|
||||||
|
|
||||||
|
b.Property<int>("RequirePrivilege")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("require_privilege");
|
||||||
|
|
||||||
|
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("storage_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class EnrichCloudPoolConfigure : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "allow_anonymous",
|
||||||
|
table: "pools",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "allow_encryption",
|
||||||
|
table: "pools",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "no_metadata",
|
||||||
|
table: "pools",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "no_optimization",
|
||||||
|
table: "pools",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "require_privilege",
|
||||||
|
table: "pools",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
276
DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs
generated
Normal file
276
DysonNetwork.Drive/Migrations/20250725184107_NullableFileMeta.Designer.cs
generated
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<string>("Id")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("file_meta");
|
||||||
|
|
||||||
|
b.Property<bool>("HasCompression")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_compression");
|
||||||
|
|
||||||
|
b.Property<bool>("HasThumbnail")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_thumbnail");
|
||||||
|
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEncrypted")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_encrypted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMarkedRecycle")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_marked_recycle");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("mime_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PoolId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("pool_id");
|
||||||
|
|
||||||
|
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("sensitive_marks");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size");
|
||||||
|
|
||||||
|
b.Property<string>("StorageId")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("storage_id");
|
||||||
|
|
||||||
|
b.Property<string>("StorageUrl")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("storage_url");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("UploadedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("uploaded_at");
|
||||||
|
|
||||||
|
b.Property<string>("UploadedTo")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("uploaded_to");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("file_id");
|
||||||
|
|
||||||
|
b.Property<string>("ResourceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("resource_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowAnonymous")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_anonymous");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowEncryption")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_encryption");
|
||||||
|
|
||||||
|
b.Property<BillingConfig>("BillingConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("billing_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<bool>("NoMetadata")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_metadata");
|
||||||
|
|
||||||
|
b.Property<bool>("NoOptimization")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_optimization");
|
||||||
|
|
||||||
|
b.Property<int>("RequirePrivilege")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("require_privilege");
|
||||||
|
|
||||||
|
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("storage_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class NullableFileMeta : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<Dictionary<string, object>>(
|
||||||
|
name: "file_meta",
|
||||||
|
table: "files",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(Dictionary<string, object>),
|
||||||
|
oldType: "jsonb");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<Dictionary<string, object>>(
|
||||||
|
name: "file_meta",
|
||||||
|
table: "files",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(Dictionary<string, object>),
|
||||||
|
oldType: "jsonb",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -52,7 +52,6 @@ namespace DysonNetwork.Drive.Migrations
|
|||||||
.HasColumnName("description");
|
.HasColumnName("description");
|
||||||
|
|
||||||
b.Property<Dictionary<string, object>>("FileMeta")
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("file_meta");
|
.HasColumnName("file_meta");
|
||||||
|
|
||||||
@@ -69,6 +68,10 @@ namespace DysonNetwork.Drive.Migrations
|
|||||||
.HasColumnType("character varying(256)")
|
.HasColumnType("character varying(256)")
|
||||||
.HasColumnName("hash");
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEncrypted")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_encrypted");
|
||||||
|
|
||||||
b.Property<bool>("IsMarkedRecycle")
|
b.Property<bool>("IsMarkedRecycle")
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasColumnName("is_marked_recycle");
|
.HasColumnName("is_marked_recycle");
|
||||||
@@ -189,6 +192,14 @@ namespace DysonNetwork.Drive.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowAnonymous")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_anonymous");
|
||||||
|
|
||||||
|
b.Property<bool>("AllowEncryption")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("allow_encryption");
|
||||||
|
|
||||||
b.Property<BillingConfig>("BillingConfig")
|
b.Property<BillingConfig>("BillingConfig")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
@@ -208,6 +219,18 @@ namespace DysonNetwork.Drive.Migrations
|
|||||||
.HasColumnType("character varying(1024)")
|
.HasColumnType("character varying(1024)")
|
||||||
.HasColumnName("name");
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<bool>("NoMetadata")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_metadata");
|
||||||
|
|
||||||
|
b.Property<bool>("NoOptimization")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("no_optimization");
|
||||||
|
|
||||||
|
b.Property<int>("RequirePrivilege")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("require_privilege");
|
||||||
|
|
||||||
b.Property<RemoteStorageConfig>("StorageConfig")
|
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
|
@@ -33,8 +33,8 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
|||||||
|
|
||||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||||
[MaxLength(4096)] public string? Description { get; set; }
|
[MaxLength(4096)] public string? Description { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object?> FileMeta { get; set; } = null!;
|
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? FileMeta { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? UserMeta { get; set; } = null!;
|
[Column(TypeName = "jsonb")] public Dictionary<string, object?>? UserMeta { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||||
[MaxLength(256)] public string? MimeType { get; set; }
|
[MaxLength(256)] public string? MimeType { get; set; }
|
||||||
[MaxLength(256)] public string? Hash { get; set; }
|
[MaxLength(256)] public string? Hash { get; set; }
|
||||||
@@ -42,6 +42,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
|||||||
public Instant? UploadedAt { get; set; }
|
public Instant? UploadedAt { get; set; }
|
||||||
public bool HasCompression { get; set; } = false;
|
public bool HasCompression { get; set; } = false;
|
||||||
public bool HasThumbnail { get; set; } = false;
|
public bool HasThumbnail { get; set; } = false;
|
||||||
|
public bool IsEncrypted { get; set; } = false;
|
||||||
|
|
||||||
[JsonIgnore] public FilePool? Pool { get; set; }
|
[JsonIgnore] public FilePool? Pool { get; set; }
|
||||||
public Guid? PoolId { get; set; }
|
public Guid? PoolId { get; set; }
|
||||||
|
60
DysonNetwork.Drive/Storage/FileEncryptor.cs
Normal file
60
DysonNetwork.Drive/Storage/FileEncryptor.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Storage;
|
||||||
|
|
||||||
|
public static class FileEncryptor
|
||||||
|
{
|
||||||
|
public static void EncryptFile(string inputPath, string outputPath, string password)
|
||||||
|
{
|
||||||
|
var salt = RandomNumberGenerator.GetBytes(16);
|
||||||
|
var key = DeriveKey(password, salt, 32);
|
||||||
|
var nonce = RandomNumberGenerator.GetBytes(12); // For AES-GCM
|
||||||
|
|
||||||
|
using var aes = new AesGcm(key, 16); // Specify 16-byte tag size explicitly
|
||||||
|
var plaintext = File.ReadAllBytes(inputPath);
|
||||||
|
var magic = "DYSON1"u8.ToArray();
|
||||||
|
var contentWithMagic = new byte[magic.Length + plaintext.Length];
|
||||||
|
Buffer.BlockCopy(magic, 0, contentWithMagic, 0, magic.Length);
|
||||||
|
Buffer.BlockCopy(plaintext, 0, contentWithMagic, magic.Length, plaintext.Length);
|
||||||
|
|
||||||
|
var ciphertext = new byte[contentWithMagic.Length];
|
||||||
|
var tag = new byte[16];
|
||||||
|
aes.Encrypt(nonce, contentWithMagic, ciphertext, tag);
|
||||||
|
|
||||||
|
// Save as: [salt (16)][nonce (12)][tag (16)][ciphertext]
|
||||||
|
using var fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
|
||||||
|
fs.Write(salt);
|
||||||
|
fs.Write(nonce);
|
||||||
|
fs.Write(tag);
|
||||||
|
fs.Write(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DecryptFile(string inputPath, string outputPath, string password)
|
||||||
|
{
|
||||||
|
var input = File.ReadAllBytes(inputPath);
|
||||||
|
|
||||||
|
var salt = input[..16];
|
||||||
|
var nonce = input[16..28];
|
||||||
|
var tag = input[28..44];
|
||||||
|
var ciphertext = input[44..];
|
||||||
|
|
||||||
|
var key = DeriveKey(password, salt, 32);
|
||||||
|
var decrypted = new byte[ciphertext.Length];
|
||||||
|
|
||||||
|
using var aes = new AesGcm(key, 16); // Specify 16-byte tag size explicitly
|
||||||
|
aes.Decrypt(nonce, ciphertext, tag, decrypted);
|
||||||
|
|
||||||
|
var magic = "DYSON1"u8.ToArray();
|
||||||
|
if (magic.Where((t, i) => decrypted[i] != t).Any())
|
||||||
|
throw new CryptographicException("Incorrect password or corrupted file.");
|
||||||
|
|
||||||
|
var plaintext = decrypted[magic.Length..];
|
||||||
|
File.WriteAllBytes(outputPath, plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] DeriveKey(string password, byte[] salt, int keyBytes)
|
||||||
|
{
|
||||||
|
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
|
||||||
|
return pbkdf2.GetBytes(keyBytes);
|
||||||
|
}
|
||||||
|
}
|
@@ -7,8 +7,6 @@ namespace DysonNetwork.Drive.Storage;
|
|||||||
|
|
||||||
public class RemoteStorageConfig
|
public class RemoteStorageConfig
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
public string Label { get; set; } = string.Empty;
|
|
||||||
public string Region { get; set; } = string.Empty;
|
public string Region { get; set; } = string.Empty;
|
||||||
public string Bucket { get; set; } = string.Empty;
|
public string Bucket { get; set; } = string.Empty;
|
||||||
public string Endpoint { get; set; } = string.Empty;
|
public string Endpoint { get; set; } = string.Empty;
|
||||||
@@ -32,6 +30,11 @@ public class FilePool : ModelBase, IIdentifiedResource
|
|||||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||||
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
|
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
|
||||||
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { 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}";
|
public string ResourceIdentifier => $"file-pool/{Id}";
|
||||||
}
|
}
|
@@ -102,16 +102,32 @@ public class FileService(
|
|||||||
public async Task<CloudFile> ProcessNewFileAsync(
|
public async Task<CloudFile> ProcessNewFileAsync(
|
||||||
Account account,
|
Account account,
|
||||||
string fileId,
|
string fileId,
|
||||||
|
string filePool,
|
||||||
Stream stream,
|
Stream stream,
|
||||||
string fileName,
|
string fileName,
|
||||||
string? contentType
|
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<string>("Tus:StorePath"), fileId));
|
var ogFilePath = Path.GetFullPath(Path.Join(configuration.GetValue<string>("Tus:StorePath"), fileId));
|
||||||
var fileSize = stream.Length;
|
var fileSize = stream.Length;
|
||||||
var hash = await HashFileAsync(stream, fileSize: fileSize);
|
|
||||||
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
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
|
var file = new CloudFile
|
||||||
{
|
{
|
||||||
Id = fileId,
|
Id = fileId,
|
||||||
@@ -119,7 +135,8 @@ public class FileService(
|
|||||||
MimeType = contentType,
|
MimeType = contentType,
|
||||||
Size = fileSize,
|
Size = fileSize,
|
||||||
Hash = hash,
|
Hash = hash,
|
||||||
AccountId = Guid.Parse(account.Id)
|
AccountId = Guid.Parse(account.Id),
|
||||||
|
IsEncrypted = !string.IsNullOrWhiteSpace(encryptPassword)
|
||||||
};
|
};
|
||||||
|
|
||||||
var existingFile = await db.Files.AsNoTracking().FirstOrDefaultAsync(f => f.Hash == hash);
|
var existingFile = await db.Files.AsNoTracking().FirstOrDefaultAsync(f => f.Hash == hash);
|
||||||
@@ -143,6 +160,7 @@ public class FileService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract metadata on the current thread for a faster initial response
|
// Extract metadata on the current thread for a faster initial response
|
||||||
|
if (!pool.NoMetadata)
|
||||||
await ExtractMetadataAsync(file, ogFilePath, stream);
|
await ExtractMetadataAsync(file, ogFilePath, stream);
|
||||||
|
|
||||||
db.Files.Add(file);
|
db.Files.Add(file);
|
||||||
@@ -150,7 +168,7 @@ public class FileService(
|
|||||||
|
|
||||||
// Offload optimization (image conversion, thumbnailing) and uploading to a background task
|
// Offload optimization (image conversion, thumbnailing) and uploading to a background task
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
ProcessAndUploadInBackgroundAsync(file.Id, file.StorageId, contentType, ogFilePath, stream));
|
ProcessAndUploadInBackgroundAsync(file.Id, filePool, file.StorageId, contentType, ogFilePath, stream));
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@@ -258,9 +276,18 @@ public class FileService(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles file optimization (image compression, video thumbnailing) and uploads to remote storage in the background.
|
/// Handles file optimization (image compression, video thumbnailing) and uploads to remote storage in the background.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ProcessAndUploadInBackgroundAsync(string fileId, string storageId, string contentType,
|
private async Task ProcessAndUploadInBackgroundAsync(
|
||||||
string originalFilePath, Stream stream)
|
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
|
await using var bgStream = stream; // Ensure stream is disposed at the end of this task
|
||||||
using var scope = scopeFactory.CreateScope();
|
using var scope = scopeFactory.CreateScope();
|
||||||
var nfs = scope.ServiceProvider.GetRequiredService<FileService>();
|
var nfs = scope.ServiceProvider.GetRequiredService<FileService>();
|
||||||
@@ -275,6 +302,7 @@ public class FileService(
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Processing file {FileId} in background...", fileId);
|
logger.LogInformation("Processing file {FileId} in background...", fileId);
|
||||||
|
|
||||||
|
if (!pool.NoOptimization)
|
||||||
switch (contentType.Split('/')[0])
|
switch (contentType.Split('/')[0])
|
||||||
{
|
{
|
||||||
case "image" when !AnimatedImageTypes.Contains(contentType):
|
case "image" when !AnimatedImageTypes.Contains(contentType):
|
||||||
@@ -337,12 +365,13 @@ public class FileService(
|
|||||||
uploads.Add((originalFilePath, string.Empty, contentType, false));
|
uploads.Add((originalFilePath, string.Empty, contentType, false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else uploads.Add((originalFilePath, string.Empty, contentType, false));
|
||||||
|
|
||||||
logger.LogInformation("Optimized file {FileId}, now uploading...", fileId);
|
logger.LogInformation("Optimized file {FileId}, now uploading...", fileId);
|
||||||
|
|
||||||
if (uploads.Count > 0)
|
if (uploads.Count > 0)
|
||||||
{
|
{
|
||||||
var destPool = Guid.Parse(configuration.GetValue<string>("Storage:PreferredRemote")!);
|
var destPool = Guid.Parse(remoteId!);
|
||||||
var uploadTasks = uploads.Select(item =>
|
var uploadTasks = uploads.Select(item =>
|
||||||
nfs.UploadFileToRemoteAsync(storageId, destPool, item.FilePath, item.Suffix, item.ContentType,
|
nfs.UploadFileToRemoteAsync(storageId, destPool, item.FilePath, item.Suffix, item.ContentType,
|
||||||
item.SelfDestruct)
|
item.SelfDestruct)
|
||||||
|
@@ -44,6 +44,10 @@ public abstract class TusService
|
|||||||
if (!allowed.HasPermission)
|
if (!allowed.HasPermission)
|
||||||
eventContext.FailRequest(HttpStatusCode.Forbidden);
|
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 =>
|
OnFileCompleteAsync = async eventContext =>
|
||||||
{
|
{
|
||||||
@@ -62,8 +66,22 @@ public abstract class TusService
|
|||||||
|
|
||||||
var fileStream = await file.GetContentAsync(eventContext.CancellationToken);
|
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<FileService>();
|
var fileService = services.GetRequiredService<FileService>();
|
||||||
var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType);
|
var info = await fileService.ProcessNewFileAsync(
|
||||||
|
user,
|
||||||
|
file.Id,
|
||||||
|
filePool,
|
||||||
|
fileStream,
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
encryptPassword
|
||||||
|
);
|
||||||
|
|
||||||
using var finalScope = eventContext.HttpContext.RequestServices.CreateScope();
|
using var finalScope = eventContext.HttpContext.RequestServices.CreateScope();
|
||||||
var jsonOptions = finalScope.ServiceProvider.GetRequiredService<IOptions<JsonOptions>>().Value
|
var jsonOptions = finalScope.ServiceProvider.GetRequiredService<IOptions<JsonOptions>>().Value
|
||||||
@@ -80,7 +98,7 @@ public abstract class TusService
|
|||||||
if (gatewayUrl is not null)
|
if (gatewayUrl is not null)
|
||||||
eventContext.SetUploadUrl(new Uri(gatewayUrl + "/drive/tus/" + eventContext.FileId));
|
eventContext.SetUploadUrl(new Uri(gatewayUrl + "/drive/tus/" + eventContext.FileId));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Debug": true,
|
"Debug": true,
|
||||||
"BaseUrl": "http://localhost:5071",
|
"BaseUrl": "http://localhost:5071",
|
||||||
"GatewayUrl": "http://localhost:5094",
|
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
@@ -42,7 +41,7 @@
|
|||||||
"StorePath": "Uploads"
|
"StorePath": "Uploads"
|
||||||
},
|
},
|
||||||
"Storage": {
|
"Storage": {
|
||||||
"PreferredRemote": "minio",
|
"PreferredRemote": "2adceae3-981a-4564-9b8d-5d71a211c873",
|
||||||
"Remote": [
|
"Remote": [
|
||||||
{
|
{
|
||||||
"Id": "minio",
|
"Id": "minio",
|
||||||
|
@@ -37,9 +37,6 @@ const userStore = useUserStore()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// Initialize user state on component mount
|
|
||||||
userStore.initialize()
|
|
||||||
|
|
||||||
const hideUserMenu = computed(() => {
|
const hideUserMenu = computed(() => {
|
||||||
return ['captcha', 'spells', 'login', 'create-account'].includes(route.name as string)
|
return ['captcha', 'spells', 'login', 'create-account'].includes(route.name as string)
|
||||||
})
|
})
|
||||||
|
@@ -42,8 +42,8 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// Initialize user state if not already initialized
|
// Initialize user state if not already initialized
|
||||||
if (!userStore.user && localStorage.getItem('authToken')) {
|
if (!userStore.user) {
|
||||||
await userStore.initialize()
|
await userStore.fetchUser(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.matched.some((record) => record.meta.requiresAuth) && !userStore.isAuthenticated) {
|
if (to.matched.some((record) => record.meta.requiresAuth) && !userStore.isAuthenticated) {
|
||||||
|
@@ -1,3 +1,22 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export const useServicesStore = defineStore('services', () => {})
|
export const useServicesStore = defineStore('services', () => {
|
||||||
|
const services = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
async function fetchServices() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/cgi/.well-known/services')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
services.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch services:', error)
|
||||||
|
services.value = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { services, fetchServices }
|
||||||
|
})
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
// State
|
// State
|
||||||
@@ -11,19 +11,13 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const isAuthenticated = computed(() => !!user.value)
|
const isAuthenticated = computed(() => !!user.value)
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
async function fetchUser() {
|
async function fetchUser(reload = true) {
|
||||||
const token = localStorage.getItem('authToken')
|
if (!reload && user.value) return // Skip fetching if already loaded and not forced to
|
||||||
if (!token) {
|
|
||||||
return // No token, no need to fetch
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/accounts/me', {
|
const response = await fetch('/api/accounts/me', {
|
||||||
headers: {
|
credentials: 'include',
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -50,9 +44,21 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
// router.push('/login')
|
// router.push('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize() {
|
watch(
|
||||||
await fetchUser()
|
user,
|
||||||
}
|
(_) => {
|
||||||
|
// Broadcast user changes to other subapps
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: 'DY:LOGIN_STATUS_CHANGE',
|
||||||
|
data: user.value != null,
|
||||||
|
},
|
||||||
|
'*',
|
||||||
|
)
|
||||||
|
console.log(`[SYNC] Message sent to parent: Login status changed to ${status}`)
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
@@ -61,6 +67,5 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
logout,
|
logout,
|
||||||
initialize
|
|
||||||
}
|
}
|
||||||
})
|
})
|
@@ -32,7 +32,3 @@ async function fetchVersion() {
|
|||||||
|
|
||||||
onMounted(() => fetchVersion())
|
onMounted(() => fetchVersion())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* Add any specific styles here if needed, though Tailwind should handle most. */
|
|
||||||
</style>
|
|
||||||
|
@@ -30,7 +30,6 @@ onMounted(async () => {
|
|||||||
const fp = await FingerprintJS.load()
|
const fp = await FingerprintJS.load()
|
||||||
const result = await fp.get()
|
const result = await fp.get()
|
||||||
deviceId.value = result.visitorId
|
deviceId.value = result.visitorId
|
||||||
localStorage.setItem('deviceId', deviceId.value)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedFactor = computed(() => {
|
const selectedFactor = computed(() => {
|
||||||
@@ -214,7 +213,6 @@ async function exchangeToken() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { token } = await response.json()
|
const { token } = await response.json()
|
||||||
localStorage.setItem('authToken', token)
|
|
||||||
await userStore.fetchUser()
|
await userStore.fetchUser()
|
||||||
|
|
||||||
const redirectUri = route.query.redirect_uri as string
|
const redirectUri = route.query.redirect_uri as string
|
||||||
|
@@ -29,11 +29,11 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:5090',
|
target: 'http://localhost:5216',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
'/cgi': {
|
'/cgi': {
|
||||||
target: 'http://localhost:5090',
|
target: 'http://localhost:5216',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAccessToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fb370f448e9f5fca62da785172d83a214319335e27ac4d51840349c6dce15d68_003FAccessToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAccessToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fb370f448e9f5fca62da785172d83a214319335e27ac4d51840349c6dce15d68_003FAccessToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAesGcm_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d932a3ff98244208ca84309a75a7734243600_003F2c_003F1063867b_003FAesGcm_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAny_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003F67_003F87f868e3_003FAny_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAny_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003F67_003F87f868e3_003FAny_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Reference in New Issue
Block a user