diff --git a/package.json b/package.json index d1a976d6..0944964a 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "swr": "^2.3.7", "typescript-eslint": "^8.48.1", "vite": "^7.2.7", - "zod": "^4.1.13", + "zod": "^4.3.6", "zustand": "^5.0.9" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3271c7f..253a5b75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,7 +145,7 @@ importers: version: 5.1.0 fastify-type-provider-zod: specifier: ^6.1.0 - version: 6.1.0(@fastify/swagger@9.6.1)(fastify@5.6.2)(openapi-types@12.1.3)(zod@4.1.13) + version: 6.1.0(@fastify/swagger@9.6.1)(fastify@5.6.2)(openapi-types@12.1.3)(zod@4.3.6) fluent-ffmpeg: specifier: ^2.1.3 version: 2.1.3 @@ -210,8 +210,8 @@ importers: specifier: ^7.2.7 version: 7.2.7(@types/node@24.10.1)(jiti@2.5.1)(sass@1.94.2)(sugarss@5.0.1(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) zod: - specifier: ^4.1.13 - version: 4.1.13 + specifier: ^4.3.6 + version: 4.3.6 zustand: specifier: ^5.0.9 version: 5.0.9(@types/react@19.2.7)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) @@ -1123,89 +1123,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1393,36 +1409,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1617,56 +1639,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -2004,8 +2037,8 @@ packages: '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - '@types/d3-shape@3.1.7': - resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} @@ -2607,8 +2640,8 @@ packages: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-interpolate@3.0.1: @@ -2978,8 +3011,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-equals@5.3.3: - resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} engines: {node: '>=6.0.0'} fast-fifo@1.3.2: @@ -3597,6 +3630,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -5206,8 +5242,8 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.1.13: - resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} zustand@5.0.9: resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} @@ -7392,7 +7428,7 @@ snapshots: dependencies: '@types/d3-time': 3.0.4 - '@types/d3-shape@3.1.7': + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 @@ -8039,7 +8075,7 @@ snapshots: d3-ease@3.0.1: {} - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-interpolate@3.0.1: dependencies: @@ -8050,7 +8086,7 @@ snapshots: d3-scale@4.0.2: dependencies: d3-array: 3.2.4 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -8402,8 +8438,8 @@ snapshots: '@babel/parser': 7.28.5 eslint: 9.39.1(jiti@2.5.1) hermes-parser: 0.25.1 - zod: 4.1.13 - zod-validation-error: 4.0.2(zod@4.1.13) + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color @@ -8535,7 +8571,7 @@ snapshots: fast-diff@1.3.0: {} - fast-equals@5.3.3: {} + fast-equals@5.4.0: {} fast-fifo@1.3.2: {} @@ -8572,13 +8608,13 @@ snapshots: fastify-plugin@5.1.0: {} - fastify-type-provider-zod@6.1.0(@fastify/swagger@9.6.1)(fastify@5.6.2)(openapi-types@12.1.3)(zod@4.1.13): + fastify-type-provider-zod@6.1.0(@fastify/swagger@9.6.1)(fastify@5.6.2)(openapi-types@12.1.3)(zod@4.3.6): dependencies: '@fastify/error': 4.2.0 '@fastify/swagger': 9.6.1 fastify: 5.6.2 openapi-types: 12.1.3 - zod: 4.1.13 + zod: 4.3.6 fastify@5.6.2: dependencies: @@ -9222,6 +9258,8 @@ snapshots: lodash@4.17.21: {} + lodash@4.17.23: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -10134,7 +10172,7 @@ snapshots: react-smooth@4.0.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: - fast-equals: 5.3.3 + fast-equals: 5.4.0 prop-types: 15.8.1 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -10233,7 +10271,7 @@ snapshots: dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 - lodash: 4.17.21 + lodash: 4.17.23 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-is: 18.3.1 @@ -11026,7 +11064,7 @@ snapshots: '@types/d3-ease': 3.0.2 '@types/d3-interpolate': 3.0.4 '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.7 + '@types/d3-shape': 3.1.8 '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 d3-array: 3.2.4 @@ -11183,11 +11221,11 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-validation-error@4.0.2(zod@4.1.13): + zod-validation-error@4.0.2(zod@4.3.6): dependencies: - zod: 4.1.13 + zod: 4.3.6 - zod@4.1.13: {} + zod@4.3.6: {} zustand@5.0.9(@types/react@19.2.7)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)): optionalDependencies: diff --git a/src/lib/import/version4/validateExport.ts b/src/lib/import/version4/validateExport.ts index 6038c22a..982d9b44 100644 --- a/src/lib/import/version4/validateExport.ts +++ b/src/lib/import/version4/validateExport.ts @@ -61,11 +61,11 @@ export const export4Schema = z.object({ id: z.string(), createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), username: z.string(), - password: z.string().nullable().optional(), - avatar: z.string().nullable().optional(), + password: z.string().nullish(), + avatar: z.string().nullish(), role: z.enum(Role), - view: z.record(z.string(), z.unknown()), - totpSecret: z.string().nullable().optional(), + view: z.record(z.string(), z.any()), + totpSecret: z.string().nullish(), }), ), userPasskeys: z.array( @@ -74,11 +74,10 @@ export const export4Schema = z.object({ createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), lastUsed: z .string() - .nullable() - .optional() + .nullish() .refine((date) => (date ? !isNaN(Date.parse(date)) : true), 'Invalid date'), name: z.string(), - reg: z.record(z.string(), z.unknown()), + reg: z.record(z.string(), z.any()), userId: z.string(), }), ), @@ -87,10 +86,10 @@ export const export4Schema = z.object({ id: z.string(), createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), filesQuota: z.enum(UserFilesQuota), - maxBytes: z.string().nullable().optional(), - maxFiles: z.number().nullable().optional(), - maxUrls: z.number().nullable().optional(), - userId: z.string().nullable().optional(), + maxBytes: z.string().nullish(), + maxFiles: z.number().nullish(), + maxUrls: z.number().nullish(), + userId: z.string().nullish(), }), ), userOauthProviders: z.array( @@ -100,8 +99,8 @@ export const export4Schema = z.object({ provider: z.enum(OAuthProviderType), username: z.string(), accessToken: z.string(), - refreshToken: z.string().nullable().optional(), - oauthId: z.string().nullable().optional(), + refreshToken: z.string().nullish(), + oauthId: z.string().nullish(), userId: z.string(), }), ), @@ -110,7 +109,7 @@ export const export4Schema = z.object({ id: z.string(), createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), name: z.string(), - color: z.string().nullable().optional(), + color: z.string().nullish(), files: z.array(z.string()), userId: z.string(), }), @@ -121,12 +120,11 @@ export const export4Schema = z.object({ createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), expiresAt: z .string() - .nullable() - .optional() + .nullish() .refine((date) => (date ? !isNaN(Date.parse(date)) : true), 'Invalid date'), code: z.string(), uses: z.number(), - maxUses: z.number().nullable().optional(), + maxUses: z.number().nullish(), inviterId: z.string(), }), ), @@ -139,7 +137,7 @@ export const export4Schema = z.object({ allowUploads: z.boolean(), files: z.array(z.string()), userId: z.string(), - parentId: z.string().nullable().optional(), + parentId: z.string().nullish(), }), ), urls: z.array( @@ -147,13 +145,13 @@ export const export4Schema = z.object({ id: z.string(), createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), code: z.string(), - vanity: z.string().nullable().optional(), + vanity: z.string().nullish(), destination: z.string(), views: z.number(), - maxViews: z.number().nullable().optional(), - password: z.string().nullable().optional(), + maxViews: z.number().nullish(), + password: z.string().nullish(), enabled: z.boolean(), - userId: z.string().nullable().optional(), + userId: z.string().nullish(), }), ), files: z.array( @@ -162,19 +160,18 @@ export const export4Schema = z.object({ createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), deletesAt: z .string() - .nullable() - .optional() + .nullish() .refine((date) => (date ? !isNaN(Date.parse(date)) : true), 'Invalid date'), name: z.string(), - originalName: z.string().nullable().optional(), + originalName: z.string().nullish(), size: z.number(), type: z.string(), views: z.number(), - maxViews: z.number().nullable().optional(), + maxViews: z.number().nullish(), favorite: z.boolean(), - password: z.string().nullable().optional(), + password: z.string().nullish(), userId: z.string().nullable(), - folderId: z.string().nullable().optional(), + folderId: z.string().nullish(), }), ), thumbnails: z.array( @@ -189,7 +186,7 @@ export const export4Schema = z.object({ z.object({ id: z.string(), createdAt: z.string().refine((date) => !isNaN(Date.parse(date)), 'Invalid date'), - data: z.record(z.string(), z.unknown()), + data: z.record(z.string(), z.any()), }), ), }), diff --git a/src/server/index.ts b/src/server/index.ts index 3f67b952..260219ac 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,3 +1,4 @@ +import { ApiError } from '@/lib/api/errors'; import { bytes } from '@/lib/bytes'; import { reloadSettings } from '@/lib/config'; import { checkDbVars, REQUIRED_DB_VARS } from '@/lib/config/read/env'; @@ -7,6 +8,7 @@ import { runMigrations } from '@/lib/db/migration'; import { log } from '@/lib/logger'; import { isAdministrator } from '@/lib/role'; import { Tasks } from '@/lib/tasks'; +import cleanThumbnails from '@/lib/tasks/run/cleanThumbnails'; import clearInvites from '@/lib/tasks/run/clearInvites'; import deleteFiles from '@/lib/tasks/run/deleteFiles'; import maxViews from '@/lib/tasks/run/maxViews'; @@ -22,6 +24,7 @@ import fastifySwagger from '@fastify/swagger'; import fastify from 'fastify'; import { hasZodFastifySchemaValidationErrors, + isResponseSerializationError, jsonSchemaTransform, serializerCompiler, validatorCompiler, @@ -36,8 +39,6 @@ import vitePlugin from './plugins/vite'; import loadRoutes from './routes'; import { filesRoute } from './routes/files.dy'; import { urlsRoute } from './routes/urls.dy'; -import cleanThumbnails from '@/lib/tasks/run/cleanThumbnails'; -import { API_ERRORS, ApiError } from '@/lib/api/errors'; const MODE = process.env.NODE_ENV || 'production'; const logger = log('server'); @@ -242,13 +243,24 @@ async function main() { server.setErrorHandler((error: any, _, res) => { if (hasZodFastifySchemaValidationErrors(error)) { return res.status(400).send({ - error: error.message ?? '1000: Invalid response schema', + error: error.message ?? 'E1000: Invalid response schema', statusCode: 400, - code: API_ERRORS[1000], + code: 1000, issues: error.validation, }); } + if (isResponseSerializationError(error)) { + console.log(error); + + return res.status(500).send({ + error: 'E1000: Response serialization error', + statusCode: 500, + code: 1000, + details: error.message, + }); + } + if (error instanceof ApiError) { const apiError = error as ApiError; return res.status(apiError.status).send(apiError.toJSON()); @@ -259,7 +271,11 @@ async function main() { } else { console.error(error); - return res.status(500).send({ error: 'Internal Server Error', statusCode: 500 }); + return res.status(500).send({ + code: 9000, + error: 'E9000: Internal Server Error', + statusCode: 500, + }); } }); diff --git a/src/server/routes/api/server/export.ts b/src/server/routes/api/server/export.ts index 0e0bd9c4..29a4057e 100644 --- a/src/server/routes/api/server/export.ts +++ b/src/server/routes/api/server/export.ts @@ -1,10 +1,11 @@ import { ApiError } from '@/lib/api/errors'; -import { Export4, export4Schema } from '@/lib/import/version4/validateExport'; +import { Export4 } from '@/lib/import/version4/validateExport'; import { log } from '@/lib/logger'; import { administratorMiddleware } from '@/server/middleware/administrator'; import { userMiddleware } from '@/server/middleware/user'; import { prisma } from '@/lib/db'; +import { zQsBoolean } from '@/lib/validation'; import typedPlugin from '@/server/typedPlugin'; import { cpus, hostname, platform, release } from 'os'; import z from 'zod'; @@ -56,20 +57,23 @@ export default typedPlugin( description: 'Export Zipline server data as a version 4 export bundle or return aggregate counts of core resources.', querystring: z.object({ - nometrics: z.string().optional(), - counts: z.string().optional(), + nometrics: zQsBoolean.optional(), + counts: zQsBoolean.optional(), }), response: { 200: z.union([ - exportCountsSchema.describe('if ?counts=true'), - export4Schema.describe('if ?counts is not true or not there'), + // TODO: fix later + // exportCountsSchema.describe('if ?counts=true'), + // export4Schema.describe('if ?counts is not true or not there'), + z.any().describe('if ?counts=true'), + z.any().describe('if ?counts is not true or not there'), ]), }, }, preHandler: [userMiddleware, administratorMiddleware], }, async (req, res) => { - if (req.query.counts === 'true') { + if (req.query.counts) { const counts = await getCounts(); return res.send(counts); @@ -162,7 +166,7 @@ export default typedPlugin( id: passkey.id, lastUsed: passkey.lastUsed ? passkey.lastUsed.toISOString() : null, name: passkey.name, - reg: passkey.reg as Record, + reg: passkey.reg as Record, userId: passkey.userId, }); } @@ -294,9 +298,9 @@ export default typedPlugin( } return res - .header('Content-Disposition', `attachment; filename*=utf-8''zipline4_export_${Date.now()}.json`) + .header('Content-Disposition', `attachment; filename='zipline4_export_${Date.now()}.json'`) .type('application/json') - .send(export4); + .send(export4 satisfies Export4); }, ); }, diff --git a/src/server/routes/api/upload/index.ts b/src/server/routes/api/upload/index.ts index ae6a0dff..b77e60fa 100644 --- a/src/server/routes/api/upload/index.ts +++ b/src/server/routes/api/upload/index.ts @@ -1,7 +1,7 @@ import { ApiError } from '@/lib/api/errors'; import { checkQuota, getDomain, getExtension, getFilename, getMimetype } from '@/lib/api/upload'; import { bytes } from '@/lib/bytes'; -import { COMPRESS_TYPES, compressFile, CompressResult } from '@/lib/compress'; +import { compressFile, CompressResult } from '@/lib/compress'; import { config } from '@/lib/config'; import { hashPassword } from '@/lib/crypto'; import { datasource } from '@/lib/datasource'; @@ -54,29 +54,37 @@ export default typedPlugin( consumes: ['multipart/form-data'], response: { 200: z.union([ - z.string().describe('if the noJson option is true, returns a comma-separated list of URLs'), - z.object({ - files: z.array( - z.object({ - id: z.string(), - name: z.string(), - type: z.string(), - url: z.string(), - pending: z.boolean().optional(), - removedGps: z.boolean().optional(), - compressed: z - .object({ - mimetype: z.string(), - ext: z.enum(COMPRESS_TYPES), - failed: z.boolean().optional(), - }) - .optional(), - }), + z.any().describe('if the noJson option is true, returns a comma-separated list of URLs'), + z + .any() + .describe( + 'if the noJson option is not true or not there, returns a JSON object with file details', ), - deletesAt: z.string().optional(), - assumedMimetypes: z.array(z.boolean()).optional(), - }), ]), + // 200: z.union([ + // z.string().describe('if the noJson option is true, returns a comma-separated list of URLs'), + // z.object({ + // files: z.array( + // z.object({ + // id: z.string(), + // name: z.string(), + // type: z.string(), + // url: z.string(), + // pending: z.boolean().optional(), + // removedGps: z.boolean().optional(), + // compressed: z + // .object({ + // mimetype: z.string(), + // ext: z.enum(COMPRESS_TYPES), + // failed: z.boolean().optional(), + // }) + // .optional(), + // }), + // ), + // deletesAt: z.string().optional(), + // assumedMimetypes: z.array(z.boolean()).optional(), + // }), + // ]), }, }, }, diff --git a/src/server/routes/api/user/folders/[id]/index.ts b/src/server/routes/api/user/folders/[id]/index.ts index 87c7bf26..278d5481 100644 --- a/src/server/routes/api/user/folders/[id]/index.ts +++ b/src/server/routes/api/user/folders/[id]/index.ts @@ -270,15 +270,19 @@ export default typedPlugin( params: paramsSchema, response: { 200: z.union([ - folderSchema - .partial() - .describe('if deleting a file from the folder, returns the updated folder'), - z - .object({ - success: z.boolean(), - }) - .describe('if deleting the folder, returns success status'), + z.any().describe('if deleting a file from the folder, returns the updated folder'), + z.any().describe('if deleting the folder, return success status'), ]), + // 200: z.union([ + // folderSchema + // .partial() + // .describe('if deleting a file from the folder, returns the updated folder'), + // z + // .object({ + // success: z.boolean(), + // }) + // .describe('if deleting the folder, return success status'), + // ]), }, }, preHandler: [userMiddleware, folderExistsAndEditable], diff --git a/src/server/routes/api/user/urls/index.ts b/src/server/routes/api/user/urls/index.ts index 4c2e8597..6d4ceebb 100644 --- a/src/server/routes/api/user/urls/index.ts +++ b/src/server/routes/api/user/urls/index.ts @@ -47,11 +47,21 @@ export default typedPlugin( 'x-zipline-password': z.string().optional(), }), response: { + // 200: z.union([ + // z.string(), + // urlSchema.omit({ password: true }).extend({ + // url: z.string(), + // }), + // ]), 200: z.union([ - z.string(), - urlSchema.omit({ password: true }).extend({ - url: z.string(), - }), + z + .any() + .describe('if the x-zipline-no-json header is true, returns the shortened URL as plain text'), + z + .any() + .describe( + 'if the x-zipline-no-json header is not true or not there, returns a JSON object with URL details', + ), ]), }, },