From fe4d55fd425a05a6c995af4469f4639e334d9e6e Mon Sep 17 00:00:00 2001 From: Samir Khanal Date: Mon, 16 Mar 2026 14:34:44 +0545 Subject: [PATCH 1/3] feat: add s3 packages --- apps/api/.env.example | 6 + apps/api/package.json | 2 + pnpm-lock.yaml | 1138 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1146 insertions(+) diff --git a/apps/api/.env.example b/apps/api/.env.example index e9e8b1b..5fc2a1c 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -21,3 +21,9 @@ FRONTEND_URL="http://localhost:3000" # Development: redis://:yourpassword@localhost:6379 # Upstash: rediss://default:yourtoken@your-endpoint.upstash.io:6379 REDIS_URL=redis://:yourpassword@localhost:6379 + +# AWS S3 Configuration +AWS_S3_BUCKET="your-s3-bucket-name" +AWS_S3_REGION="ap-south-1" +AWS_ACCESS_KEY_ID="your-aws-access-key-id" +AWS_SECRET_ACCESS_KEY="your-aws-secret-access-key" diff --git a/apps/api/package.json b/apps/api/package.json index 938767a..aa03cd0 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -29,6 +29,8 @@ "check-types": "tsc --noEmit" }, "dependencies": { + "@aws-sdk/client-s3": "^3.1009.0", + "@aws-sdk/s3-request-presigner": "^3.1009.0", "@nestjs/bullmq": "^11.0.4", "@nestjs/common": "^11.0.1", "@nestjs/config": "^4.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad9b513..0e6af97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,12 @@ importers: apps/api: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.1009.0 + version: 3.1009.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.1009.0 + version: 3.1009.0 '@nestjs/bullmq': specifier: ^11.0.4 version: 11.0.4(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.69.3) @@ -414,6 +420,16 @@ packages: resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -427,6 +443,10 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + '@aws-sdk/client-s3@3.1009.0': + resolution: {integrity: sha512-luy8CxallkoiGWTqU86ca/BbvkWJjs0oala7uIIRN1JtQxMb5i4Yl/PBZVcQFhbK9kQi0PK0GfD8gIpLkI91fw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-sesv2@3.946.0': resolution: {integrity: sha512-JYj3BPqgyRXgBjZ3Xvo4Abd+vLxcsHe4gb0TvwiSM/k7e6MRgBZoYwDOnwbNDs/62X1sn7MPHqqB3miuO4nR5g==} engines: {node: '>=18.0.0'} @@ -439,70 +459,170 @@ packages: resolution: {integrity: sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==} engines: {node: '>=18.0.0'} + '@aws-sdk/core@3.973.20': + resolution: {integrity: sha512-i3GuX+lowD892F3IuJf8o6AbyDupMTdyTxQrCJGcn71ni5hTZ82L4nQhcdumxZ7XPJRJJVHS/CR3uYOIIs0PVA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.5': + resolution: {integrity: sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.946.0': resolution: {integrity: sha512-P4l+K6wX1tf8LmWUvZofdQ+BgCNyk6Tb9u1H10npvqpuCD+dCM4pXIBq3PQcv/juUBOvLGGREo+Govuh3lfD0Q==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-env@3.972.18': + resolution: {integrity: sha512-X0B8AlQY507i5DwjLByeU2Af4ARsl9Vr84koDcXCbAkplmU+1xBFWxEPrWRAoh56waBne/yJqEloSwvRf4x6XA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.946.0': resolution: {integrity: sha512-/zeOJ6E7dGZQ/l2k7KytEoPJX0APIhwt0A79hPf/bUpMF4dDs2P6JmchDrotk0a0Y/MIdNF8sBQ/MEOPnBiYoQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-http@3.972.20': + resolution: {integrity: sha512-ey9Lelj001+oOfrbKmS6R2CJAiXX7QKY4Vj9VJv6L2eE6/VjD8DocHIoYqztTm70xDLR4E1jYPTKfIui+eRNDA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.946.0': resolution: {integrity: sha512-Pdgcra3RivWj/TuZmfFaHbqsvvgnSKO0CxlRUMMr0PgBiCnUhyl+zBktdNOeGsOPH2fUzQpYhcUjYUgVSdcSDQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.972.20': + resolution: {integrity: sha512-5flXSnKHMloObNF+9N0cupKegnH1Z37cdVlpETVgx8/rAhCe+VNlkcZH3HDg2SDn9bI765S+rhNPXGDJJPfbtA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.946.0': resolution: {integrity: sha512-5iqLNc15u2Zx+7jOdQkIbP62N7n2031tw5hkmIG0DLnozhnk64osOh2CliiOE9x3c4P9Pf4frAwgyy9GzNTk2g==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-login@3.972.20': + resolution: {integrity: sha512-gEWo54nfqp2jABMu6HNsjVC4hDLpg9HC8IKSJnp0kqWtxIJYHTmiLSsIfI4ScQjxEwpB+jOOH8dOLax1+hy/Hw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.946.0': resolution: {integrity: sha512-I7URUqnBPng1a5y81OImxrwERysZqMBREG6svhhGeZgxmqcpAZ8z5ywILeQXdEOCuuES8phUp/ojzxFjPXp/eA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.972.21': + resolution: {integrity: sha512-hah8if3/B/Q+LBYN5FukyQ1Mym6PLPDsBOBsIgNEYD6wLyZg0UmUF/OKIVC3nX9XH8TfTPuITK+7N/jenVACWA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.946.0': resolution: {integrity: sha512-GtGHX7OGqIeVQ3DlVm5RRF43Qmf3S1+PLJv9svrdvAhAdy2bUb044FdXXqrtSsIfpzTKlHgQUiRo5MWLd35Ntw==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.972.18': + resolution: {integrity: sha512-Tpl7SRaPoOLT32jbTWchPsn52hYYgJ0kpiFgnwk8pxTANQdUymVSZkzFvv1+oOgZm1CrbQUP9MBeoMZ9IzLZjA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.946.0': resolution: {integrity: sha512-LeGSSt2V5iwYey1ENGY75RmoDP3bA2iE/py8QBKW8EDA8hn74XBLkprhrK5iccOvU3UGWY8WrEKFAFGNjJOL9g==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.972.20': + resolution: {integrity: sha512-p+R+PYR5Z7Gjqf/6pvbCnzEHcqPCpLzR7Yf127HjJ6EAb4hUcD+qsNRnuww1sB/RmSeCLxyay8FMyqREw4p1RA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.946.0': resolution: {integrity: sha512-ocBCvjWfkbjxElBI1QUxOnHldsNhoU0uOICFvuRDAZAoxvypJHN3m5BJkqb7gqorBbcv3LRgmBdEnWXOAvq+7Q==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.20': + resolution: {integrity: sha512-rWCmh8o7QY4CsUj63qopzMzkDq/yPpkrpb+CnjBEFSOg/02T/we7sSTVg4QsDiVS9uwZ8VyONhq98qt+pIh3KA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + resolution: {integrity: sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.8': + resolution: {integrity: sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.973.6': + resolution: {integrity: sha512-0nYEgkJH7Yt9k+nZJyllTghnkKaz17TWFcr5Mi0XMVMzYlF4ytDZADQpF2/iJo36cKL5AYSzRsvlykE4M/ErTA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-host-header@3.936.0': resolution: {integrity: sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.8': + resolution: {integrity: sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-logger@3.936.0': resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-recursion-detection@3.936.0': resolution: {integrity: sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.972.8': + resolution: {integrity: sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-sdk-s3@3.946.0': resolution: {integrity: sha512-0UTFmFd8PX2k/jLu/DBmR+mmLQWAtUGHYps9Rjx3dcXNwaMLaa/39NoV3qn7Dwzfpqc6JZlZzBk+NDOCJIHW9g==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-sdk-s3@3.972.20': + resolution: {integrity: sha512-yhva/xL5H4tWQgsBjwV+RRD0ByCzg0TcByDCLp3GXdn/wlyRNfy8zsswDtCvr1WSKQkSQYlyEzPuWkJG0f5HvQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.8': + resolution: {integrity: sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-user-agent@3.946.0': resolution: {integrity: sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.972.21': + resolution: {integrity: sha512-62XRl1GDYPpkt7cx1AX1SPy9wgNE9Iw/NPuurJu4lmhCWS7sGKO+kS53TQ8eRmIxy3skmvNInnk0ZbWrU5Dpyg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/nested-clients@3.946.0': resolution: {integrity: sha512-rjAtEguukeW8mlyEQMQI56vxFoyWlaNwowmz1p1rav948SUjtrzjHAp4TOQWhibb7AR7BUTHBCgIcyCRjBEf4g==} engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.996.10': + resolution: {integrity: sha512-SlDol5Z+C7Ivnc2rKGqiqfSUmUZzY1qHfVs9myt/nxVwswgfpjdKahyTzLTx802Zfq0NFRs7AejwKzzzl5Co2w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/region-config-resolver@3.936.0': resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} engines: {node: '>=18.0.0'} + '@aws-sdk/region-config-resolver@3.972.8': + resolution: {integrity: sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1009.0': + resolution: {integrity: sha512-iLHNt/R35ZWnmP/oxYbcA6NAsOXHn2kUHAii48aPR9A/0+eMXajBaeIQ37CsQPZPTzqGhy0pqBG8CnpcRXP6rg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.946.0': resolution: {integrity: sha512-61FZ685lKiJuQ06g6U7K3PL9EwKCxNm51wNlxyKV57nnl1GrLD0NC8O3/hDNkCQLNBArT9y3IXl2H7TtIxP8Jg==} engines: {node: '>=18.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.8': + resolution: {integrity: sha512-n1qYFD+tbqZuyskVaxUE+t10AUz9g3qzDw3Tp6QZDKmqsjfDmZBd4GIk2EKJJNtcCBtE5YiUjDYA+3djFAFBBg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1009.0': + resolution: {integrity: sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.946.0': resolution: {integrity: sha512-a5c+rM6CUPX2ExmUZ3DlbLlS5rQr4tbdoGcgBsjnAHiYx8MuMNAI+8M7wfjF13i2yvUQj5WEIddvLpayfEZj9g==} engines: {node: '>=18.0.0'} @@ -511,14 +631,30 @@ packages: resolution: {integrity: sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==} engines: {node: '>=18.0.0'} + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-arn-parser@3.893.0': resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-endpoints@3.936.0': resolution: {integrity: sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.8': + resolution: {integrity: sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.893.0': resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} engines: {node: '>=18.0.0'} @@ -526,6 +662,9 @@ packages: '@aws-sdk/util-user-agent-browser@3.936.0': resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + '@aws-sdk/util-user-agent-node@3.946.0': resolution: {integrity: sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q==} engines: {node: '>=18.0.0'} @@ -535,10 +674,23 @@ packages: aws-crt: optional: true + '@aws-sdk/util-user-agent-node@3.973.7': + resolution: {integrity: sha512-Hz6EZMUAEzqUd7e+vZ9LE7mn+5gMbxltXy18v+YSFY+9LBJz15wkNZvw5JqfX3z0FS9n3bgUtz3L5rAsfh4YlA==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + '@aws-sdk/xml-builder@3.930.0': resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} engines: {node: '>=18.0.0'} + '@aws-sdk/xml-builder@3.972.11': + resolution: {integrity: sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.2.2': resolution: {integrity: sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==} engines: {node: '>=18.0.0'} @@ -2514,10 +2666,26 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@smithy/abort-controller@4.2.12': + resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} + engines: {node: '>=18.0.0'} + '@smithy/abort-controller@4.2.5': resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} engines: {node: '>=18.0.0'} + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.11': + resolution: {integrity: sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw==} + engines: {node: '>=18.0.0'} + '@smithy/config-resolver@4.4.3': resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} engines: {node: '>=18.0.0'} @@ -2526,18 +2694,66 @@ packages: resolution: {integrity: sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==} engines: {node: '>=18.0.0'} + '@smithy/core@3.23.11': + resolution: {integrity: sha512-952rGf7hBRnhUIaeLp6q4MptKW8sPFe5VvkoZ5qIzFAtx6c/QZ/54FS3yootsyUSf9gJX/NBqEBNdNR7jMIlpQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.2.5': resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.2.12': + resolution: {integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.12': + resolution: {integrity: sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.12': + resolution: {integrity: sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.12': + resolution: {integrity: sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.12': + resolution: {integrity: sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.6': resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} engines: {node: '>=18.0.0'} + '@smithy/hash-blob-browser@4.2.13': + resolution: {integrity: sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + '@smithy/hash-node@4.2.5': resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} engines: {node: '>=18.0.0'} + '@smithy/hash-stream-node@4.2.12': + resolution: {integrity: sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + '@smithy/invalid-dependency@4.2.5': resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} engines: {node: '>=18.0.0'} @@ -2550,6 +2766,18 @@ packages: resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.12': + resolution: {integrity: sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-content-length@4.2.5': resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} engines: {node: '>=18.0.0'} @@ -2558,42 +2786,86 @@ packages: resolution: {integrity: sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==} engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.4.25': + resolution: {integrity: sha512-dqjLwZs2eBxIUG6Qtw8/YZ4DvzHGIf0DA18wrgtfP6a50UIO7e2nY0FPdcbv5tVJKqWCCU5BmGMOUwT7Puan+A==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.4.14': resolution: {integrity: sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==} engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.4.42': + resolution: {integrity: sha512-vbwyqHRIpIZutNXZpLAozakzamcINaRCpEy1MYmK6xBeW3xN+TyPRA123GjXnuxZIjc9848MRRCugVMTXxC4Eg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.14': + resolution: {integrity: sha512-+CcaLoLa5apzSRtloOyG7lQvkUw2ZDml3hRh4QiG9WyEPfW5Ke/3tPOPiPjUneuT59Tpn8+c3RVaUvvkkwqZwg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.2.6': resolution: {integrity: sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.5': resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.5': resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.16': + resolution: {integrity: sha512-ULC8UCS/HivdCB3jhi+kLFYe4B5gxH2gi9vHBfEIiRrT2jfKiZNiETJSlzRtE6B26XbBHjPtc8iZKSNqMol9bw==} + engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.4.5': resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.5': resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.5': resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.2.5': resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.5': resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.2.5': resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} engines: {node: '>=18.0.0'} @@ -2602,18 +2874,38 @@ packages: resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.3.5': resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.12.5': + resolution: {integrity: sha512-UqwYawyqSr/aog8mnLnfbPurS0gi4G7IYDcD28cUIBhsvWs1+rQcL2IwkUQ+QZ7dibaoRzhNF99fAQ9AUcO00w==} + engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.9.10': resolution: {integrity: sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==} engines: {node: '>=18.0.0'} + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.9.0': resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.5': resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} engines: {node: '>=18.0.0'} @@ -2622,14 +2914,26 @@ packages: resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@4.2.0': resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@4.2.1': resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -2638,34 +2942,70 @@ packages: resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@4.2.0': resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@4.3.13': resolution: {integrity: sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@4.3.41': + resolution: {integrity: sha512-M1w1Ux0rSVvBOxIIiqbxvZvhnjQ+VUjJrugtORE90BbadSTH+jsQL279KRL3Hv0w69rE7EuYkV/4Lepz/NBW9g==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@4.2.16': resolution: {integrity: sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@4.2.44': + resolution: {integrity: sha512-YPze3/lD1KmWuZsl9JlfhcgGLX7AXhSoaCDtiPntUjNW5/YY0lOHjkcgxyE9x/h5vvS1fzDifMGjzqnNlNiqOQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@3.2.5': resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@4.2.0': resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.2.5': resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.2.12': + resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.2.5': resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.19': + resolution: {integrity: sha512-v4sa+3xTweL1CLO2UP0p7tvIMH/Rq1X4KKOxd568mpe6LSLMQCnDHs4uv7m3ukpl3HvcN2JH6jiCS0SNRXKP/w==} + engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.6': resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} engines: {node: '>=18.0.0'} @@ -2674,6 +3014,10 @@ packages: resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} @@ -2682,10 +3026,22 @@ packages: resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} engines: {node: '>=18.0.0'} + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.13': + resolution: {integrity: sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==} + engines: {node: '>=18.0.0'} + '@smithy/uuid@1.1.0': resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -4060,10 +4416,17 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-builder@1.1.3: + resolution: {integrity: sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==} + fast-xml-parser@5.2.5: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true + fast-xml-parser@5.4.1: + resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==} + hasBin: true + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -5311,6 +5674,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.1.3: + resolution: {integrity: sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -5868,6 +6235,9 @@ packages: strnum@2.1.1: resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + strnum@2.2.0: + resolution: {integrity: sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==} + strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} @@ -6475,6 +6845,27 @@ snapshots: transitivePeerDependencies: - chokidar + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -6501,6 +6892,66 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + '@aws-sdk/client-s3@3.1009.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.20 + '@aws-sdk/credential-provider-node': 3.972.21 + '@aws-sdk/middleware-bucket-endpoint': 3.972.8 + '@aws-sdk/middleware-expect-continue': 3.972.8 + '@aws-sdk/middleware-flexible-checksums': 3.973.6 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-location-constraint': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-sdk-s3': 3.972.20 + '@aws-sdk/middleware-ssec': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.21 + '@aws-sdk/region-config-resolver': 3.972.8 + '@aws-sdk/signature-v4-multi-region': 3.996.8 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.7 + '@smithy/config-resolver': 4.4.11 + '@smithy/core': 3.23.11 + '@smithy/eventstream-serde-browser': 4.2.12 + '@smithy/eventstream-serde-config-resolver': 4.3.12 + '@smithy/eventstream-serde-node': 4.2.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-blob-browser': 4.2.13 + '@smithy/hash-node': 4.2.12 + '@smithy/hash-stream-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/md5-js': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.25 + '@smithy/middleware-retry': 4.4.42 + '@smithy/middleware-serde': 4.2.14 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.4.16 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.41 + '@smithy/util-defaults-mode-node': 4.2.44 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-stream': 4.5.19 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.13 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-sesv2@3.946.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -6605,6 +7056,27 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@aws-sdk/core@3.973.20': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.11 + '@smithy/core': 3.23.11 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.5': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6613,6 +7085,14 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.18': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6626,6 +7106,19 @@ snapshots: '@smithy/util-stream': 4.5.6 tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/types': 3.973.6 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.4.16 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.19 + tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6645,6 +7138,25 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/credential-provider-env': 3.972.18 + '@aws-sdk/credential-provider-http': 3.972.20 + '@aws-sdk/credential-provider-login': 3.972.20 + '@aws-sdk/credential-provider-process': 3.972.18 + '@aws-sdk/credential-provider-sso': 3.972.20 + '@aws-sdk/credential-provider-web-identity': 3.972.20 + '@aws-sdk/nested-clients': 3.996.10 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-login@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6658,6 +7170,19 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-login@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/nested-clients': 3.996.10 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-node@3.946.0': dependencies: '@aws-sdk/credential-provider-env': 3.946.0 @@ -6675,6 +7200,23 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.972.21': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.18 + '@aws-sdk/credential-provider-http': 3.972.20 + '@aws-sdk/credential-provider-ini': 3.972.20 + '@aws-sdk/credential-provider-process': 3.972.18 + '@aws-sdk/credential-provider-sso': 3.972.20 + '@aws-sdk/credential-provider-web-identity': 3.972.20 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-process@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6684,6 +7226,15 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.18': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-sso@3.946.0': dependencies: '@aws-sdk/client-sso': 3.946.0 @@ -6697,6 +7248,19 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-sso@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/nested-clients': 3.996.10 + '@aws-sdk/token-providers': 3.1009.0 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-web-identity@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6709,6 +7273,52 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/nested-clients': 3.996.10 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.973.6': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.20 + '@aws-sdk/crc64-nvme': 3.972.5 + '@aws-sdk/types': 3.973.6 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.19 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 @@ -6716,12 +7326,31 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 @@ -6730,6 +7359,14 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6747,6 +7384,29 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.972.20': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.11 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.19 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6757,6 +7417,17 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.972.21': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.11 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.12 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.946.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -6800,6 +7471,49 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/nested-clients@3.996.10': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.20 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.21 + '@aws-sdk/region-config-resolver': 3.972.8 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.7 + '@smithy/config-resolver': 4.4.11 + '@smithy/core': 3.23.11 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.25 + '@smithy/middleware-retry': 4.4.42 + '@smithy/middleware-serde': 4.2.14 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.4.16 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.41 + '@smithy/util-defaults-mode-node': 4.2.44 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/region-config-resolver@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 @@ -6808,6 +7522,25 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/region-config-resolver@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.11 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1009.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.8 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-format-url': 3.972.8 + '@smithy/middleware-endpoint': 4.4.25 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.946.0': dependencies: '@aws-sdk/middleware-sdk-s3': 3.946.0 @@ -6817,6 +7550,27 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.8': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.20 + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1009.0': + dependencies: + '@aws-sdk/core': 3.973.20 + '@aws-sdk/nested-clients': 3.996.10 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/token-providers@3.946.0': dependencies: '@aws-sdk/core': 3.946.0 @@ -6834,10 +7588,19 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.936.0': dependencies: '@aws-sdk/types': 3.936.0 @@ -6846,6 +7609,21 @@ snapshots: '@smithy/util-endpoints': 3.2.5 tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.893.0': dependencies: tslib: 2.8.1 @@ -6857,6 +7635,13 @@ snapshots: bowser: 2.13.1 tslib: 2.8.1 + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.13.1 + tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.946.0': dependencies: '@aws-sdk/middleware-user-agent': 3.946.0 @@ -6865,12 +7650,27 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.973.7': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.21 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/xml-builder@3.930.0': dependencies: '@smithy/types': 4.9.0 fast-xml-parser: 5.2.5 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.11': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.4.1 + tslib: 2.8.1 + '@aws/lambda-invoke-store@0.2.2': {} '@babel/code-frame@7.27.1': @@ -8875,11 +9675,34 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/abort-controller@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.11': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + '@smithy/config-resolver@4.4.3': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -8902,6 +9725,27 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 + '@smithy/core@3.23.11': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.19 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.5': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -8910,6 +9754,44 @@ snapshots: '@smithy/url-parser': 4.2.5 tslib: 2.8.1 + '@smithy/eventstream-codec@4.2.12': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.12': + dependencies: + '@smithy/eventstream-codec': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.6': dependencies: '@smithy/protocol-http': 5.3.5 @@ -8918,6 +9800,20 @@ snapshots: '@smithy/util-base64': 4.3.0 tslib: 2.8.1 + '@smithy/hash-blob-browser@4.2.13': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/hash-node@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -8925,6 +9821,17 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/hash-stream-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/invalid-dependency@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -8938,6 +9845,22 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-content-length@4.2.5': dependencies: '@smithy/protocol-http': 5.3.5 @@ -8955,6 +9878,17 @@ snapshots: '@smithy/util-middleware': 4.2.5 tslib: 2.8.1 + '@smithy/middleware-endpoint@4.4.25': + dependencies: + '@smithy/core': 3.23.11 + '@smithy/middleware-serde': 4.2.14 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + '@smithy/middleware-retry@4.4.14': dependencies: '@smithy/node-config-provider': 4.3.5 @@ -8967,17 +9901,48 @@ snapshots: '@smithy/uuid': 1.1.0 tslib: 2.8.1 + '@smithy/middleware-retry@4.4.42': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.14': + dependencies: + '@smithy/core': 3.23.11 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-serde@4.2.6': dependencies: '@smithy/protocol-http': 5.3.5 '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/middleware-stack@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/node-config-provider@4.3.5': dependencies: '@smithy/property-provider': 4.2.5 @@ -8985,6 +9950,14 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/node-http-handler@4.4.16': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/node-http-handler@4.4.5': dependencies: '@smithy/abort-controller': 4.2.5 @@ -8993,27 +9966,52 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/property-provider@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/protocol-http@5.3.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + '@smithy/querystring-builder@4.2.5': dependencies: '@smithy/types': 4.9.0 '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/querystring-parser@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/service-error-classification@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -9023,6 +10021,22 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/signature-v4@5.3.5': dependencies: '@smithy/is-array-buffer': 4.2.0 @@ -9034,6 +10048,16 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/smithy-client@4.12.5': + dependencies: + '@smithy/core': 3.23.11 + '@smithy/middleware-endpoint': 4.4.25 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.19 + tslib: 2.8.1 + '@smithy/smithy-client@4.9.10': dependencies: '@smithy/core': 3.18.7 @@ -9044,10 +10068,20 @@ snapshots: '@smithy/util-stream': 4.5.6 tslib: 2.8.1 + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + '@smithy/types@4.9.0': dependencies: tslib: 2.8.1 + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/url-parser@4.2.5': dependencies: '@smithy/querystring-parser': 4.2.5 @@ -9060,14 +10094,28 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-body-length-browser@4.2.0': dependencies: tslib: 2.8.1 + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-body-length-node@4.2.1': dependencies: tslib: 2.8.1 + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 @@ -9078,10 +10126,19 @@ snapshots: '@smithy/is-array-buffer': 4.2.0 tslib: 2.8.1 + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + '@smithy/util-config-provider@4.2.0': dependencies: tslib: 2.8.1 + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@4.3.13': dependencies: '@smithy/property-provider': 4.2.5 @@ -9089,6 +10146,13 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@4.3.41': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-defaults-mode-node@4.2.16': dependencies: '@smithy/config-resolver': 4.4.3 @@ -9099,27 +10163,69 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/util-defaults-mode-node@4.2.44': + dependencies: + '@smithy/config-resolver': 4.4.11 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.5 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-endpoints@3.2.5': dependencies: '@smithy/node-config-provider': 4.3.5 '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-middleware@4.2.5': dependencies: '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/util-retry@4.2.12': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/util-retry@4.2.5': dependencies: '@smithy/service-error-classification': 4.2.5 '@smithy/types': 4.9.0 tslib: 2.8.1 + '@smithy/util-stream@4.5.19': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.4.16 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-stream@4.5.6': dependencies: '@smithy/fetch-http-handler': 5.3.6 @@ -9135,6 +10241,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 @@ -9145,10 +10255,25 @@ snapshots: '@smithy/util-buffer-from': 4.2.0 tslib: 2.8.1 + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.13': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/uuid@1.1.0': dependencies: tslib: 2.8.1 + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.0.0': {} @@ -10757,10 +11882,19 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-builder@1.1.3: + dependencies: + path-expression-matcher: 1.1.3 + fast-xml-parser@5.2.5: dependencies: strnum: 2.1.1 + fast-xml-parser@5.4.1: + dependencies: + fast-xml-builder: 1.1.3 + strnum: 2.2.0 + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -12179,6 +13313,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.1.3: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -12882,6 +14018,8 @@ snapshots: strnum@2.1.1: {} + strnum@2.2.0: {} + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 From 3cb2914032c2214d9a2b0921fef94b6dfb1b1523 Mon Sep 17 00:00:00 2001 From: Samir Khanal Date: Mon, 16 Mar 2026 15:46:20 +0545 Subject: [PATCH 2/3] feat: implement resource management with S3 integration and validation --- .../20260316095939_resources/migration.sql | 32 ++ apps/api/prisma/schema.prisma | 53 +-- apps/api/src/app/app.module.ts | 4 + apps/api/src/common/storage/storage.module.ts | 9 + .../api/src/common/storage/storage.service.ts | 77 ++++ apps/api/src/config/env.config.ts | 4 + .../src/resource/dto/create-resource.dto.ts | 4 + apps/api/src/resource/dto/index.ts | 2 + .../src/resource/dto/update-resource.dto.ts | 4 + apps/api/src/resource/resource.controller.ts | 141 +++++++ apps/api/src/resource/resource.module.ts | 10 + apps/api/src/resource/resource.service.ts | 397 ++++++++++++++++++ packages/schemas/src/index.ts | 1 + .../schemas/src/schemas/resource.schema.ts | 98 +++++ 14 files changed, 812 insertions(+), 24 deletions(-) create mode 100644 apps/api/prisma/migrations/20260316095939_resources/migration.sql create mode 100644 apps/api/src/common/storage/storage.module.ts create mode 100644 apps/api/src/common/storage/storage.service.ts create mode 100644 apps/api/src/resource/dto/create-resource.dto.ts create mode 100644 apps/api/src/resource/dto/index.ts create mode 100644 apps/api/src/resource/dto/update-resource.dto.ts create mode 100644 apps/api/src/resource/resource.controller.ts create mode 100644 apps/api/src/resource/resource.module.ts create mode 100644 apps/api/src/resource/resource.service.ts create mode 100644 packages/schemas/src/schemas/resource.schema.ts diff --git a/apps/api/prisma/migrations/20260316095939_resources/migration.sql b/apps/api/prisma/migrations/20260316095939_resources/migration.sql new file mode 100644 index 0000000..e3c7a20 --- /dev/null +++ b/apps/api/prisma/migrations/20260316095939_resources/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "public"."ResourceType" AS ENUM ('DOCUMENT', 'IMAGE', 'LINK'); + +-- CreateTable +CREATE TABLE "public"."Resource" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "resourceType" "public"."ResourceType" NOT NULL, + "fileUrl" TEXT, + "fileName" TEXT, + "fileSize" INTEGER, + "mimeType" TEXT, + "externalLink" TEXT, + "isUploaded" BOOLEAN NOT NULL DEFAULT false, + "isPublished" BOOLEAN NOT NULL DEFAULT false, + "publishedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "subjectTeacherId" TEXT NOT NULL, + + CONSTRAINT "Resource_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Resource_subjectTeacherId_idx" ON "public"."Resource"("subjectTeacherId"); + +-- CreateIndex +CREATE INDEX "Resource_isPublished_idx" ON "public"."Resource"("isPublished"); + +-- AddForeignKey +ALTER TABLE "public"."Resource" ADD CONSTRAINT "Resource_subjectTeacherId_fkey" FOREIGN KEY ("subjectTeacherId") REFERENCES "public"."SubjectTeacher"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 32da3ad..38da227 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -108,29 +108,34 @@ model SubjectTeacher { teacher User @relation(fields: [teacherId], references: [id], onDelete: Cascade) assignments Assignment[] + resources Resource[] @@unique([subjectId, teacherId]) } -// model Resource { -// id String @id @default(cuid()) -// title String -// description String? -// resourceType ResourceType -// fileUrl String? -// externalLink String? -// isPublished Boolean @default(false) -// publishedAt DateTime? -// createdAt DateTime @default(now()) -// updatedAt DateTime @updatedAt -// subjectTeacherId String - -// // Relations -// subjectTeacher SubjectTeacher @relation(fields: [subjectTeacherId], references: [id], onDelete: Cascade) -// assignments Assignment[] - -// @@index([id]) -// } +model Resource { + id String @id @default(cuid()) + title String + description String? + resourceType ResourceType + fileUrl String? + fileName String? + fileSize Int? + mimeType String? + externalLink String? + isUploaded Boolean @default(false) + isPublished Boolean @default(false) + publishedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + subjectTeacherId String + + // Relations + subjectTeacher SubjectTeacher @relation(fields: [subjectTeacherId], references: [id], onDelete: Cascade) + + @@index([subjectTeacherId]) + @@index([isPublished]) +} model Assignment { id String @id @default(cuid()) @@ -243,11 +248,11 @@ enum SemesterNumber { EIGHTH } -// enum ResourceType { -// DOCUMENT -// LINK -// NOTES -// } +enum ResourceType { + DOCUMENT + IMAGE + LINK +} enum AssignmentStatus { DRAFT diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 0ba106a..14b1bc4 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -19,6 +19,8 @@ import { AnnouncementModule } from 'src/announcement/announcement.module' import { ChatModule } from '@src/chat/chat.module' import { QueueModule } from 'src/common/queue/queue.module' import { DashboardModule } from 'src/dashboard/dashboard.module' +import { StorageModule } from 'src/common/storage/storage.module' +import { ResourceModule } from 'src/resource/resource.module' @Module({ imports: [ @@ -36,6 +38,8 @@ import { DashboardModule } from 'src/dashboard/dashboard.module' AnnouncementModule, ChatModule, DashboardModule, + StorageModule, + ResourceModule, ConfigModule.forRoot({ isGlobal: true, validate: (config) => { diff --git a/apps/api/src/common/storage/storage.module.ts b/apps/api/src/common/storage/storage.module.ts new file mode 100644 index 0000000..380adb5 --- /dev/null +++ b/apps/api/src/common/storage/storage.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common' +import { StorageService } from './storage.service' + +@Global() +@Module({ + providers: [StorageService], + exports: [StorageService], +}) +export class StorageModule {} diff --git a/apps/api/src/common/storage/storage.service.ts b/apps/api/src/common/storage/storage.service.ts new file mode 100644 index 0000000..630185a --- /dev/null +++ b/apps/api/src/common/storage/storage.service.ts @@ -0,0 +1,77 @@ +import { Injectable, Logger } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + DeleteObjectCommand, +} from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import type { Env } from 'src/config/env.config' + +@Injectable() +export class StorageService { + private readonly logger = new Logger(StorageService.name) + private readonly s3Client: S3Client + private readonly bucket: string + + constructor(private readonly configService: ConfigService) { + this.bucket = this.configService.get('AWS_S3_BUCKET', { infer: true })! + const region = this.configService.get('AWS_S3_REGION', { infer: true })! + + this.s3Client = new S3Client({ + region, + credentials: { + accessKeyId: this.configService.get('AWS_ACCESS_KEY_ID', { infer: true })!, + secretAccessKey: this.configService.get('AWS_SECRET_ACCESS_KEY', { infer: true })!, + }, + }) + + this.logger.log(`S3 storage initialized with bucket: ${this.bucket} in region: ${region}`) + } + + //build a file path + getObjectKey(subjectTeacherId: string, resourceId: string, fileName: string): string { + const sanitized = fileName.replace(/[^a-zA-Z0-9.\-_]/g, '_') + return `resources/${subjectTeacherId}/${resourceId}/${sanitized}` + } + + //Creates a temporary URL the client can use to upload a file directly to S3, bypassing your server + async generatePresignedUploadUrl( + key: string, + contentType: string, + expiresIn = 300, // 5 minutes + ): Promise { + const command = new PutObjectCommand({ + Bucket: this.bucket, + Key: key, + ContentType: contentType, + }) + + const url = await getSignedUrl(this.s3Client, command, { expiresIn }) + this.logger.log(`Generated presigned upload URL for key: ${key}`) + return url + } + + // Creates a temporary URL the client can use to download a file directly from S3, bypassing your server + async generatePresignedDownloadUrl(key: string, expiresIn = 3600): Promise { + const command = new GetObjectCommand({ + Bucket: this.bucket, + Key: key, + }) + + const url = await getSignedUrl(this.s3Client, command, { expiresIn }) + this.logger.log(`Generated presigned download URL for key: ${key}`) + return url + } + + async deleteObject(key: string): Promise { + const command = new DeleteObjectCommand({ + Bucket: this.bucket, + Key: key, + }) + + await this.s3Client.send(command) + this.logger.log(`Deleted S3 object: ${key}`) + } +} diff --git a/apps/api/src/config/env.config.ts b/apps/api/src/config/env.config.ts index adf741f..6260240 100644 --- a/apps/api/src/config/env.config.ts +++ b/apps/api/src/config/env.config.ts @@ -19,6 +19,10 @@ export const envSchema = z.object({ COOKIE_DOMAIN: z.string().optional(), NODE_ENV: z.enum(['development', 'production']).default('development'), BE_PORT: z.coerce.number().optional().default(8000), + AWS_S3_BUCKET: z.string().min(1), + AWS_S3_REGION: z.string().min(1), + AWS_ACCESS_KEY_ID: z.string().min(1), + AWS_SECRET_ACCESS_KEY: z.string().min(1), }) export type Env = z.infer diff --git a/apps/api/src/resource/dto/create-resource.dto.ts b/apps/api/src/resource/dto/create-resource.dto.ts new file mode 100644 index 0000000..e4582f0 --- /dev/null +++ b/apps/api/src/resource/dto/create-resource.dto.ts @@ -0,0 +1,4 @@ +import { CreateResourceSchema } from '@repo/schemas' +import { createZodDto } from 'nestjs-zod' + +export class CreateResourceDto extends createZodDto(CreateResourceSchema) {} diff --git a/apps/api/src/resource/dto/index.ts b/apps/api/src/resource/dto/index.ts new file mode 100644 index 0000000..34d9a41 --- /dev/null +++ b/apps/api/src/resource/dto/index.ts @@ -0,0 +1,2 @@ +export * from './create-resource.dto' +export * from './update-resource.dto' diff --git a/apps/api/src/resource/dto/update-resource.dto.ts b/apps/api/src/resource/dto/update-resource.dto.ts new file mode 100644 index 0000000..f116187 --- /dev/null +++ b/apps/api/src/resource/dto/update-resource.dto.ts @@ -0,0 +1,4 @@ +import { UpdateResourceSchema } from '@repo/schemas' +import { createZodDto } from 'nestjs-zod' + +export class UpdateResourceDto extends createZodDto(UpdateResourceSchema) {} diff --git a/apps/api/src/resource/resource.controller.ts b/apps/api/src/resource/resource.controller.ts new file mode 100644 index 0000000..6c8d18c --- /dev/null +++ b/apps/api/src/resource/resource.controller.ts @@ -0,0 +1,141 @@ +import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common' +import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger' +import { Role } from '@prisma/client' +import type { AuthUser } from '@repo/schemas' +import { Roles } from 'src/auth/decorators/roles.decorator' +import { CurrentUser } from 'src/auth/decorators/current-user.decorator' +import { ResourceService } from './resource.service' +import { CreateResourceDto, UpdateResourceDto } from './dto' + +@ApiTags('Resource Management') +@Controller('resources') +export class ResourceController { + constructor(private readonly resourceService: ResourceService) {} + + @Post() + @Roles(Role.TEACHER, Role.ADMIN) + @ApiOperation({ summary: 'Create a new resource (returns presigned upload URL for file types)' }) + @ApiResponse({ status: 201, description: 'Resource created successfully' }) + @ApiResponse({ status: 403, description: 'Teacher not assigned to this subject' }) + async create(@Body() createResourceDto: CreateResourceDto, @CurrentUser() user: AuthUser) { + const result = await this.resourceService.create(createResourceDto, user) + return { + message: 'Resource created successfully', + data: result, + } + } + + @Patch(':id/confirm') + @Roles(Role.TEACHER, Role.ADMIN) + @ApiOperation({ summary: 'Confirm file upload completed for a resource' }) + @ApiParam({ name: 'id', description: 'Resource ID' }) + @ApiResponse({ status: 200, description: 'Upload confirmed' }) + @ApiResponse({ + status: 400, + description: 'Invalid confirmation (LINK type or already confirmed)', + }) + async confirmUpload(@Param('id') id: string, @CurrentUser() user: AuthUser) { + const resource = await this.resourceService.confirmUpload(id, user) + return { + message: 'Upload confirmed successfully', + data: resource, + } + } + + @Get() + @ApiOperation({ summary: 'Get resources based on user role' }) + @ApiResponse({ status: 200, description: 'List of resources' }) + async findAll(@CurrentUser() user: AuthUser) { + const resources = await this.resourceService.findAll(user) + return { + message: 'Resources retrieved successfully', + data: resources, + } + } + + @Get('my-subjects') + @Roles(Role.TEACHER) + @ApiOperation({ summary: 'Get subject-teacher records for the authenticated teacher' }) + @ApiResponse({ status: 200, description: 'List of subject-teacher records' }) + async getTeacherSubjects(@CurrentUser('id') teacherId: string) { + const subjects = await this.resourceService.getTeacherSubjects(teacherId) + return { + message: 'Teacher subjects retrieved successfully', + data: subjects, + } + } + + @Get('all-subject-teachers') + @Roles(Role.ADMIN) + @ApiOperation({ summary: 'Get all subject-teacher records (admin only)' }) + @ApiResponse({ status: 200, description: 'List of all subject-teacher records' }) + async getAllSubjectTeachers() { + const subjects = await this.resourceService.getAllSubjectTeachers() + return { + message: 'All subject-teacher records retrieved successfully', + data: subjects, + } + } + + @Get(':id') + @ApiOperation({ summary: 'Get a single resource by ID' }) + @ApiParam({ name: 'id', description: 'Resource ID' }) + @ApiResponse({ status: 200, description: 'Resource details' }) + @ApiResponse({ status: 404, description: 'Resource not found' }) + @ApiResponse({ status: 403, description: 'Access denied' }) + async findOne(@Param('id') id: string, @CurrentUser() user: AuthUser) { + const resource = await this.resourceService.findOne(id, user) + return { + message: 'Resource retrieved successfully', + data: resource, + } + } + + @Get(':id/download') + @ApiOperation({ summary: 'Get a presigned download URL for a resource file' }) + @ApiParam({ name: 'id', description: 'Resource ID' }) + @ApiResponse({ status: 200, description: 'Presigned download URL' }) + @ApiResponse({ status: 400, description: 'No file to download' }) + @ApiResponse({ status: 404, description: 'Resource not found' }) + async getDownloadUrl(@Param('id') id: string, @CurrentUser() user: AuthUser) { + const result = await this.resourceService.getDownloadUrl(id, user) + return { + message: 'Download URL generated successfully', + data: result, + } + } + + @Patch(':id') + @Roles(Role.TEACHER, Role.ADMIN) + @ApiOperation({ summary: 'Update resource metadata' }) + @ApiParam({ name: 'id', description: 'Resource ID' }) + @ApiResponse({ status: 200, description: 'Resource updated successfully' }) + @ApiResponse({ status: 404, description: 'Resource not found' }) + @ApiResponse({ status: 403, description: 'Access denied' }) + async update( + @Param('id') id: string, + @Body() updateResourceDto: UpdateResourceDto, + @CurrentUser() user: AuthUser, + ) { + const resource = await this.resourceService.update(id, updateResourceDto, user) + return { + message: 'Resource updated successfully', + data: resource, + } + } + + @Delete(':id') + @Roles(Role.TEACHER, Role.ADMIN) + @ApiOperation({ summary: 'Delete a resource and its S3 file' }) + @ApiParam({ name: 'id', description: 'Resource ID' }) + @ApiResponse({ status: 200, description: 'Resource deleted successfully' }) + @ApiResponse({ status: 404, description: 'Resource not found' }) + @ApiResponse({ status: 403, description: 'Access denied' }) + async remove(@Param('id') id: string, @CurrentUser() user: AuthUser) { + const result = await this.resourceService.remove(id, user) + return { + message: 'Resource deleted successfully', + data: result, + } + } +} diff --git a/apps/api/src/resource/resource.module.ts b/apps/api/src/resource/resource.module.ts new file mode 100644 index 0000000..8dcdc4d --- /dev/null +++ b/apps/api/src/resource/resource.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common' +import { ResourceController } from './resource.controller' +import { ResourceService } from './resource.service' + +@Module({ + controllers: [ResourceController], + providers: [ResourceService], + exports: [ResourceService], +}) +export class ResourceModule {} diff --git a/apps/api/src/resource/resource.service.ts b/apps/api/src/resource/resource.service.ts new file mode 100644 index 0000000..602d008 --- /dev/null +++ b/apps/api/src/resource/resource.service.ts @@ -0,0 +1,397 @@ +import { + Injectable, + Logger, + NotFoundException, + ForbiddenException, + BadRequestException, +} from '@nestjs/common' +import { PrismaService } from 'src/prisma/prisma.service' +import { StorageService } from 'src/common/storage/storage.service' +import { CreateResourceDto, UpdateResourceDto } from './dto' +import { ResourceType, Role, Prisma } from '@prisma/client' +import { ResourceTypeEnum, type AuthUser } from '@repo/schemas' + +@Injectable() +export class ResourceService { + private readonly logger = new Logger(ResourceService.name) + + constructor( + private readonly prisma: PrismaService, + private readonly storage: StorageService, + ) {} + + private readonly RESOURCE_INCLUDE = { + subjectTeacher: { + select: { + id: true, + subject: { + select: { + id: true, + subjectCode: true, + subjectName: true, + }, + }, + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + }, + } as const + + private isAdmin(user: AuthUser) { + return user.role === Role.ADMIN + } + + private async findOneWithAccess(client: Prisma.TransactionClient, id: string, user: AuthUser) { + const resource = await client.resource.findUnique({ + where: { id }, + include: this.RESOURCE_INCLUDE, + }) + + if (!resource) { + throw new NotFoundException(`Resource with id ${id} not found`) + } + + if (this.isAdmin(user)) return resource + + if (user.role === Role.STUDENT) { + // Students can only see published resources for subjects in their batch's current semester + if (!resource.isPublished) { + throw new ForbiddenException('You do not have access to this resource') + } + + const student = await client.user.findUnique({ + where: { id: user.id }, + select: { + batchId: true, + batch: { + select: { currentSemesterId: true }, + }, + }, + }) + + if (!student?.batch?.currentSemesterId) { + throw new ForbiddenException('You do not have access to this resource') + } + + // Check if the resource's subject belongs to the student's current semester + const subjectTeacher = await client.subjectTeacher.findUnique({ + where: { id: resource.subjectTeacherId }, + select: { + subject: { + select: { semesterId: true }, + }, + }, + }) + + if (subjectTeacher?.subject.semesterId !== student.batch.currentSemesterId) { + throw new ForbiddenException('You do not have access to this resource') + } + + return resource + } + + // Teacher: can only access their own resources + if (resource.subjectTeacher.teacher.id !== user.id) { + throw new ForbiddenException('You do not have access to this resource') + } + + return resource + } + + async create(createResourceDto: CreateResourceDto, user: AuthUser) { + this.logger.log(`Creating resource: ${createResourceDto.title} by user: ${user.id}`) + + // Verify subject-teacher access + const subjectTeacher = this.isAdmin(user) + ? await this.prisma.subjectTeacher.findUnique({ + where: { id: createResourceDto.subjectTeacherId }, + }) + : await this.prisma.subjectTeacher.findUnique({ + where: { + id: createResourceDto.subjectTeacherId, + teacherId: user.id, + isActive: true, + }, + }) + + if (!subjectTeacher) { + throw this.isAdmin(user) + ? new NotFoundException( + `SubjectTeacher with id ${createResourceDto.subjectTeacherId} not found`, + ) + : new ForbiddenException( + 'You are not assigned to this subject or the assignment is inactive', + ) + } + + const isFileResource = + createResourceDto.resourceType === ResourceTypeEnum.enum.DOCUMENT || + createResourceDto.resourceType === ResourceTypeEnum.enum.IMAGE + + // For file resources, validate that file details are provided + if (isFileResource) { + if ( + !createResourceDto.fileName || + !createResourceDto.fileSize || + !createResourceDto.mimeType + ) { + throw new BadRequestException( + 'fileName, fileSize, and mimeType are required for file resources', + ) + } + } + + // For link resources, validate that externalLink is provided + if (createResourceDto.resourceType === ResourceTypeEnum.enum.LINK) { + if (!createResourceDto.externalLink) { + throw new BadRequestException('externalLink is required for LINK resources') + } + } + + // Create the resource record + const resource = await this.prisma.resource.create({ + data: { + title: createResourceDto.title, + description: createResourceDto.description ?? null, + resourceType: createResourceDto.resourceType as ResourceType, + subjectTeacherId: createResourceDto.subjectTeacherId, + fileName: createResourceDto.fileName ?? null, + fileSize: createResourceDto.fileSize ?? null, + mimeType: createResourceDto.mimeType ?? null, + externalLink: createResourceDto.externalLink ?? null, + // LINK resources are immediately "uploaded" (no file to upload) + isUploaded: createResourceDto.resourceType === ResourceTypeEnum.enum.LINK, + }, + include: this.RESOURCE_INCLUDE, + }) + + // Generate presigned upload URL for file resources + let uploadUrl: string | null = null + if (isFileResource && createResourceDto.fileName && createResourceDto.mimeType) { + const objectKey = this.storage.getObjectKey( + createResourceDto.subjectTeacherId as string, + resource.id as string, + createResourceDto.fileName as string, + ) + + // Store the S3 object key on the resource + await this.prisma.resource.update({ + where: { id: resource.id }, + data: { fileUrl: objectKey }, + }) + + resource.fileUrl = objectKey + + uploadUrl = await this.storage.generatePresignedUploadUrl( + objectKey, + createResourceDto.mimeType as string, + ) + } + + this.logger.log(`Created resource: ${resource.id}`) + return { resource, uploadUrl } + } + + /** + * Confirm that a file has been successfully uploaded to S3. + */ + async confirmUpload(id: string, user: AuthUser) { + this.logger.log(`Confirming upload for resource: ${id} by user: ${user.id}`) + + return this.prisma.$transaction(async (tx) => { + const resource = await this.findOneWithAccess(tx, id, user) + + if (resource.resourceType === ResourceTypeEnum.enum.LINK) { + throw new BadRequestException('Cannot confirm upload for a LINK resource') + } + + if (resource.isUploaded) { + throw new BadRequestException('Resource is already marked as uploaded') + } + + if (!resource.fileUrl) { + throw new BadRequestException('Resource has no file URL to confirm') + } + + const updated = await tx.resource.update({ + where: { id }, + data: { isUploaded: true }, + include: this.RESOURCE_INCLUDE, + }) + + this.logger.log(`Confirmed upload for resource: ${id}`) + return updated + }) + } + + async findAll(user: AuthUser) { + this.logger.log(`Finding all resources for user: ${user.id} (role: ${user.role})`) + + if (this.isAdmin(user)) { + return this.prisma.resource.findMany({ + where: { isUploaded: true }, + include: this.RESOURCE_INCLUDE, + orderBy: { createdAt: 'desc' }, + }) + } + + if (user.role === Role.STUDENT) { + const student = await this.prisma.user.findUnique({ + where: { id: user.id }, + select: { + batchId: true, + batch: { + select: { currentSemesterId: true }, + }, + }, + }) + + if (!student?.batch?.currentSemesterId) return [] + + return this.prisma.resource.findMany({ + where: { + isPublished: true, + isUploaded: true, + subjectTeacher: { + subject: { + semesterId: student.batch.currentSemesterId, + }, + isActive: true, + }, + }, + include: this.RESOURCE_INCLUDE, + orderBy: { createdAt: 'desc' }, + }) + } + + // Teacher: see their own resources (including unpublished) + return this.prisma.resource.findMany({ + where: { + subjectTeacher: { teacherId: user.id }, + isUploaded: true, + }, + include: this.RESOURCE_INCLUDE, + orderBy: { createdAt: 'desc' }, + }) + } + + async findOne(id: string, user: AuthUser) { + this.logger.log(`Finding resource: ${id} for user: ${user.id}`) + return this.findOneWithAccess(this.prisma, id, user) + } + + async getDownloadUrl(id: string, user: AuthUser) { + this.logger.log(`Getting download URL for resource: ${id} by user: ${user.id}`) + + const resource = await this.findOneWithAccess(this.prisma, id, user) + + if (resource.resourceType === 'LINK') { + return { downloadUrl: resource.externalLink } + } + + if (!resource.fileUrl) { + throw new BadRequestException('Resource has no file to download') + } + + if (!resource.isUploaded) { + throw new BadRequestException('Resource file has not been uploaded yet') + } + + const downloadUrl = await this.storage.generatePresignedDownloadUrl(resource.fileUrl as string) + return { downloadUrl } + } + + async update(id: string, updateResourceDto: UpdateResourceDto, user: AuthUser) { + this.logger.log(`Updating resource: ${id} by user: ${user.id}`) + + return this.prisma.$transaction(async (tx) => { + await this.findOneWithAccess(tx, id, user) + + const resource = await tx.resource.update({ + where: { id }, + data: { + ...(updateResourceDto.title && { title: updateResourceDto.title }), + ...(updateResourceDto.description !== undefined && { + description: updateResourceDto.description, + }), + ...(updateResourceDto.isPublished !== undefined && { + isPublished: updateResourceDto.isPublished, + publishedAt: updateResourceDto.isPublished ? new Date() : null, + }), + }, + include: this.RESOURCE_INCLUDE, + }) + + this.logger.log(`Updated resource: ${resource.id}`) + return resource + }) + } + + async remove(id: string, user: AuthUser) { + this.logger.log(`Deleting resource: ${id} by user: ${user.id}`) + + return this.prisma.$transaction(async (tx) => { + const resource = await this.findOneWithAccess(tx, id, user) + + // Delete from S3 if there's a file + if (resource.fileUrl && resource.isUploaded) { + await this.storage.deleteObject(resource.fileUrl as string) + } + + await tx.resource.delete({ where: { id } }) + this.logger.log(`Deleted resource: ${id}`) + return { id } + }) + } + + async getTeacherSubjects(teacherId: string) { + this.logger.log(`Getting subject-teacher records for teacher: ${teacherId}`) + + return this.prisma.subjectTeacher.findMany({ + where: { teacherId, isActive: true }, + select: { + id: true, + subject: { + select: { + id: true, + subjectCode: true, + subjectName: true, + }, + }, + }, + orderBy: { subject: { subjectName: 'asc' } }, + }) + } + + /** + * Get all subject-teacher records (for admin form dropdowns). + */ + async getAllSubjectTeachers() { + this.logger.log('Getting all subject-teacher records (admin)') + + return this.prisma.subjectTeacher.findMany({ + where: { isActive: true }, + select: { + id: true, + subject: { + select: { + id: true, + subjectCode: true, + subjectName: true, + }, + }, + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + orderBy: { subject: { subjectName: 'asc' } }, + }) + } +} diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index 9d72d92..bf45b35 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -7,6 +7,7 @@ export * from './schemas/subject.schema' export * from './schemas/admin.schema' export * from './schemas/assignment.schema' export * from './schemas/announcement.schema' +export * from './schemas/resource.schema' export * from './schemas/chat.schema' export * from './schemas/dashboard.schema' export * from './types/api-response' diff --git a/packages/schemas/src/schemas/resource.schema.ts b/packages/schemas/src/schemas/resource.schema.ts new file mode 100644 index 0000000..06e407c --- /dev/null +++ b/packages/schemas/src/schemas/resource.schema.ts @@ -0,0 +1,98 @@ +import { z } from 'zod' + +export const ResourceTypeEnum = z.enum(['DOCUMENT', 'IMAGE', 'LINK']) +export type ResourceType = z.infer + +const ALLOWED_MIME_TYPES = [ + // Documents + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'text/plain', + // Images + 'image/jpeg', + 'image/png', + 'image/webp', +] as const + +export const RESOURCE_ALLOWED_MIME_TYPES = ALLOWED_MIME_TYPES +export const RESOURCE_MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB + +export const CreateResourceSchema = z + .object({ + title: z.string().min(1, 'Title is required').max(200, 'Title must be 200 characters or less'), + description: z.string().max(2000, 'Description must be 2000 characters or less').optional(), + resourceType: ResourceTypeEnum, + subjectTeacherId: z.cuid('Subject teacher is required'), + // File upload fields (required for DOCUMENT and IMAGE types) + fileName: z.string().optional(), + fileSize: z.number().max(RESOURCE_MAX_FILE_SIZE, 'File size must be 10MB or less').optional(), + mimeType: z + .string() + .refine( + (val) => ALLOWED_MIME_TYPES.includes(val as (typeof ALLOWED_MIME_TYPES)[number]), + 'Unsupported file type', + ) + .optional(), + // Link field (required for LINK type) + externalLink: z.url('Invalid URL').optional(), + }) + .refine( + (data) => { + if (data.resourceType === ResourceTypeEnum.enum.LINK) { + return !!data.externalLink + } + return !!data.fileName && !!data.fileSize && !!data.mimeType + }, + { + message: + 'File details (fileName, fileSize, mimeType) are required for DOCUMENT/IMAGE resources. External link is required for LINK resources.', + }, + ) +export type CreateResourceDto = z.infer + +export const UpdateResourceSchema = z.object({ + title: z + .string() + .min(1, 'Title is required') + .max(200, 'Title must be 200 characters or less') + .optional(), + description: z.string().max(2000, 'Description must be 2000 characters or less').optional(), + isPublished: z.boolean().optional(), +}) +export type UpdateResourceDto = z.infer + +export const ResourceResponseSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string().nullable(), + resourceType: ResourceTypeEnum, + fileUrl: z.string().nullable(), + fileName: z.string().nullable(), + fileSize: z.number().nullable(), + mimeType: z.string().nullable(), + externalLink: z.string().nullable(), + isUploaded: z.boolean(), + isPublished: z.boolean(), + publishedAt: z.string().nullable(), + createdAt: z.string(), + updatedAt: z.string(), + subjectTeacherId: z.string(), + subjectTeacher: z.object({ + id: z.string(), + subject: z.object({ + id: z.string(), + subjectCode: z.string(), + subjectName: z.string(), + }), + teacher: z.object({ + id: z.string(), + name: z.string(), + }), + }), +}) +export type ResourceResponse = z.infer From d065b35391f16681f9908f4bbad717d272d8fec6 Mon Sep 17 00:00:00 2001 From: Samir Khanal Date: Mon, 16 Mar 2026 16:29:38 +0545 Subject: [PATCH 3/3] feat: implement resource management feature for teachers, admins, and students - Added resource upload functionality with CreateResourceSheet component. - Created ResourceListTable for displaying resources with actions for editing, deleting, and downloading. - Implemented hooks for resource CRUD operations in useResource. - Added API endpoints for resource management in resource.api.ts. - Developed separate pages for admin, teacher, and student resource views. - Introduced file upload component with validation for resource files. - Enhanced schemas to support resource creation and updates. - Updated navigation to include resources for admin and student dashboards. --- apps/api/src/resource/resource.controller.ts | 6 +- apps/api/src/resource/resource.service.ts | 15 +- apps/web/apis/assignment.api.ts | 24 +- apps/web/apis/resource.api.ts | 85 ++++++ apps/web/app/admin/resources/page.tsx | 41 +++ .../student/courses/[id]/resources/page.tsx | 50 ++++ apps/web/app/student/resources/page.tsx | 28 ++ apps/web/app/teacher/resources/page.tsx | 41 +-- .../assignment/create-assignment-dialog.tsx | 7 +- apps/web/components/dashboard/nav-config.ts | 2 + .../resource/create-resource-sheet.tsx | 272 ++++++++++++++++++ .../resource/edit-resource-sheet.tsx | 115 ++++++++ .../resource/resource-list-table.tsx | 235 +++++++++++++++ apps/web/components/ui/file-upload.tsx | 169 +++++++++++ apps/web/hooks/useResource.ts | 127 ++++++++ apps/web/lib/formatters.ts | 8 + apps/web/lib/query-keys.ts | 4 + .../schemas/src/schemas/resource.schema.ts | 6 + .../schemas/src/schemas/subject.schema.ts | 32 +++ 19 files changed, 1220 insertions(+), 47 deletions(-) create mode 100644 apps/web/apis/resource.api.ts create mode 100644 apps/web/app/admin/resources/page.tsx create mode 100644 apps/web/app/student/courses/[id]/resources/page.tsx create mode 100644 apps/web/app/student/resources/page.tsx create mode 100644 apps/web/components/resource/create-resource-sheet.tsx create mode 100644 apps/web/components/resource/edit-resource-sheet.tsx create mode 100644 apps/web/components/resource/resource-list-table.tsx create mode 100644 apps/web/components/ui/file-upload.tsx create mode 100644 apps/web/hooks/useResource.ts diff --git a/apps/api/src/resource/resource.controller.ts b/apps/api/src/resource/resource.controller.ts index 6c8d18c..913874b 100644 --- a/apps/api/src/resource/resource.controller.ts +++ b/apps/api/src/resource/resource.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common' +import { Controller, Get, Post, Patch, Delete, Body, Param, Query } from '@nestjs/common' import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger' import { Role } from '@prisma/client' import type { AuthUser } from '@repo/schemas' @@ -45,8 +45,8 @@ export class ResourceController { @Get() @ApiOperation({ summary: 'Get resources based on user role' }) @ApiResponse({ status: 200, description: 'List of resources' }) - async findAll(@CurrentUser() user: AuthUser) { - const resources = await this.resourceService.findAll(user) + async findAll(@Query('subjectId') subjectId: string | undefined, @CurrentUser() user: AuthUser) { + const resources = await this.resourceService.findAll(user, subjectId) return { message: 'Resources retrieved successfully', data: resources, diff --git a/apps/api/src/resource/resource.service.ts b/apps/api/src/resource/resource.service.ts index 602d008..26d6abe 100644 --- a/apps/api/src/resource/resource.service.ts +++ b/apps/api/src/resource/resource.service.ts @@ -228,12 +228,17 @@ export class ResourceService { }) } - async findAll(user: AuthUser) { + async findAll(user: AuthUser, subjectId?: string) { this.logger.log(`Finding all resources for user: ${user.id} (role: ${user.role})`) if (this.isAdmin(user)) { return this.prisma.resource.findMany({ - where: { isUploaded: true }, + where: { + isUploaded: true, + ...(subjectId && { + subjectTeacher: { subject: { id: subjectId } }, + }), + }, include: this.RESOURCE_INCLUDE, orderBy: { createdAt: 'desc' }, }) @@ -259,6 +264,7 @@ export class ResourceService { subjectTeacher: { subject: { semesterId: student.batch.currentSemesterId, + ...(subjectId && { id: subjectId }), }, isActive: true, }, @@ -271,7 +277,10 @@ export class ResourceService { // Teacher: see their own resources (including unpublished) return this.prisma.resource.findMany({ where: { - subjectTeacher: { teacherId: user.id }, + subjectTeacher: { + teacherId: user.id, + ...(subjectId && { subject: { id: subjectId } }), + }, isUploaded: true, }, include: this.RESOURCE_INCLUDE, diff --git a/apps/web/apis/assignment.api.ts b/apps/web/apis/assignment.api.ts index c89234c..e8ac87a 100644 --- a/apps/web/apis/assignment.api.ts +++ b/apps/web/apis/assignment.api.ts @@ -5,30 +5,10 @@ import type { CreateAssignmentDto, UpdateAssignmentDto, UpdateAssignmentStatusDto, + TeacherSubjectRecord, + AllSubjectTeacherRecord, } from '@repo/schemas' -export interface TeacherSubjectRecord { - id: string - subject: { - id: string - subjectCode: string - subjectName: string - } -} - -export interface AllSubjectTeacherRecord { - id: string - subject: { - id: string - subjectCode: string - subjectName: string - } - teacher: { - id: string - name: string - } -} - export const assignmentApi = { getAllAssignments: async (): Promise> => { const { data } = await apiClient.get>('/assignments') diff --git a/apps/web/apis/resource.api.ts b/apps/web/apis/resource.api.ts new file mode 100644 index 0000000..934ba80 --- /dev/null +++ b/apps/web/apis/resource.api.ts @@ -0,0 +1,85 @@ +import apiClient from '@/lib/api' +import type { + ApiResponse, + ResourceResponse, + CreateResourceDto, + UpdateResourceDto, + CreateResourceResponse, + TeacherSubjectRecord, + AllSubjectTeacherRecord, +} from '@repo/schemas' + +export const resourceApi = { + getAllResources: async (subjectId?: string): Promise> => { + const params = subjectId ? { subjectId } : undefined + const { data } = await apiClient.get>('/resources', { params }) + return data + }, + + getResource: async (id: string): Promise> => { + const { data } = await apiClient.get>(`/resources/${id}`) + return data + }, + + createResource: async (dto: CreateResourceDto): Promise> => { + const { data } = await apiClient.post>('/resources', dto) + return data + }, + + confirmUpload: async (id: string): Promise> => { + const { data } = await apiClient.patch>( + `/resources/${id}/confirm`, + ) + return data + }, + + updateResource: async ( + id: string, + dto: UpdateResourceDto, + ): Promise> => { + const { data } = await apiClient.patch>(`/resources/${id}`, dto) + return data + }, + + deleteResource: async (id: string): Promise> => { + const { data } = await apiClient.delete>(`/resources/${id}`) + return data + }, + + getDownloadUrl: async (id: string): Promise> => { + const { data } = await apiClient.get>( + `/resources/${id}/download`, + ) + return data + }, + + getTeacherSubjects: async (): Promise> => { + const { data } = + await apiClient.get>('/resources/my-subjects') + return data + }, + + getAllSubjectTeachers: async (): Promise> => { + const { data } = await apiClient.get>( + '/resources/all-subject-teachers', + ) + return data + }, + + /** + * Upload a file directly to S3 using a presigned PUT URL. + * Uses native fetch instead of axios to bypass the API auth interceptor. + */ + uploadFileToS3: async (presignedUrl: string, file: File, contentType: string): Promise => { + const response = await fetch(presignedUrl, { + method: 'PUT', + body: file, + headers: { + 'Content-Type': contentType, + }, + }) + if (!response.ok) { + throw new Error('Failed to upload file to storage') + } + }, +} diff --git a/apps/web/app/admin/resources/page.tsx b/apps/web/app/admin/resources/page.tsx new file mode 100644 index 0000000..d8972ec --- /dev/null +++ b/apps/web/app/admin/resources/page.tsx @@ -0,0 +1,41 @@ +'use client' + +import { useState } from 'react' +import { Plus } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { PageHeader } from '@/components/ui/page-header' +import { ResourceListTable } from '@/components/resource/resource-list-table' +import { CreateResourceSheet } from '@/components/resource/create-resource-sheet' +import { useResources } from '@/hooks/useResource' + +export default function AdminResourcesPage() { + const { data: resources, isLoading } = useResources() + const [isCreateOpen, setIsCreateOpen] = useState(false) + + return ( +
+
+ setIsCreateOpen(true)}> + + Upload Resource + + } + /> + + + + +
+
+ ) +} diff --git a/apps/web/app/student/courses/[id]/resources/page.tsx b/apps/web/app/student/courses/[id]/resources/page.tsx new file mode 100644 index 0000000..35ba011 --- /dev/null +++ b/apps/web/app/student/courses/[id]/resources/page.tsx @@ -0,0 +1,50 @@ +'use client' + +import { useParams } from 'next/navigation' +import { useSubject } from '@/hooks/useSubject' +import { useResources } from '@/hooks/useResource' +import { PageHeader } from '@/components/ui/page-header' +import { LoadingState } from '@/components/ui/loading-state' +import { NotFoundState } from '@/components/ui/not-found-state' +import { ResourceListTable } from '@/components/resource/resource-list-table' + +export default function StudentCourseResourcesPage() { + const params = useParams() + const subjectId = params.id as string + + const { data: subject, isLoading: isSubjectLoading } = useSubject(subjectId) + const { data: resources, isLoading: isResourcesLoading } = useResources(subjectId) + + if (isSubjectLoading) { + return + } + + if (!subject) { + return ( + + ) + } + + return ( +
+
+ + + +
+
+ ) +} diff --git a/apps/web/app/student/resources/page.tsx b/apps/web/app/student/resources/page.tsx new file mode 100644 index 0000000..8a84fc5 --- /dev/null +++ b/apps/web/app/student/resources/page.tsx @@ -0,0 +1,28 @@ +'use client' + +import { PageHeader } from '@/components/ui/page-header' +import { ResourceListTable } from '@/components/resource/resource-list-table' +import { useResources } from '@/hooks/useResource' + +export default function StudentResourcesPage() { + const { data: resources, isLoading } = useResources() + + return ( +
+
+ + + +
+
+ ) +} diff --git a/apps/web/app/teacher/resources/page.tsx b/apps/web/app/teacher/resources/page.tsx index f198388..fc57e88 100644 --- a/apps/web/app/teacher/resources/page.tsx +++ b/apps/web/app/teacher/resources/page.tsx @@ -1,32 +1,39 @@ 'use client' -import { FolderOpen, Construction } from 'lucide-react' -import { Card, CardContent } from '@/components/ui/card' +import { useState } from 'react' +import { Plus } from 'lucide-react' +import { Button } from '@/components/ui/button' import { PageHeader } from '@/components/ui/page-header' +import { ResourceListTable } from '@/components/resource/resource-list-table' +import { CreateResourceSheet } from '@/components/resource/create-resource-sheet' +import { useResources } from '@/hooks/useResource' export default function TeacherResourcesPage() { + const { data: resources, isLoading } = useResources() + const [isCreateOpen, setIsCreateOpen] = useState(false) + return (
setIsCreateOpen(true)}> + + Upload Resource + + } + /> + + - - -
- - -
-

