mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 07:40:45 -08:00
feat: export files
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
"@fastify/multipart": "^8.2.0",
|
||||
"@fastify/rate-limit": "^9.1.0",
|
||||
"@fastify/sensible": "^5.5.0",
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@github/webauthn-json": "^2.1.1",
|
||||
"@mantine/code-highlight": "^7.2.2",
|
||||
"@mantine/core": "^7.2.2",
|
||||
@@ -52,6 +53,7 @@
|
||||
"fast-glob": "^3.3.2",
|
||||
"fastify": "^4.26.2",
|
||||
"fastify-plugin": "^4.5.1",
|
||||
"fflate": "^0.8.2",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"highlight.js": "^11.9.0",
|
||||
"isomorphic-dompurify": "^1.11.0",
|
||||
|
||||
194
pnpm-lock.yaml
generated
194
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
||||
'@fastify/sensible':
|
||||
specifier: ^5.5.0
|
||||
version: 5.5.0
|
||||
'@fastify/static':
|
||||
specifier: ^7.0.4
|
||||
version: 7.0.4
|
||||
'@github/webauthn-json':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
@@ -104,6 +107,9 @@ importers:
|
||||
fastify-plugin:
|
||||
specifier: ^4.5.1
|
||||
version: 4.5.1
|
||||
fflate:
|
||||
specifier: ^0.8.2
|
||||
version: 0.8.2
|
||||
fluent-ffmpeg:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
@@ -960,6 +966,10 @@ packages:
|
||||
resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
'@fastify/accept-negotiator@1.1.0':
|
||||
resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@fastify/ajv-compiler@3.5.0':
|
||||
resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==}
|
||||
|
||||
@@ -991,9 +1001,15 @@ packages:
|
||||
'@fastify/rate-limit@9.1.0':
|
||||
resolution: {integrity: sha512-h5dZWCkuZXN0PxwqaFQLxeln8/LNwQwH9popywmDCFdKfgpi4b/HoMH1lluy6P+30CG9yzzpSpwTCIPNB9T1JA==}
|
||||
|
||||
'@fastify/send@2.1.0':
|
||||
resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==}
|
||||
|
||||
'@fastify/sensible@5.5.0':
|
||||
resolution: {integrity: sha512-D0zpl+nocsRXLceSbc4gasQaO3ZNQR4dy9Uu8Ym0mh8VUdrjpZ4g8Ca9O3pGXbBVOnPIGHUJNTV7Yf9dg/OYdg==}
|
||||
|
||||
'@fastify/static@7.0.4':
|
||||
resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==}
|
||||
|
||||
'@floating-ui/core@1.5.1':
|
||||
resolution: {integrity: sha512-QgcKYwzcc8vvZ4n/5uklchy8KVdjJwcOeI+HnnTNclJjs2nYsy23DOCf+sSV1kBwD9yDAoVKCkv/gEPzgQU3Pw==}
|
||||
|
||||
@@ -1030,6 +1046,10 @@ packages:
|
||||
'@humanwhocodes/object-schema@2.0.1':
|
||||
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.3':
|
||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -1228,6 +1248,10 @@ packages:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@pkgr/utils@2.4.2':
|
||||
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
@@ -1765,6 +1789,10 @@ packages:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-regex@6.0.1:
|
||||
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ansi-styles@2.2.1:
|
||||
resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1777,6 +1805,10 @@ packages:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@6.2.1:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
@@ -2396,6 +2428,9 @@ packages:
|
||||
resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==}
|
||||
hasBin: true
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
|
||||
@@ -2714,6 +2749,9 @@ packages:
|
||||
fecha@4.2.3:
|
||||
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@@ -2763,6 +2801,10 @@ packages:
|
||||
for-each@0.3.3:
|
||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||
|
||||
foreground-child@3.3.0:
|
||||
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -2861,6 +2903,10 @@ packages:
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
glob@7.1.6:
|
||||
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
|
||||
|
||||
@@ -2869,10 +2915,12 @@ packages:
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
global-dirs@3.0.1:
|
||||
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
|
||||
@@ -3233,6 +3281,9 @@ packages:
|
||||
iterator.prototype@1.1.2:
|
||||
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
jdataview@2.5.0:
|
||||
resolution: {integrity: sha512-ZJop3D5nyDcWPBPv4NPnhCvx3HgQNsCXMfw8gpNKY16BobgxmVF+kJ08aHuqk6bJQVeL2mkf6nDCcZPMompalw==}
|
||||
|
||||
@@ -3452,6 +3503,9 @@ packages:
|
||||
resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
@@ -3643,6 +3697,11 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mime@3.0.0:
|
||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3666,6 +3725,10 @@ packages:
|
||||
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
@@ -3677,6 +3740,10 @@ packages:
|
||||
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4002,6 +4069,9 @@ packages:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
package-json-from-dist@1.0.0:
|
||||
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
|
||||
|
||||
packet-reader@1.0.0:
|
||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
|
||||
|
||||
@@ -4043,6 +4113,10 @@ packages:
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
|
||||
|
||||
@@ -4596,6 +4670,10 @@ packages:
|
||||
signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
signal-exit@4.1.0:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
|
||||
@@ -4693,6 +4771,10 @@ packages:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
string-width@5.1.2:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
string.prototype.matchall@4.0.10:
|
||||
resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==}
|
||||
|
||||
@@ -4720,6 +4802,10 @@ packages:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@7.1.0:
|
||||
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-bom@3.0.0:
|
||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5259,6 +5345,14 @@ packages:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
@@ -6450,6 +6544,8 @@ snapshots:
|
||||
|
||||
'@eslint/js@8.54.0': {}
|
||||
|
||||
'@fastify/accept-negotiator@1.1.0': {}
|
||||
|
||||
'@fastify/ajv-compiler@3.5.0':
|
||||
dependencies:
|
||||
ajv: 8.12.0
|
||||
@@ -6495,6 +6591,14 @@ snapshots:
|
||||
fastify-plugin: 4.5.1
|
||||
toad-cache: 3.7.0
|
||||
|
||||
'@fastify/send@2.1.0':
|
||||
dependencies:
|
||||
'@lukeed/ms': 2.0.2
|
||||
escape-html: 1.0.3
|
||||
fast-decode-uri-component: 1.0.1
|
||||
http-errors: 2.0.0
|
||||
mime: 3.0.0
|
||||
|
||||
'@fastify/sensible@5.5.0':
|
||||
dependencies:
|
||||
'@lukeed/ms': 2.0.2
|
||||
@@ -6505,6 +6609,15 @@ snapshots:
|
||||
type-is: 1.6.18
|
||||
vary: 1.1.2
|
||||
|
||||
'@fastify/static@7.0.4':
|
||||
dependencies:
|
||||
'@fastify/accept-negotiator': 1.1.0
|
||||
'@fastify/send': 2.1.0
|
||||
content-disposition: 0.5.4
|
||||
fastify-plugin: 4.5.1
|
||||
fastq: 1.17.1
|
||||
glob: 10.4.5
|
||||
|
||||
'@floating-ui/core@1.5.1':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.1.6
|
||||
@@ -6544,6 +6657,15 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/object-schema@2.0.1': {}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
string-width-cjs: string-width@4.2.3
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi-cjs: strip-ansi@6.0.1
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.3':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.1.2
|
||||
@@ -6734,6 +6856,9 @@ snapshots:
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@pkgr/utils@2.4.2':
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
@@ -7550,6 +7675,8 @@ snapshots:
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.0.1: {}
|
||||
|
||||
ansi-styles@2.2.1: {}
|
||||
|
||||
ansi-styles@3.2.1:
|
||||
@@ -7560,6 +7687,8 @@ snapshots:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@6.2.1: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
@@ -8173,6 +8302,8 @@ snapshots:
|
||||
dependencies:
|
||||
minimatch: 3.1.2
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -8717,6 +8848,8 @@ snapshots:
|
||||
|
||||
fecha@4.2.3: {}
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
dependencies:
|
||||
flat-cache: 3.2.0
|
||||
@@ -8788,6 +8921,11 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
foreground-child@3.3.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.0:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
@@ -8884,6 +9022,15 @@ snapshots:
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
glob@10.4.5:
|
||||
dependencies:
|
||||
foreground-child: 3.3.0
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.0
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@7.1.6:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@@ -9249,6 +9396,12 @@ snapshots:
|
||||
reflect.getprototypeof: 1.0.4
|
||||
set-function-name: 2.0.1
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jdataview@2.5.0: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
@@ -9473,6 +9626,8 @@ snapshots:
|
||||
|
||||
lru-cache@10.1.0: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -9827,6 +9982,8 @@ snapshots:
|
||||
|
||||
mime@1.6.0: {}
|
||||
|
||||
mime@3.0.0: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
@@ -9843,6 +10000,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
minipass@3.3.6:
|
||||
@@ -9851,6 +10012,8 @@ snapshots:
|
||||
|
||||
minipass@5.0.0: {}
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minizlib@2.1.2:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
@@ -10203,6 +10366,8 @@ snapshots:
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
package-json-from-dist@1.0.0: {}
|
||||
|
||||
packet-reader@1.0.0: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
@@ -10234,6 +10399,11 @@ snapshots:
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
path-to-regexp@0.1.7: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
@@ -10845,6 +11015,8 @@ snapshots:
|
||||
|
||||
signal-exit@3.0.7: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
simple-concat@1.0.1: {}
|
||||
|
||||
simple-get@4.0.1:
|
||||
@@ -10938,6 +11110,12 @@ snapshots:
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
string-width@5.1.2:
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
string.prototype.matchall@4.0.10:
|
||||
dependencies:
|
||||
call-bind: 1.0.5
|
||||
@@ -10984,6 +11162,10 @@ snapshots:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-ansi@7.1.0:
|
||||
dependencies:
|
||||
ansi-regex: 6.0.1
|
||||
|
||||
strip-bom@3.0.0: {}
|
||||
|
||||
strip-final-newline@2.0.0: {}
|
||||
@@ -11572,6 +11754,18 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.16.0: {}
|
||||
|
||||
@@ -11,6 +11,7 @@ import SettingsMfa from './parts/SettingsMfa';
|
||||
import SettingsOAuth from './parts/SettingsOAuth';
|
||||
import SettingsServerActions from './parts/SettingsServerUtil';
|
||||
import SettingsUser from './parts/SettingsUser';
|
||||
import SettingsExports from './parts/SettingsExports';
|
||||
|
||||
export default function DashboardSettings() {
|
||||
const config = useConfig();
|
||||
@@ -37,6 +38,8 @@ export default function DashboardSettings() {
|
||||
<SettingsGenerators />
|
||||
|
||||
{isAdministrator(user?.role) && <SettingsServerActions />}
|
||||
|
||||
<SettingsExports />
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
|
||||
123
src/components/pages/settings/parts/SettingsExports.tsx
Normal file
123
src/components/pages/settings/parts/SettingsExports.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { ActionIcon, Button, Paper, ScrollArea, Table, Title } from '@mantine/core';
|
||||
import { modals } from '@mantine/modals';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconPlus, IconTrashFilled } from '@tabler/icons-react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export default function SettingsExports() {
|
||||
const { data, isLoading, mutate } = useSWR<Response['/api/user/export']>('/api/user/export', {
|
||||
refreshInterval: 5000,
|
||||
});
|
||||
|
||||
const handleNewExport = async () => {
|
||||
modals.openConfirmModal({
|
||||
title: <Title>New Export?</Title>,
|
||||
children:
|
||||
'Are you sure you want to start a new export? If you have a lot of files, this may take a while.',
|
||||
onConfirm: async () => {
|
||||
await fetch('/api/user/export', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Export started',
|
||||
message: 'Export has been started, you can check its status in the table below',
|
||||
color: 'blue',
|
||||
loading: true,
|
||||
});
|
||||
mutate();
|
||||
},
|
||||
labels: {
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Start export',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (name: string) => {
|
||||
await fetch(`/api/user/export?name=${name}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Export deleted',
|
||||
message: 'Export has been deleted',
|
||||
color: 'red',
|
||||
});
|
||||
|
||||
mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>Export Files</Title>
|
||||
|
||||
<Button
|
||||
mt='sm'
|
||||
fullWidth
|
||||
color='blue'
|
||||
disabled={isLoading}
|
||||
onClick={handleNewExport}
|
||||
leftSection={<IconPlus size='1rem' />}
|
||||
>
|
||||
New Export
|
||||
</Button>
|
||||
|
||||
<Title order={4} mt='sm'>
|
||||
Completed Exports
|
||||
</Title>
|
||||
<ScrollArea.Autosize mah={500} type='auto'>
|
||||
<Table highlightOnHover stickyHeader>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Name</Table.Th>
|
||||
<Table.Th>Started On</Table.Th>
|
||||
<Table.Th>Files</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{isLoading && <Table.Tr>Loading...</Table.Tr>}
|
||||
{data?.complete.map((file) => (
|
||||
<Table.Tr key={file.name}>
|
||||
<Table.Td>{file.name}</Table.Td>
|
||||
<Table.Td>{new Date(file.date).toLocaleString()}</Table.Td>
|
||||
<Table.Td>{file.files}</Table.Td>
|
||||
<Table.Td>
|
||||
<ActionIcon onClick={() => handleDelete(file.name)}>
|
||||
<IconTrashFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea.Autosize>
|
||||
|
||||
<Title order={4} mt='sm'>
|
||||
Running Exports
|
||||
</Title>
|
||||
<ScrollArea.Autosize mah={500} type='auto'>
|
||||
<Table highlightOnHover stickyHeader>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Name</Table.Th>
|
||||
<Table.Th>Started On</Table.Th>
|
||||
<Table.Th>Files</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{isLoading && <Table.Tr>Loading...</Table.Tr>}
|
||||
{data?.running.map((file) => (
|
||||
<Table.Tr key={file.name}>
|
||||
<Table.Td>{file.name}</Table.Td>
|
||||
<Table.Td>{new Date(file.date).toLocaleString()}</Table.Td>
|
||||
<Table.Td>{file.files}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea.Autosize>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { ApiSetupResponse } from '@/server/routes/api/setup';
|
||||
import { ApiStatsResponse } from '@/server/routes/api/stats';
|
||||
import { ApiUploadResponse } from '@/server/routes/api/upload';
|
||||
import { ApiUserResponse } from '@/server/routes/api/user';
|
||||
import { ApiUserExportResponse } from '@/server/routes/api/user/export';
|
||||
import { ApiUserFilesResponse } from '@/server/routes/api/user/files';
|
||||
import { ApiUserFilesIdResponse } from '@/server/routes/api/user/files/[id]';
|
||||
import { ApiUserFilesIdPasswordResponse } from '@/server/routes/api/user/files/[id]/password';
|
||||
@@ -60,6 +61,7 @@ export type Response = {
|
||||
'/api/user/stats': ApiUserStatsResponse;
|
||||
'/api/user/recent': ApiUserRecentResponse;
|
||||
'/api/user/token': ApiUserTokenResponse;
|
||||
'/api/user/export': ApiUserExportResponse;
|
||||
'/api/users': ApiUsersResponse;
|
||||
'/api/users/[id]': ApiUsersIdResponse;
|
||||
'/api/server/clear_temp': ApiServerClearTempResponse;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { fastifyCors } from '@fastify/cors';
|
||||
import { fastifyMultipart } from '@fastify/multipart';
|
||||
import { fastifyRateLimit } from '@fastify/rate-limit';
|
||||
import { fastifySensible } from '@fastify/sensible';
|
||||
import { fastifyStatic } from '@fastify/static';
|
||||
import fastify from 'fastify';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import { parse } from 'url';
|
||||
@@ -78,6 +79,11 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
await server.register(fastifyStatic, {
|
||||
serve: false,
|
||||
root: '/',
|
||||
});
|
||||
|
||||
if (config.ratelimit.enabled) {
|
||||
try {
|
||||
checkRateLimit(config);
|
||||
|
||||
204
src/server/routes/api/user/export.ts
Normal file
204
src/server/routes/api/user/export.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { config } from '@/lib/config';
|
||||
import { datasource } from '@/lib/datasource';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { log } from '@/lib/logger';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import { Zip, ZipPassThrough } from 'fflate';
|
||||
import { createWriteStream } from 'fs';
|
||||
import { readdir, rename, rm } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
export type ApiUserExportResponse = {
|
||||
running?: boolean;
|
||||
deleted?: boolean;
|
||||
} & {
|
||||
[key in 'running' | 'complete']: {
|
||||
date: number;
|
||||
files: number;
|
||||
name: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
type Query = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export const PATH = '/api/user/export';
|
||||
|
||||
const logger = log('api').c('user').c('export');
|
||||
|
||||
export default fastifyPlugin(
|
||||
(server, _, done) => {
|
||||
server.get<{ Querystring: Query }>(PATH, { preHandler: [userMiddleware] }, async (req, res) => {
|
||||
const tmpFiles = await readdir(config.core.tempDirectory);
|
||||
const userExports = tmpFiles
|
||||
.filter((file) => file.startsWith(`zexport_${req.user.id}`) && file.endsWith('.zip'))
|
||||
.map((file) => file.split('_'))
|
||||
.filter((file) => file.length === 5);
|
||||
|
||||
const incompleteExports = userExports
|
||||
.filter((file) => file[file.length - 1] === 'incomplete.zip')
|
||||
.map((file) => ({
|
||||
date: Number(file[2]),
|
||||
files: Number(file[3]),
|
||||
name: file.join('_'),
|
||||
}));
|
||||
const completeExports = userExports
|
||||
.filter((file) => file[file.length - 1] === 'complete.zip')
|
||||
.map((file) => ({
|
||||
date: Number(file[2]),
|
||||
files: Number(file[3]),
|
||||
name: file.join('_'),
|
||||
}));
|
||||
|
||||
if (req.query.name) {
|
||||
const file = completeExports.find((file) => file.name === req.query.name);
|
||||
if (!file) return res.notFound();
|
||||
|
||||
const path = join(config.core.tempDirectory, file.name);
|
||||
return res.sendFile(path);
|
||||
}
|
||||
|
||||
return res.send({
|
||||
running: incompleteExports,
|
||||
complete: completeExports,
|
||||
});
|
||||
});
|
||||
|
||||
server.delete<{ Querystring: Query }>(PATH, { preHandler: [userMiddleware] }, async (req, res) => {
|
||||
if (!req.query.name) return res.badRequest('No name provided');
|
||||
|
||||
const tmpFiles = await readdir(config.core.tempDirectory);
|
||||
const userExports = tmpFiles
|
||||
.filter((file) => file.startsWith(`zexport_${req.user.id}`) && file.endsWith('.zip'))
|
||||
.map((file) => file.split('_'))
|
||||
.filter((file) => file.length === 5 && file[file.length - 1] === 'complete.zip')
|
||||
.map((file) => file.join('_'));
|
||||
|
||||
if (!userExports.includes(req.query.name)) return res.notFound();
|
||||
|
||||
const path = join(config.core.tempDirectory, req.query.name);
|
||||
await rm(path);
|
||||
|
||||
logger.info(`deleted export ${req.query.name}`);
|
||||
|
||||
return res.send({ deleted: true });
|
||||
});
|
||||
|
||||
server.post(PATH, { preHandler: [userMiddleware] }, async (req, res) => {
|
||||
const files = await prisma.file.findMany({
|
||||
where: { userId: req.user.id },
|
||||
});
|
||||
|
||||
if (!files.length) return res.badRequest('No files to export');
|
||||
|
||||
const exportFileName = `zexport_${req.user.id}_${Date.now()}_${files.length}_incomplete.zip`;
|
||||
const exportPath = join(config.core.tempDirectory, exportFileName);
|
||||
|
||||
logger.debug(`exporting ${req.user.id}`, { exportPath, files: files.length });
|
||||
|
||||
const writeStream = createWriteStream(exportPath);
|
||||
const zip = new Zip();
|
||||
|
||||
const onBackpressure = (stream: any, outputStream: any, cb: any) => {
|
||||
const runCb = () => {
|
||||
cb(applyOutputBackpressure || backpressureBytes > backpressureThreshold);
|
||||
};
|
||||
|
||||
const backpressureThreshold = 65536;
|
||||
const backpressure: number[] = [];
|
||||
let backpressureBytes = 0;
|
||||
const push = stream.push;
|
||||
stream.push = (data: string | any[], final: any) => {
|
||||
backpressure.push(data.length);
|
||||
backpressureBytes += data.length;
|
||||
runCb();
|
||||
push.call(stream, data, final);
|
||||
};
|
||||
let ondata = stream.ondata;
|
||||
const ondataPatched = (err: any, data: any, final: any) => {
|
||||
ondata.call(stream, err, data, final);
|
||||
backpressureBytes -= backpressure.shift()!;
|
||||
runCb();
|
||||
};
|
||||
if (ondata) {
|
||||
stream.ondata = ondataPatched;
|
||||
} else {
|
||||
Object.defineProperty(stream, 'ondata', {
|
||||
get: () => ondataPatched,
|
||||
set: (cb) => (ondata = cb),
|
||||
});
|
||||
}
|
||||
|
||||
let applyOutputBackpressure = false;
|
||||
const write = outputStream.write;
|
||||
outputStream.write = (data: any) => {
|
||||
const outputNotFull = write.call(outputStream, data);
|
||||
applyOutputBackpressure = !outputNotFull;
|
||||
runCb();
|
||||
};
|
||||
outputStream.on('drain', () => {
|
||||
applyOutputBackpressure = false;
|
||||
runCb();
|
||||
});
|
||||
};
|
||||
|
||||
zip.ondata = async (err, data, final) => {
|
||||
if (err) {
|
||||
writeStream.close();
|
||||
logger.debug('error while writing to zip', { err });
|
||||
logger.error(`export for ${req.user.id} failed`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
writeStream.write(data);
|
||||
|
||||
if (!final) return;
|
||||
|
||||
const newExportName = `zexport_${req.user.id}_${Date.now()}_${files.length}_complete.zip`;
|
||||
const path = join(config.core.tempDirectory, newExportName);
|
||||
|
||||
writeStream.end();
|
||||
logger.debug('exported', { path, bytes: data.length });
|
||||
logger.info(`export for ${req.user.id} finished at ${path}`);
|
||||
|
||||
await rename(exportPath, path);
|
||||
};
|
||||
|
||||
for (let i = 0; i !== files.length; ++i) {
|
||||
const file = files[i];
|
||||
|
||||
const stream = await datasource.get(file.name);
|
||||
if (!stream) {
|
||||
logger.warn(`failed to get file ${file.name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const passThrough = new ZipPassThrough(file.name);
|
||||
zip.add(passThrough);
|
||||
|
||||
onBackpressure(passThrough, stream, (applyBackpressure: boolean) => {
|
||||
if (applyBackpressure) {
|
||||
stream.pause();
|
||||
} else if (stream.isPaused()) {
|
||||
stream.resume();
|
||||
}
|
||||
});
|
||||
stream.on('data', (c) => passThrough.push(c));
|
||||
stream.on('end', () => {
|
||||
passThrough.push(new Uint8Array(0), true);
|
||||
logger.debug(`file ${i + 1}/${files.length} added to zip`, { name: file.name });
|
||||
});
|
||||
}
|
||||
|
||||
zip.end();
|
||||
|
||||
return res.send({ running: true });
|
||||
});
|
||||
|
||||
done();
|
||||
},
|
||||
{ name: PATH },
|
||||
);
|
||||
Reference in New Issue
Block a user