Coming soon

-

- The resources feature is currently under development. You'll be able to upload - and share teaching materials here. -

-
-
+
) diff --git a/apps/web/components/assignment/create-assignment-dialog.tsx b/apps/web/components/assignment/create-assignment-dialog.tsx index d396c6b..cdfa455 100644 --- a/apps/web/components/assignment/create-assignment-dialog.tsx +++ b/apps/web/components/assignment/create-assignment-dialog.tsx @@ -2,7 +2,11 @@ import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' -import { CreateAssignmentSchema, type CreateAssignmentDto } from '@repo/schemas' +import { + CreateAssignmentSchema, + type CreateAssignmentDto, + type TeacherSubjectRecord, +} from '@repo/schemas' import { Dialog, DialogContent, @@ -31,7 +35,6 @@ import { import { Loader2 } from 'lucide-react' import { useCreateAssignment, useTeacherSubjects } from '@/hooks/useAssignment' import { useBatches } from '@/hooks/useBatch' -import type { TeacherSubjectRecord } from '@/apis/assignment.api' interface CreateAssignmentDialogProps { open: boolean diff --git a/apps/web/components/dashboard/nav-config.ts b/apps/web/components/dashboard/nav-config.ts index fdca7cf..9bd9179 100644 --- a/apps/web/components/dashboard/nav-config.ts +++ b/apps/web/components/dashboard/nav-config.ts @@ -36,6 +36,7 @@ export const adminNavGroups: NavGroup[] = [ { title: 'Batches', url: '/admin/batches', icon: GraduationCap }, { title: 'Semesters', url: '/admin/semesters', icon: BookOpen }, { title: 'Assignments', url: '/admin/assignments', icon: ClipboardList }, + { title: 'Resources', url: '/admin/resources', icon: FolderOpen }, { title: 'Announcements', url: '/admin/announcements', icon: Megaphone }, { title: 'Chat', url: '/admin/chat', icon: MessageCircle }, ], @@ -78,6 +79,7 @@ export const studentNavGroups: NavGroup[] = [ items: [ { title: 'Courses', url: '/student/courses', icon: BookOpen }, { title: 'Assignments', url: '/student/assignments', icon: ClipboardList }, + { title: 'Resources', url: '/student/resources', icon: FolderOpen }, { title: 'Announcements', url: '/student/announcements', icon: Megaphone }, { title: 'Chat', url: '/student/chat', icon: MessageCircle }, ], diff --git a/apps/web/components/resource/create-resource-sheet.tsx b/apps/web/components/resource/create-resource-sheet.tsx new file mode 100644 index 0000000..ca60a71 --- /dev/null +++ b/apps/web/components/resource/create-resource-sheet.tsx @@ -0,0 +1,272 @@ +'use client' + +import { useState, useEffect } from 'react' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { CreateResourceSchema, type CreateResourceDto, RESOURCE_MAX_FILE_SIZE } from '@repo/schemas' +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, +} from '@/components/ui/sheet' +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' +import { Button } from '@/components/ui/button' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { FileUpload } from '@/components/ui/file-upload' +import { Loader2 } from 'lucide-react' +import { + useCreateResource, + useTeacherSubjectsForResource, + useAllSubjectTeachersForResource, +} from '@/hooks/useResource' + +const DOCUMENT_ACCEPT = + 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/plain' +const IMAGE_ACCEPT = 'image/jpeg,image/png,image/webp' + +interface CreateResourceSheetProps { + open: boolean + onOpenChange: (open: boolean) => void + variant: 'teacher' | 'admin' +} + +export function CreateResourceSheet({ open, onOpenChange, variant }: CreateResourceSheetProps) { + const { mutate: createResource, isPending } = useCreateResource() + const { data: teacherSubjects } = useTeacherSubjectsForResource() + const { data: allSubjectTeachers } = useAllSubjectTeachersForResource() + const [selectedFile, setSelectedFile] = useState(null) + + const form = useForm({ + resolver: zodResolver(CreateResourceSchema), + defaultValues: { + title: '', + description: '', + resourceType: 'DOCUMENT', + subjectTeacherId: '', + fileName: undefined, + fileSize: undefined, + mimeType: undefined, + externalLink: undefined, + }, + }) + + const resourceType = form.watch('resourceType') + const isFileType = resourceType === 'DOCUMENT' || resourceType === 'IMAGE' + const isLink = resourceType === 'LINK' + + // Reset conditional fields when resource type changes + useEffect(() => { + if (isLink) { + setSelectedFile(null) + form.setValue('fileName', undefined) + form.setValue('fileSize', undefined) + form.setValue('mimeType', undefined) + } else { + form.setValue('externalLink', undefined) + } + }, [resourceType, isLink, form]) + + const handleFileSelect = (file: File | null) => { + setSelectedFile(file) + if (file) { + form.setValue('fileName', file.name) + form.setValue('fileSize', file.size) + form.setValue('mimeType', file.type) + form.clearErrors() + } else { + form.setValue('fileName', undefined) + form.setValue('fileSize', undefined) + form.setValue('mimeType', undefined) + } + } + + const onSubmit = (data: CreateResourceDto) => { + createResource( + { dto: data, file: selectedFile ?? undefined }, + { + onSuccess: () => { + form.reset() + setSelectedFile(null) + onOpenChange(false) + }, + }, + ) + } + + const fileAccept = resourceType === 'IMAGE' ? IMAGE_ACCEPT : DOCUMENT_ACCEPT + + return ( + + + + Upload Resource + Share a document, image, or link with your students. + + +
+
+ + ( + + Title + + + + + + )} + /> + + ( + + Description (Optional) + +