diff --git a/.env b/.env index 82e6a29..dc76c9c 100644 --- a/.env +++ b/.env @@ -1 +1,6 @@ -DATABASE_URL=postgresql://user:password@localhost/dbname \ No newline at end of file +ENVIRONMENT=development + +SECRET_KEY=polieats + +DATABASE_URL=postgresql://user:password@localhost/dbname +MISTRAL_API_KEY=Yn7p3GEHvjZ1HV0nLKjw5hNaUYfJn5Oi diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..42745f8 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +ENVIRONMENT=development + +SECRET_KEY=secret_key_here + +DATABASE_URL=postgresql://user:password@localhost/dbname +MISTRAL_API_KEY=mistral_api_key_here \ No newline at end of file diff --git a/.github/workflows/deno_test.yml b/.github/workflows/deno_test.yml new file mode 100644 index 0000000..ce2b46f --- /dev/null +++ b/.github/workflows/deno_test.yml @@ -0,0 +1,24 @@ +name: CI/Deno Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.3.1 + + - name: Run unit tests + run: deno task test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..df01c9a --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# PoliEats 🍽 - Backend + +## Descrição: + +O PoliEats é um chatbot desenvolvido para auxiliar os alunos, alunos e demais visitantes do colégio **Poliedro** a +fazerem pedidos de comida e bebida a partir de uma interface de chat totalmente automatizada. O bot é capaz de responder perguntas frequentes, fornecer informações sobre o cardápio e realizar pedidos de forma rápida e eficiente. O objetivo principal do PoliEats é facilitar a experiência de compra dos usuários, tornando o processo mais ágil e prático. + +## Funcionalidades: +- **Cardápio**: O bot fornece informações detalhadas sobre o cardápio, incluindo preços e opções disponíveis. +- **Pedidos**: Os usuários podem fazer pedidos diretamente pelo bot, que irá encaminhar as informações para a equipe responsável. +- **Perguntas Frequentes**: O bot é capaz de responder perguntas frequentes sobre o colégio, cardápio e outros assuntos relacionados. +- **Horários**: O bot fornece informações sobre os horários de funcionamento do colégio e do serviço de alimentação. + +## Tecnologias Utilizadas: +- **TypeScript**: Linguagem de programação utilizada para desenvolver o backend. +- **Deno**: Ambiente de execução para o TypeScript. +- **PostgreSQL**: Banco de dados utilizado para armazenar informações sobre o cardápio, pedidos e usuários. +- **DrizzleORM**: ORM utilizado para facilitar a interação com o banco de dados PostgreSQL. +- **Mistral AI**: Modelo de linguagem utilizado para processar as mensagens dos usuários e gerar respostas. +- **LangChain**: Biblioteca utilizada para integrar o modelo de linguagem com o bot e facilitar a construção de fluxos de conversa. + +## Como executar o projeto: +1. Clone o repositório: +```bash +git clone https://github.com/PoliEats/Backend.git +cd Backend +``` + +2. Instale as dependências: +```bash +deno install +``` + +3. Configure o .env: +```bash +cp .env.example .env +``` + +4. Configure o banco de dados: +```bash +deno task db:migrate +``` + +5. Execute o projeto: +```bash +deno task start +``` \ No newline at end of file diff --git a/deno.json b/deno.json index e15b4b3..a8ecd45 100644 --- a/deno.json +++ b/deno.json @@ -1,10 +1,35 @@ { - "tasks": { - "dev": "deno run --allow-net ./src/main.ts" - }, - "imports": { - "@types/express": "npm:@types/express@^5.0.1", - "drizzle-kit": "npm:drizzle-kit@^0.31.0", - "express": "npm:express@^5.1.0" - } -} \ No newline at end of file + "tasks": { + "dev": "deno run --allow-net ./src/main.ts", + "test": "deno test --env --allow-env --allow-read --allow-ffi --allow-sys --allow-net" + }, + "imports": { + "@faker-js/faker": "npm:@faker-js/faker@^9.7.0", + "@langchain/core": "npm:@langchain/core@^0.3.53", + "@langchain/langgraph": "npm:@langchain/langgraph@^0.2.68", + "@types/bcrypt": "npm:@types/bcrypt@^5.0.2", + "@types/cookie-parser": "npm:@types/cookie-parser@^1.4.8", + "@types/cors": "npm:@types/cors@^2.8.18", + "@types/express": "npm:@types/express@^5.0.1", + "@types/pg": "npm:@types/pg@^8.15.1", + "@types/supertest": "npm:@types/supertest@^6.0.3", + "bcrypt": "npm:bcrypt@^6.0.0", + "cookie-parser": "npm:cookie-parser@^1.4.7", + "cors": "npm:cors@^2.8.5", + "drizzle-kit": "npm:drizzle-kit@^0.31.1", + "drizzle-orm": "npm:drizzle-orm@^0.43.1", + "express": "npm:express@^5.1.0", + "@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0", + "jose": "npm:jose@^6.0.11", + "pg": "npm:pg@^8.16.0", + "socket.io": "npm:socket.io@^4.8.1", + "socket.io-client": "npm:socket.io-client@^4.8.1", + "supertest": "npm:supertest@^7.1.1", + "uuid": "npm:uuid@^11.1.0", + "zod": "npm:zod@^3.24.4" + }, + "nodeModulesDir": "auto", + "unstable": [ + "sloppy-imports" + ] +} diff --git a/deno.lock b/deno.lock index 183c574..8323c0b 100644 --- a/deno.lock +++ b/deno.lock @@ -1,11 +1,72 @@ { - "version": "4", + "version": "5", "specifiers": { + "jsr:@std/assert@^1.0.10": "1.0.11", + "jsr:@std/assert@^1.0.11": "1.0.11", + "jsr:@std/crypto@*": "1.0.4", + "jsr:@std/expect@*": "1.0.13", + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/testing@*": "1.0.9", + "npm:@faker-js/faker@^9.7.0": "9.7.0", + "npm:@langchain/core@*": "0.3.53_zod@3.24.4", + "npm:@langchain/core@~0.3.53": "0.3.53_zod@3.24.4", + "npm:@langchain/langgraph@~0.2.68": "0.2.68_@langchain+core@0.3.53__zod@3.24.4", + "npm:@langchain/mistralai@0.2": "0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4", + "npm:@types/bcrypt@^5.0.2": "5.0.2", + "npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.1", + "npm:@types/cors@^2.8.18": "2.8.18", "npm:@types/express@^5.0.1": "5.0.1", - "npm:drizzle-kit@0.31": "0.31.0_esbuild@0.25.3", - "npm:express@^5.1.0": "5.1.0" + "npm:@types/node@*": "22.12.0", + "npm:@types/pg@^8.15.1": "8.15.1", + "npm:@types/supertest@^6.0.3": "6.0.3", + "npm:bcrypt@6": "6.0.0", + "npm:cookie-parser@^1.4.7": "1.4.7", + "npm:cors@*": "2.8.5", + "npm:cors@^2.8.5": "2.8.5", + "npm:drizzle-kit@~0.31.1": "0.31.1_esbuild@0.25.4", + "npm:drizzle-orm@~0.43.1": "0.43.1_pg@8.16.0_@types+pg@8.15.1", + "npm:express@^5.1.0": "5.1.0", + "npm:jose@^6.0.11": "6.0.11", + "npm:pg@^8.16.0": "8.16.0", + "npm:socket.io-client@^4.8.1": "4.8.1", + "npm:socket.io@*": "4.8.1", + "npm:socket.io@^4.8.1": "4.8.1", + "npm:supertest@^7.1.1": "7.1.1", + "npm:uuid@^11.1.0": "11.1.0", + "npm:zod@^3.24.4": "3.24.4" + }, + "jsr": { + "@std/assert@1.0.11": { + "integrity": "2461ef3c368fe88bc60e186e7744a93112f16fd110022e113a0849e94d1c83c1", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/crypto@1.0.4": { + "integrity": "cee245c453bd5366207f4d8aa25ea3e9c86cecad2be3fefcaa6cb17203d79340" + }, + "@std/expect@1.0.13": { + "integrity": "d8e236c7089cd9fcf5e6032f27dadc3db6349d0aee48c15bc71d717bca5baa42", + "dependencies": [ + "jsr:@std/assert@^1.0.11", + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/testing@1.0.9": { + "integrity": "9bdd4ac07cb13e7594ac30e90f6ceef7254ac83a9aeaa089be0008f33aab5cd4", + "dependencies": [ + "jsr:@std/assert@^1.0.10", + "jsr:@std/internal" + ] + } }, "npm": { + "@cfworker/json-schema@4.1.1": { + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==" + }, "@drizzle-team/brocli@0.10.2": { "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==" }, @@ -14,155 +75,336 @@ "dependencies": [ "esbuild@0.18.20", "source-map-support" - ] + ], + "deprecated": true }, "@esbuild-kit/esm-loader@2.6.5": { "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", "dependencies": [ "@esbuild-kit/core-utils", "get-tsconfig" - ] + ], + "deprecated": true }, - "@esbuild/aix-ppc64@0.25.3": { - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==" + "@esbuild/aix-ppc64@0.25.4": { + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "os": ["aix"], + "cpu": ["ppc64"] }, "@esbuild/android-arm64@0.18.20": { - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==" + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "os": ["android"], + "cpu": ["arm64"] }, - "@esbuild/android-arm64@0.25.3": { - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==" + "@esbuild/android-arm64@0.25.4": { + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "os": ["android"], + "cpu": ["arm64"] }, "@esbuild/android-arm@0.18.20": { - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==" + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "os": ["android"], + "cpu": ["arm"] }, - "@esbuild/android-arm@0.25.3": { - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==" + "@esbuild/android-arm@0.25.4": { + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "os": ["android"], + "cpu": ["arm"] }, "@esbuild/android-x64@0.18.20": { - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==" + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "os": ["android"], + "cpu": ["x64"] }, - "@esbuild/android-x64@0.25.3": { - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==" + "@esbuild/android-x64@0.25.4": { + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "os": ["android"], + "cpu": ["x64"] }, "@esbuild/darwin-arm64@0.18.20": { - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==" + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "os": ["darwin"], + "cpu": ["arm64"] }, - "@esbuild/darwin-arm64@0.25.3": { - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==" + "@esbuild/darwin-arm64@0.25.4": { + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "os": ["darwin"], + "cpu": ["arm64"] }, "@esbuild/darwin-x64@0.18.20": { - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==" + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "os": ["darwin"], + "cpu": ["x64"] }, - "@esbuild/darwin-x64@0.25.3": { - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==" + "@esbuild/darwin-x64@0.25.4": { + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "os": ["darwin"], + "cpu": ["x64"] }, "@esbuild/freebsd-arm64@0.18.20": { - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==" + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "os": ["freebsd"], + "cpu": ["arm64"] }, - "@esbuild/freebsd-arm64@0.25.3": { - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==" + "@esbuild/freebsd-arm64@0.25.4": { + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "os": ["freebsd"], + "cpu": ["arm64"] }, "@esbuild/freebsd-x64@0.18.20": { - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==" + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, - "@esbuild/freebsd-x64@0.25.3": { - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==" + "@esbuild/freebsd-x64@0.25.4": { + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "os": ["freebsd"], + "cpu": ["x64"] }, "@esbuild/linux-arm64@0.18.20": { - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==" + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "os": ["linux"], + "cpu": ["arm64"] }, - "@esbuild/linux-arm64@0.25.3": { - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==" + "@esbuild/linux-arm64@0.25.4": { + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "os": ["linux"], + "cpu": ["arm64"] }, "@esbuild/linux-arm@0.18.20": { - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==" + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "os": ["linux"], + "cpu": ["arm"] }, - "@esbuild/linux-arm@0.25.3": { - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==" + "@esbuild/linux-arm@0.25.4": { + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "os": ["linux"], + "cpu": ["arm"] }, "@esbuild/linux-ia32@0.18.20": { - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==" + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "os": ["linux"], + "cpu": ["ia32"] }, - "@esbuild/linux-ia32@0.25.3": { - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==" + "@esbuild/linux-ia32@0.25.4": { + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "os": ["linux"], + "cpu": ["ia32"] }, "@esbuild/linux-loong64@0.18.20": { - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==" + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "os": ["linux"], + "cpu": ["loong64"] }, - "@esbuild/linux-loong64@0.25.3": { - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==" + "@esbuild/linux-loong64@0.25.4": { + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "os": ["linux"], + "cpu": ["loong64"] }, "@esbuild/linux-mips64el@0.18.20": { - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==" + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "os": ["linux"], + "cpu": ["mips64el"] }, - "@esbuild/linux-mips64el@0.25.3": { - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==" + "@esbuild/linux-mips64el@0.25.4": { + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "os": ["linux"], + "cpu": ["mips64el"] }, "@esbuild/linux-ppc64@0.18.20": { - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==" + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "os": ["linux"], + "cpu": ["ppc64"] }, - "@esbuild/linux-ppc64@0.25.3": { - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==" + "@esbuild/linux-ppc64@0.25.4": { + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "os": ["linux"], + "cpu": ["ppc64"] }, "@esbuild/linux-riscv64@0.18.20": { - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==" + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "os": ["linux"], + "cpu": ["riscv64"] }, - "@esbuild/linux-riscv64@0.25.3": { - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==" + "@esbuild/linux-riscv64@0.25.4": { + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "os": ["linux"], + "cpu": ["riscv64"] }, "@esbuild/linux-s390x@0.18.20": { - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==" + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "os": ["linux"], + "cpu": ["s390x"] }, - "@esbuild/linux-s390x@0.25.3": { - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==" + "@esbuild/linux-s390x@0.25.4": { + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "os": ["linux"], + "cpu": ["s390x"] }, "@esbuild/linux-x64@0.18.20": { - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==" + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "os": ["linux"], + "cpu": ["x64"] }, - "@esbuild/linux-x64@0.25.3": { - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==" + "@esbuild/linux-x64@0.25.4": { + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "os": ["linux"], + "cpu": ["x64"] }, - "@esbuild/netbsd-arm64@0.25.3": { - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==" + "@esbuild/netbsd-arm64@0.25.4": { + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "os": ["netbsd"], + "cpu": ["arm64"] }, "@esbuild/netbsd-x64@0.18.20": { - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==" + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "os": ["netbsd"], + "cpu": ["x64"] }, - "@esbuild/netbsd-x64@0.25.3": { - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==" + "@esbuild/netbsd-x64@0.25.4": { + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "os": ["netbsd"], + "cpu": ["x64"] }, - "@esbuild/openbsd-arm64@0.25.3": { - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==" + "@esbuild/openbsd-arm64@0.25.4": { + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "os": ["openbsd"], + "cpu": ["arm64"] }, "@esbuild/openbsd-x64@0.18.20": { - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==" + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "os": ["openbsd"], + "cpu": ["x64"] }, - "@esbuild/openbsd-x64@0.25.3": { - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==" + "@esbuild/openbsd-x64@0.25.4": { + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "os": ["openbsd"], + "cpu": ["x64"] }, "@esbuild/sunos-x64@0.18.20": { - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==" + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "os": ["sunos"], + "cpu": ["x64"] }, - "@esbuild/sunos-x64@0.25.3": { - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==" + "@esbuild/sunos-x64@0.25.4": { + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "os": ["sunos"], + "cpu": ["x64"] }, "@esbuild/win32-arm64@0.18.20": { - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==" + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "os": ["win32"], + "cpu": ["arm64"] }, - "@esbuild/win32-arm64@0.25.3": { - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==" + "@esbuild/win32-arm64@0.25.4": { + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "os": ["win32"], + "cpu": ["arm64"] }, "@esbuild/win32-ia32@0.18.20": { - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==" + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "os": ["win32"], + "cpu": ["ia32"] }, - "@esbuild/win32-ia32@0.25.3": { - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==" + "@esbuild/win32-ia32@0.25.4": { + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "os": ["win32"], + "cpu": ["ia32"] }, "@esbuild/win32-x64@0.18.20": { - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==" + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.25.4": { + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@faker-js/faker@9.7.0": { + "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==" + }, + "@langchain/core@0.3.53_zod@3.24.4": { + "integrity": "sha512-rHlBcEG5PNaWxlVhPTLiZ0WRCr/URNEUynhgZTZ8QbTJhQ1vEMibdr2YL9LYKHSXNyAp/b5j3itcu3epB8FD7Q==", + "dependencies": [ + "@cfworker/json-schema", + "ansi-styles@5.2.0", + "camelcase", + "decamelize", + "js-tiktoken", + "langsmith", + "mustache", + "p-queue", + "p-retry", + "uuid@10.0.0", + "zod", + "zod-to-json-schema" + ] + }, + "@langchain/langgraph-checkpoint@0.0.17_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-6b3CuVVYx+7x0uWLG+7YXz9j2iBa+tn2AXvkLxzEvaAsLE6Sij++8PPbS2BZzC+S/FPJdWsz6I5bsrqL0BYrCA==", + "dependencies": [ + "@langchain/core", + "uuid@10.0.0" + ] + }, + "@langchain/langgraph-sdk@0.0.74_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-IUN0m4BYkGWdviFd4EaWDcQgxNq8z+1LIwXajCSt9B+Cb/pz0ZNpIPdu5hAIsf6a0RWu5yRUhzL1L40t7vu3Zg==", + "dependencies": [ + "@langchain/core", + "@types/json-schema", + "p-queue", + "p-retry", + "uuid@9.0.1" + ], + "optionalPeers": [ + "@langchain/core" + ] + }, + "@langchain/langgraph@0.2.68_@langchain+core@0.3.53__zod@3.24.4": { + "integrity": "sha512-wxdGmOeRUQutCWfdKwFb+92RMdZZ8jvXkG7PD3ZcVLiuOXhC9LSe5xcc1UbWHcV29fV542IDHMfvf2OVdv9dZQ==", + "dependencies": [ + "@langchain/core", + "@langchain/langgraph-checkpoint", + "@langchain/langgraph-sdk", + "uuid@10.0.0", + "zod" + ] + }, + "@langchain/mistralai@0.2.0_@langchain+core@0.3.53__zod@3.24.4_zod@3.24.4": { + "integrity": "sha512-VdfbKZopAuSXf/vlXbriGWLK3c7j5s47DoB3S31xpprY2BMSKZZiX9vE9TsgxMfAPuIDPIYcfgU7p1upvTYt8g==", + "dependencies": [ + "@langchain/core", + "@mistralai/mistralai", + "uuid@10.0.0", + "zod", + "zod-to-json-schema" + ] }, - "@esbuild/win32-x64@0.25.3": { - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==" + "@mistralai/mistralai@1.6.0_zod@3.24.4": { + "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", + "dependencies": [ + "zod", + "zod-to-json-schema" + ] + }, + "@noble/hashes@1.8.0": { + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" + }, + "@paralleldrive/cuid2@2.2.2": { + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dependencies": [ + "@noble/hashes" + ] + }, + "@socket.io/component-emitter@3.1.2": { + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "@types/bcrypt@5.0.2": { + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dependencies": [ + "@types/node" + ] }, "@types/body-parser@1.19.5": { "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", @@ -177,6 +419,27 @@ "@types/node" ] }, + "@types/cookie-parser@1.4.8_@types+express@5.0.1": { + "integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==", + "dependencies": [ + "@types/express" + ] + }, + "@types/cookiejar@2.1.5": { + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==" + }, + "@types/cors@2.8.17": { + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": [ + "@types/node" + ] + }, + "@types/cors@2.8.18": { + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "dependencies": [ + "@types/node" + ] + }, "@types/express-serve-static-core@5.0.6": { "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dependencies": [ @@ -197,6 +460,12 @@ "@types/http-errors@2.0.4": { "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "@types/methods@1.1.4": { + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==" + }, "@types/mime@1.3.5": { "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, @@ -206,12 +475,23 @@ "undici-types" ] }, + "@types/pg@8.15.1": { + "integrity": "sha512-YKHrkGWBX5+ivzvOQ66I0fdqsQTsvxqM0AGP2i0XrVZ9DP5VA/deEbTf7VuLPGpY7fJB9uGbkZ6KjVhuHcrTkQ==", + "dependencies": [ + "@types/node", + "pg-protocol", + "pg-types@4.0.2" + ] + }, "@types/qs@6.9.18": { "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" }, "@types/range-parser@1.2.7": { "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "@types/retry@0.12.0": { + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "@types/send@0.17.4": { "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dependencies": [ @@ -227,19 +507,74 @@ "@types/send" ] }, + "@types/superagent@8.1.9": { + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dependencies": [ + "@types/cookiejar", + "@types/methods", + "@types/node", + "form-data" + ] + }, + "@types/supertest@6.0.3": { + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dependencies": [ + "@types/methods", + "@types/superagent" + ] + }, + "@types/uuid@10.0.0": { + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types@2.1.35", + "negotiator@0.6.3" + ] + }, "accepts@2.0.0": { "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": [ - "mime-types", - "negotiator" + "mime-types@3.0.1", + "negotiator@1.0.0" + ] + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" ] }, + "ansi-styles@5.2.0": { + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "asap@2.0.6": { + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "asynckit@0.4.0": { + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64id@2.0.0": { + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bcrypt@6.0.0": { + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "dependencies": [ + "node-addon-api", + "node-gyp-build" + ], + "scripts": true + }, "body-parser@2.2.0": { "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": [ "bytes", "content-type", - "debug", + "debug@4.4.0", "http-errors", "iconv-lite", "on-finished", @@ -268,6 +603,40 @@ "get-intrinsic" ] }, + "camelcase@6.3.0": { + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles@4.3.0", + "supports-color" + ] + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream@1.0.8": { + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": [ + "delayed-stream" + ] + }, + "component-emitter@1.3.1": { + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==" + }, + "console-table-printer@2.12.1": { + "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "dependencies": [ + "simple-wcswidth" + ] + }, "content-disposition@1.0.0": { "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dependencies": [ @@ -277,28 +646,91 @@ "content-type@1.0.5": { "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, + "cookie-parser@1.4.7": { + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": [ + "cookie", + "cookie-signature@1.0.6" + ] + }, + "cookie-signature@1.0.6": { + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "cookie-signature@1.2.2": { "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" }, "cookie@0.7.2": { "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, + "cookiejar@2.1.4": { + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, + "cors@2.8.5": { + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": [ + "object-assign", + "vary" + ] + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms" + ] + }, "debug@4.4.0": { "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": [ "ms" ] }, + "decamelize@1.2.0": { + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, + "delayed-stream@1.0.0": { + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, - "drizzle-kit@0.31.0_esbuild@0.25.3": { - "integrity": "sha512-pcKVT+GbfPA+bUovPIilgVOoq+onNBo/YQBG86sf3/GFHkN6lRJPm1l7dKN0IMAk57RQoIm4GUllRrasLlcaSg==", + "dezalgo@1.0.4": { + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": [ + "asap", + "wrappy" + ] + }, + "drizzle-kit@0.31.1_esbuild@0.25.4": { + "integrity": "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==", "dependencies": [ "@drizzle-team/brocli", "@esbuild-kit/esm-loader", - "esbuild-register", - "esbuild@0.25.3" + "esbuild@0.25.4", + "esbuild-register" + ], + "bin": true + }, + "drizzle-orm@0.43.1": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==" + }, + "drizzle-orm@0.43.1_pg@8.16.0": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==", + "dependencies": [ + "pg" + ], + "optionalPeers": [ + "pg" + ] + }, + "drizzle-orm@0.43.1_pg@8.16.0_@types+pg@8.15.1": { + "integrity": "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==", + "dependencies": [ + "@types/pg", + "pg" + ], + "optionalPeers": [ + "@types/pg", + "pg" ] }, "dunder-proto@1.0.1": { @@ -315,6 +747,33 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, + "engine.io-client@6.6.3": { + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-parser", + "ws", + "xmlhttprequest-ssl" + ] + }, + "engine.io-parser@5.2.3": { + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "engine.io@6.6.4": { + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": [ + "@types/cors@2.8.17", + "@types/node", + "accepts@1.3.8", + "base64id", + "cookie", + "cors", + "debug@4.3.7", + "engine.io-parser", + "ws" + ] + }, "es-define-property@1.0.1": { "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, @@ -327,25 +786,34 @@ "es-errors" ] }, - "esbuild-register@3.6.0_esbuild@0.25.3": { + "es-set-tostringtag@2.1.0": { + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": [ + "es-errors", + "get-intrinsic", + "has-tostringtag", + "hasown" + ] + }, + "esbuild-register@3.6.0_esbuild@0.25.4": { "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dependencies": [ - "debug", - "esbuild@0.25.3" + "debug@4.4.0", + "esbuild@0.25.4" ] }, "esbuild@0.18.20": { "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dependencies": [ - "@esbuild/android-arm64@0.18.20", + "optionalDependencies": [ "@esbuild/android-arm@0.18.20", + "@esbuild/android-arm64@0.18.20", "@esbuild/android-x64@0.18.20", "@esbuild/darwin-arm64@0.18.20", "@esbuild/darwin-x64@0.18.20", "@esbuild/freebsd-arm64@0.18.20", "@esbuild/freebsd-x64@0.18.20", - "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-arm@0.18.20", + "@esbuild/linux-arm64@0.18.20", "@esbuild/linux-ia32@0.18.20", "@esbuild/linux-loong64@0.18.20", "@esbuild/linux-mips64el@0.18.20", @@ -359,37 +827,41 @@ "@esbuild/win32-arm64@0.18.20", "@esbuild/win32-ia32@0.18.20", "@esbuild/win32-x64@0.18.20" - ] + ], + "scripts": true, + "bin": true }, - "esbuild@0.25.3": { - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", - "dependencies": [ + "esbuild@0.25.4": { + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "optionalDependencies": [ "@esbuild/aix-ppc64", - "@esbuild/android-arm64@0.25.3", - "@esbuild/android-arm@0.25.3", - "@esbuild/android-x64@0.25.3", - "@esbuild/darwin-arm64@0.25.3", - "@esbuild/darwin-x64@0.25.3", - "@esbuild/freebsd-arm64@0.25.3", - "@esbuild/freebsd-x64@0.25.3", - "@esbuild/linux-arm64@0.25.3", - "@esbuild/linux-arm@0.25.3", - "@esbuild/linux-ia32@0.25.3", - "@esbuild/linux-loong64@0.25.3", - "@esbuild/linux-mips64el@0.25.3", - "@esbuild/linux-ppc64@0.25.3", - "@esbuild/linux-riscv64@0.25.3", - "@esbuild/linux-s390x@0.25.3", - "@esbuild/linux-x64@0.25.3", + "@esbuild/android-arm@0.25.4", + "@esbuild/android-arm64@0.25.4", + "@esbuild/android-x64@0.25.4", + "@esbuild/darwin-arm64@0.25.4", + "@esbuild/darwin-x64@0.25.4", + "@esbuild/freebsd-arm64@0.25.4", + "@esbuild/freebsd-x64@0.25.4", + "@esbuild/linux-arm@0.25.4", + "@esbuild/linux-arm64@0.25.4", + "@esbuild/linux-ia32@0.25.4", + "@esbuild/linux-loong64@0.25.4", + "@esbuild/linux-mips64el@0.25.4", + "@esbuild/linux-ppc64@0.25.4", + "@esbuild/linux-riscv64@0.25.4", + "@esbuild/linux-s390x@0.25.4", + "@esbuild/linux-x64@0.25.4", "@esbuild/netbsd-arm64", - "@esbuild/netbsd-x64@0.25.3", + "@esbuild/netbsd-x64@0.25.4", "@esbuild/openbsd-arm64", - "@esbuild/openbsd-x64@0.25.3", - "@esbuild/sunos-x64@0.25.3", - "@esbuild/win32-arm64@0.25.3", - "@esbuild/win32-ia32@0.25.3", - "@esbuild/win32-x64@0.25.3" - ] + "@esbuild/openbsd-x64@0.25.4", + "@esbuild/sunos-x64@0.25.4", + "@esbuild/win32-arm64@0.25.4", + "@esbuild/win32-ia32@0.25.4", + "@esbuild/win32-x64@0.25.4" + ], + "scripts": true, + "bin": true }, "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" @@ -397,16 +869,19 @@ "etag@1.8.1": { "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "eventemitter3@4.0.7": { + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "express@5.1.0": { "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dependencies": [ - "accepts", + "accepts@2.0.0", "body-parser", "content-disposition", "content-type", "cookie", - "cookie-signature", - "debug", + "cookie-signature@1.2.2", + "debug@4.4.0", "encodeurl", "escape-html", "etag", @@ -414,7 +889,7 @@ "fresh", "http-errors", "merge-descriptors", - "mime-types", + "mime-types@3.0.1", "on-finished", "once", "parseurl", @@ -429,10 +904,13 @@ "vary" ] }, + "fast-safe-stringify@2.1.1": { + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "finalhandler@2.1.0": { "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dependencies": [ - "debug", + "debug@4.4.0", "encodeurl", "escape-html", "on-finished", @@ -440,6 +918,23 @@ "statuses" ] }, + "form-data@4.0.2": { + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": [ + "asynckit", + "combined-stream", + "es-set-tostringtag", + "mime-types@2.1.35" + ] + }, + "formidable@3.5.4": { + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dependencies": [ + "@paralleldrive/cuid2", + "dezalgo", + "once" + ] + }, "forwarded@0.2.0": { "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, @@ -480,9 +975,18 @@ "gopd@1.2.0": { "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "has-symbols@1.1.0": { "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, + "has-tostringtag@1.0.2": { + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": [ + "has-symbols" + ] + }, "hasown@2.0.2": { "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": [ @@ -514,6 +1018,27 @@ "is-promise@4.0.0": { "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "jose@6.0.11": { + "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==" + }, + "js-tiktoken@1.0.20": { + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "dependencies": [ + "base64-js" + ] + }, + "langsmith@0.3.25": { + "integrity": "sha512-KuJu89VY3DmCdFvlVxQG4owQl546Z6pQc6TbhsyP77MkVJgZr8yvevZvvcXDWIpT2o2s52c9Aww2XVOH6GmHxQ==", + "dependencies": [ + "@types/uuid", + "chalk", + "console-table-printer", + "p-queue", + "p-retry", + "semver", + "uuid@10.0.0" + ] + }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, @@ -523,24 +1048,60 @@ "merge-descriptors@2.0.0": { "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, + "methods@1.1.2": { + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, "mime-db@1.54.0": { "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db@1.52.0" + ] + }, "mime-types@3.0.1": { "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dependencies": [ - "mime-db" + "mime-db@1.54.0" ] }, + "mime@2.6.0": { + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": true + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "mustache@4.2.0": { + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": true + }, + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, "negotiator@1.0.0": { "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, + "node-addon-api@8.3.1": { + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==" + }, + "node-gyp-build@4.8.4": { + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": true + }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect@1.13.4": { "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, + "obuf@1.1.2": { + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, "on-finished@2.4.1": { "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": [ @@ -553,12 +1114,130 @@ "wrappy" ] }, + "p-finally@1.0.0": { + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + }, + "p-queue@6.6.2": { + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": [ + "eventemitter3", + "p-timeout" + ] + }, + "p-retry@4.6.2": { + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": [ + "@types/retry", + "retry" + ] + }, + "p-timeout@3.2.0": { + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": [ + "p-finally" + ] + }, "parseurl@1.3.3": { "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp@8.2.0": { "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" }, + "pg-cloudflare@1.2.5": { + "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==" + }, + "pg-connection-string@2.9.0": { + "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==" + }, + "pg-int8@1.0.1": { + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric@1.0.2": { + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==" + }, + "pg-pool@3.10.0_pg@8.16.0": { + "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "dependencies": [ + "pg" + ] + }, + "pg-protocol@1.10.0": { + "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==" + }, + "pg-types@2.2.0": { + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": [ + "pg-int8", + "postgres-array@2.0.0", + "postgres-bytea@1.0.0", + "postgres-date@1.0.7", + "postgres-interval@1.2.0" + ] + }, + "pg-types@4.0.2": { + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dependencies": [ + "pg-int8", + "pg-numeric", + "postgres-array@3.0.4", + "postgres-bytea@3.0.0", + "postgres-date@2.1.0", + "postgres-interval@3.0.0", + "postgres-range" + ] + }, + "pg@8.16.0": { + "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "dependencies": [ + "pg-connection-string", + "pg-pool", + "pg-protocol", + "pg-types@2.2.0", + "pgpass" + ], + "optionalDependencies": [ + "pg-cloudflare" + ] + }, + "pgpass@1.0.5": { + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": [ + "split2" + ] + }, + "postgres-array@2.0.0": { + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-array@3.0.4": { + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==" + }, + "postgres-bytea@1.0.0": { + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-bytea@3.0.0": { + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dependencies": [ + "obuf" + ] + }, + "postgres-date@1.0.7": { + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-date@2.1.0": { + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==" + }, + "postgres-interval@1.2.0": { + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": [ + "xtend" + ] + }, + "postgres-interval@3.0.0": { + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==" + }, + "postgres-range@1.1.4": { + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" + }, "proxy-addr@2.0.7": { "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": [ @@ -587,10 +1266,13 @@ "resolve-pkg-maps@1.0.0": { "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" }, + "retry@0.13.1": { + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, "router@2.2.0": { "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dependencies": [ - "debug", + "debug@4.4.0", "depd", "is-promise", "parseurl", @@ -603,16 +1285,20 @@ "safer-buffer@2.1.2": { "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver@7.7.1": { + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": true + }, "send@1.2.0": { "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dependencies": [ - "debug", + "debug@4.4.0", "encodeurl", "escape-html", "etag", "fresh", "http-errors", - "mime-types", + "mime-types@3.0.1", "ms", "on-finished", "range-parser", @@ -667,6 +1353,44 @@ "side-channel-weakmap" ] }, + "simple-wcswidth@1.0.1": { + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" + }, + "socket.io-adapter@2.5.5": { + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": [ + "debug@4.3.7", + "ws" + ] + }, + "socket.io-client@4.8.1": { + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-client", + "socket.io-parser" + ] + }, + "socket.io-parser@4.2.4": { + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7" + ] + }, + "socket.io@4.8.1": { + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": [ + "accepts@1.3.8", + "base64id", + "cors", + "debug@4.3.7", + "engine.io", + "socket.io-adapter", + "socket.io-parser" + ] + }, "source-map-support@0.5.21": { "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dependencies": [ @@ -677,9 +1401,39 @@ "source-map@0.6.1": { "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "split2@4.2.0": { + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "statuses@2.0.1": { "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "superagent@10.2.1": { + "integrity": "sha512-O+PCv11lgTNJUzy49teNAWLjBZfc+A1enOwTpLlH6/rsvKcTwcdTT8m9azGkVqM7HBl5jpyZ7KTPhHweokBcdg==", + "dependencies": [ + "component-emitter", + "cookiejar", + "debug@4.4.0", + "fast-safe-stringify", + "form-data", + "formidable", + "methods", + "mime", + "qs" + ] + }, + "supertest@7.1.1": { + "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", + "dependencies": [ + "methods", + "superagent" + ] + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, "toidentifier@1.0.1": { "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, @@ -688,7 +1442,7 @@ "dependencies": [ "content-type", "media-typer", - "mime-types" + "mime-types@3.0.1" ] }, "undici-types@6.20.0": { @@ -697,18 +1451,68 @@ "unpipe@1.0.0": { "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "uuid@10.0.0": { + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "bin": true + }, + "uuid@11.1.0": { + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "bin": true + }, + "uuid@9.0.1": { + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "bin": true + }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws@8.17.1": { + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + }, + "xmlhttprequest-ssl@2.1.2": { + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, + "xtend@4.0.2": { + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "zod-to-json-schema@3.24.5_zod@3.24.4": { + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dependencies": [ + "zod" + ] + }, + "zod@3.24.4": { + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==" } }, "workspace": { "dependencies": [ + "npm:@faker-js/faker@^9.7.0", + "npm:@langchain/core@~0.3.53", + "npm:@langchain/langgraph@~0.2.68", + "npm:@langchain/mistralai@0.2", + "npm:@types/bcrypt@^5.0.2", + "npm:@types/cookie-parser@^1.4.8", + "npm:@types/cors@^2.8.18", "npm:@types/express@^5.0.1", - "npm:drizzle-kit@0.31", - "npm:express@^5.1.0" + "npm:@types/pg@^8.15.1", + "npm:@types/supertest@^6.0.3", + "npm:bcrypt@6", + "npm:cookie-parser@^1.4.7", + "npm:cors@^2.8.5", + "npm:drizzle-kit@~0.31.1", + "npm:drizzle-orm@~0.43.1", + "npm:express@^5.1.0", + "npm:jose@^6.0.11", + "npm:pg@^8.16.0", + "npm:socket.io-client@^4.8.1", + "npm:socket.io@^4.8.1", + "npm:supertest@^7.1.1", + "npm:uuid@^11.1.0", + "npm:zod@^3.24.4" ] } } diff --git a/drizzle.config.ts b/drizzle.config.ts index d6f7756..b041ca5 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,10 +1,11 @@ -import { defineConfig } from 'drizzle-kit'; +import { defineConfig } from "drizzle-kit"; export default defineConfig({ - out: './drizzle', - schema: './src/db/schema.ts', - dialect: 'postgresql', + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", dbCredentials: { - url: Deno.env.get('DATABASE_URL') || 'postgres://postgres:password@localhost:5432/postgres', + url: Deno.env.get("DATABASE_URL") || + "postgres://postgres:password@localhost:5432/postgres", }, }); diff --git a/src/database/MockDatabase.ts b/src/database/MockDatabase.ts new file mode 100644 index 0000000..291e93c --- /dev/null +++ b/src/database/MockDatabase.ts @@ -0,0 +1,93 @@ +// deno-lint-ignore-file +import { PgTableWithColumns } from "drizzle-orm/pg-core/table"; +import { InferInsertModel, InferSelectModel, TableConfig } from "drizzle-orm/table"; +import { IDatabase } from "../interfaces/IDatabase.ts"; + +export class MockDatabase implements IDatabase { + constructor() { + this.data = new Map(); + } + + private generateId(): string { + return Math.random().toString(36).substring(2, 15); + } + + private generateNumberId(): number { + return Math.floor(Math.random() * 1000000); + } + + private data: Map; + + insert( + _table: PgTableWithColumns, + data: InferInsertModel>, + ): Promise<{ + id: typeof _table["id"]["_"]["data"]; + }> { + let id; + if (typeof _table.id == "number") { + id = this.generateNumberId(); + } + + id = this.generateId(); + this.data.set(id, { ...data, id, table: _table }); + return Promise.resolve({ id }); + } + + update( + _table: PgTableWithColumns, + id: string | number, + data: InferInsertModel>, + ): Promise<{ + id: typeof _table["id"]["_"]["data"]; + }> { + if (this.data.has(id)) { + this.data.set(id, { ...this.data.get(id), ...data }); + return Promise.resolve({ id }); + } + return Promise.reject(new Error("Not found")); + } + + delete( + _table: PgTableWithColumns, + id: string | number, + ): Promise { + if (this.data.has(id)) { + this.data.delete(id); + return Promise.resolve(true); + } + return Promise.resolve(false); + } + + select( + _table: PgTableWithColumns, + id: string | number, + ): Promise> | null> { + if (this.data.has(id)) { + return Promise.resolve(this.data.get(id)); + } + return Promise.resolve(null); + } + + selectAll( + table: PgTableWithColumns, + ): Promise>[]> { + const results: InferSelectModel>[] = []; + this.data.forEach((value) => { + if (value.table === table) { + results.push(value); + } + }); + return Promise.resolve(results); + } + + selectByField(table: PgTableWithColumns, field: keyof InferSelectModel>, value: string | number): Promise>[]> { + const results: InferSelectModel>[] = []; + this.data.forEach((record) => { + if (record.table === table && record[field] === value) { + results.push(record); + } + }); + return Promise.resolve(results); + } +} \ No newline at end of file diff --git a/src/database/schema.ts b/src/database/schema.ts new file mode 100644 index 0000000..1d07295 --- /dev/null +++ b/src/database/schema.ts @@ -0,0 +1,26 @@ +import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: serial("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + document: text("document").notNull().unique(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}) + +export const salt = pgTable("salt", { + id: serial("id").primaryKey(), + userId: serial("user_id").references(() => user.id), + salt: text("salt").notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}); + +export const password = pgTable("password", { + id: serial("id").primaryKey(), + userId: serial("user_id").references(() => user.id), + password: text("password").notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()), +}); \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/interfaces/IDatabase.ts b/src/interfaces/IDatabase.ts new file mode 100644 index 0000000..acd8314 --- /dev/null +++ b/src/interfaces/IDatabase.ts @@ -0,0 +1,76 @@ +import { InferInsertModel, InferSelectModel } from "drizzle-orm"; +import { PgTableWithColumns, TableConfig } from "drizzle-orm/pg-core"; + +export interface IDatabase { + /** + * Inserts a new record into the specified table. + * @param table - The table to insert the record into. + * @param data - The data to insert. + * @returns The inserted record's ID. + */ + insert( + table: PgTableWithColumns, + data: InferInsertModel>, + ): Promise<{ + id: typeof table["id"]["_"]["data"]; + }>; + + /** + * Updates an existing record in the specified table. + * @param table - The table to update the record in. + * @param id - The ID of the record to update. + * @param data - The data to update. + * @returns The updated record's ID. + */ + update( + table: PgTableWithColumns, + id: string | number, + data: InferInsertModel>, + ): Promise<{ + id: typeof table["id"]["_"]["data"]; + }>; + + /** + * Deletes a record from the specified table. + * @param table - The table to delete the record from. + * @param id - The ID of the record to delete. + * @returns A boolean indicating whether the deletion was successful. + */ + delete( + table: PgTableWithColumns, + id: string | number, + ): Promise; + + /** + * Selects a record from the specified table by its ID. + * @param table - The table to select the record from. + * @param id - The ID of the record to select. + * @returns The selected record or null if not found. + */ + select( + table: PgTableWithColumns, + id: string | number, + ): Promise> | null>; + + /** + * Selects all records from the specified table. + * @param table - The table to select records from. + * @returns An array of selected records. + */ + selectAll( + table: PgTableWithColumns, + ): Promise>[]>; + + /** + * Selects records from the specified table by a specific field and value. + * @param table - The table to select records from. + * @param field - The field to filter by. + * @param value - The value to filter by. + * @returns An array of selected records. + */ + selectByField( + table: PgTableWithColumns, + field: keyof InferSelectModel>, + value: string | number, + ): Promise>[]>; +} \ No newline at end of file diff --git a/src/interfaces/IUser.ts b/src/interfaces/IUser.ts new file mode 100644 index 0000000..2f87ec3 --- /dev/null +++ b/src/interfaces/IUser.ts @@ -0,0 +1,14 @@ +/** + * User Interface + * This interface defines the structure of a user object. + * It includes properties such as id, name, email, document, createdAt, and updatedAt. +*/ + +export interface IUser { + id: number; + name: string; + email: string; + document: string; + createdAt: Date; + updatedAt: Date; +} \ No newline at end of file diff --git a/src/lc/model.ts b/src/lc/model.ts new file mode 100644 index 0000000..28f8748 --- /dev/null +++ b/src/lc/model.ts @@ -0,0 +1,58 @@ +import { HumanMessage, SystemMessage } from "@langchain/core/messages"; +import { ChatMistralAI } from "@langchain/mistralai"; +import { DynamicStructuredTool } from "npm:@langchain/core/tools"; +import { cancelOrder, createOrder, getOrder } from "./tools.ts"; + +const llm = new ChatMistralAI({ + model: "mistral-small-latest", + temperature: 0, +}); + +const tools: DynamicStructuredTool[] = [getOrder, createOrder, cancelOrder]; + +const llmWithTools = llm.bindTools(tools); + +const messages = [ + new SystemMessage( + "Você é um assistente de um chatbot chamado 'PoliEats', sua função é prover informações sobre o estabelecimento como: Cardápio, horários de funcionamento, receber pedidos e prover status dos pedidos em andamento. Você responde apenas em português. Você tem permissão para se adequar ao perfil do usuário, utilizando gírias e expressões populares.", + ), +]; + +const toolsByName = tools.reduce((acc, tool) => { + acc[tool.name] = tool; + return acc; +}, {} as Record); + +export async function addMessage(message: string) { + // Send the message to the LLM + messages.push(new HumanMessage(message)); + + const firstResponse = await llmWithTools.invoke(messages); + + // Check if the response contains a tool call + if (firstResponse.tool_calls && firstResponse.tool_calls.length > 0) { + messages.push(firstResponse); + for (const toolCall of firstResponse.tool_calls) { + const selectedTool = toolsByName[toolCall.name]; + const toolMessage = await selectedTool.invoke(toolCall); + messages.push(toolMessage); + } + + // Add the final response to the messages + const finalResponse = await llmWithTools.invoke(messages); + messages.push(finalResponse); + + console.log(messages); + return JSON.stringify({ + type: "response", + message: finalResponse.content, + }); + } else { + console.log(messages); + messages.push(firstResponse); + return JSON.stringify({ + type: "response", + message: firstResponse.content, + }); + } +} diff --git a/src/lc/tools.ts b/src/lc/tools.ts new file mode 100644 index 0000000..6c4eb4f --- /dev/null +++ b/src/lc/tools.ts @@ -0,0 +1,156 @@ +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; +import { io, pendingConfirmation } from "../main.ts"; + +const orders = [ + { + id: 1, + name: "Order 1", + status: "pending", + items: [ + { id: 1, name: "Item 1", quantity: 2 }, + { id: 2, name: "Item 2", quantity: 1 }, + ], + total: 20.0, + createdAt: new Date(), + updatedAt: new Date(), + }, +]; + +const availableItems = [ + { id: 1, name: "Hamburguer", price: 10.0 }, + { id: 2, name: "Pizza", price: 5.0 }, + { id: 3, name: "Salada", price: 15.0 }, +]; + +export const cancelOrder = tool( + ({ orderId }: { orderId: number }) => { + const orderIndex = orders.findIndex((order) => order.id === orderId); + if (orderIndex === -1) { + return `Pedido não encontrado.`; + } + + orders.splice(orderIndex, 1); + return `Pedido ${orderId} cancelado com sucesso!`; + }, + { + name: "cancelarPedido", + description: "Cancela um pedido existente.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja cancelar."), + }), + }, +); + +export const createOrder = tool( + async ({ items }: { + items: { + name: string; + quantity: number; + observation?: string; + }[]; + }) => { + // Parse the items from the input, the input is a string with the item names + const parsedItems = items.map((itemName) => { + const item = availableItems.find((i) => + i.name.toLowerCase() === itemName.name.toLowerCase() + ); + if (item) { + return { + id: item.id, + name: item.name, + quantity: itemName.quantity, + price: item.price, + observation: itemName.observation, + }; + } else { + return null; + } + }).filter((item) => item !== null); + + if (parsedItems.length === 0) { + return `Nenhum item encontrado.`; + } + + const newOrder = { + id: orders.length + 1, + name: `Order ${orders.length + 1}`, + status: "pending", + items: parsedItems, + total: parsedItems.reduce( + (acc, item) => acc + item.price * item.quantity, + 0, + ), + createdAt: new Date(), + updatedAt: new Date(), + }; + + io.emit( + "message", + JSON.stringify({ + type: "order", + order: newOrder, + }), + ); + + const isConfirmed = await new Promise((resolve) => { + pendingConfirmation.set(newOrder.id, { resolve }); + + setTimeout(() => { + if (pendingConfirmation.has(newOrder.id)) { + pendingConfirmation.delete(newOrder.id); + resolve(false); + } + }, 30000); + }) + + if (isConfirmed) { + orders.push(newOrder); + + io.emit( + "new_order", + JSON.stringify({ + newOrder + }), + ); + + return `Pedido ${newOrder.id} criado com sucesso!`; + } else { + return `Pedido ${newOrder.id} não confirmado.`; + } + }, + { + name: "criarPedido", + description: "Cria um novo pedido com os itens especificados.", + schema: z.object({ + items: z.array( + z.object({ + name: z.string().describe("Nome do item"), + quantity: z.number().describe("Quantidade do determinado item"), + observation: z.string().optional().describe( + "Observações sobre o item", + ), + }), + ).describe( + "Uma lista contendo as informações de cada item, como: nome do item, quantidade desse item e alguma possível observação", + ), + }), + }, +); + +export const getOrder = tool( + ({ orderId }: { orderId: number }) => { + const order = orders.find((order) => order.id === orderId); + if (!order) { + return `Pedido não encontrado.`; + } + return `Pedido encontrado: ${JSON.stringify(order)}`; + }, + { + name: "verPedido", + description: "Verifica o status de um pedido.", + schema: z.object({ + orderId: z.number().describe("ID do pedido que deseja verificar."), + }), + }, +); diff --git a/src/main.ts b/src/main.ts index d68df84..4ed91e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,126 @@ +import cookieParser from "cookie-parser"; import express from "express"; +import { createServer } from "node:http"; +import cors from "npm:cors"; +import { Server } from "npm:socket.io"; +import { MockDatabase } from "./database/MockDatabase.ts"; +import { addMessage } from "./lc/model.ts"; +import { ValidateJWT } from "./middlewares/ValidateJWT.ts"; +import { AuthenticationService } from "./services/AuthenticationService.ts"; const app = express(); +// Instantiate the services +const db = new MockDatabase(); +const authenticationService = new AuthenticationService(db); +const JWTmiddleware = new ValidateJWT(authenticationService); + +app.use(cookieParser()) +app.use(express.json()); + +app.use(cors({ + origin: "*", +})); + +export const wsServer = createServer(app); +export const io = new Server(wsServer, { + cors: { + origin: "*", + }, +}); + app.get("/", (_req, res) => { res.send("Welcome to the Dinosaur API!"); }); -app.listen(8000); +app.post("/auth/register", async (req, res) => { + const { name, email, document, password } = req.body; + try { + const userId = await authenticationService.registerUser( + { id: 123, name, email, document, createdAt: new Date(), updatedAt: new Date() }, + password, + ); + + const token = await authenticationService.createJWT(userId); + + res.cookie("token", token, { + httpOnly: true, + secure: false, + sameSite: "strict", + }); + + res.status(201).send(); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + + res.status(400).json({ error: error.message }); + } +}); + +app.post("/auth/login", async (req, res) => { + const { email, password } = req.body; + try { + const userId = await authenticationService.loginUser(email, password); + if (!userId) { + res.status(401).json({ error: "Invalid email or password" }); + return; + } + + const token = await authenticationService.createJWT(userId.id); + res.cookie("token", token, { + httpOnly: true, + secure: false, + sameSite: "strict", + }); + + res.status(200).send(); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + + res.status(401).json({ error: "Email ou senha inválidos." }); + } +}); + +app.get("/hidden", JWTmiddleware.validateToken, (_req, res) => { + res.status(200).json({ message: "This is a hidden route" }); +}); + +// Handle messages from chat +export const pendingConfirmation = new Map(); + +io.on("connection", (socket) => { + socket.on("message", async (e) => { + await addMessage(e).then((final) => { + socket.emit("message", final); + }); + }); + + socket.on("order_confirmation", (e) => { + const { orderId, type } = JSON.parse(e); + if (pendingConfirmation.has(orderId)) { + const { resolve } = pendingConfirmation.get(orderId); + pendingConfirmation.delete(orderId); + if (type === "confirm") { + resolve(true); + } else { + resolve(false); + } + } + }); + + socket.emit( + "message", + JSON.stringify({ + type: "welcome", + message: + "Olá, tudo bem ?, bem vindo ao PoliEats! Sou um assistente virtual e estou aqui para te ajudar com o que você precisar. Você pode me perguntar sobre o cardápio, horários de funcionamento, fazer pedidos e consultar o status dos pedidos em andamento. Como posso te ajudar hoje? 🤗", + }), + ); +}); + +wsServer.listen(8000); console.log(`Server is running on http://localhost:8000`); diff --git a/src/middlewares/ValidateJWT.ts b/src/middlewares/ValidateJWT.ts new file mode 100644 index 0000000..13ba4b6 --- /dev/null +++ b/src/middlewares/ValidateJWT.ts @@ -0,0 +1,28 @@ +import { NextFunction, Request, Response } from "express"; +import { AuthenticationService } from "../services/AuthenticationService.ts"; + +export class ValidateJWT { + private authService: AuthenticationService; + + constructor(authService: AuthenticationService) { + this.authService = authService; + this.validateToken = this.validateToken.bind(this); + } + + async validateToken(req: Request, res: Response, next: NextFunction) { + const token = req.cookies.token; + + if (!token) { + res.status(401).json({ error: "Unauthorized" }); + return; + } + + await this.authService.verifyJWT(token) + .then(() => { + next(); + }) + .catch(() => { + res.status(401).json({ error: "Invalid token" }); + }); + } +} \ No newline at end of file diff --git a/src/mocks/User.ts b/src/mocks/User.ts new file mode 100644 index 0000000..2b9145c --- /dev/null +++ b/src/mocks/User.ts @@ -0,0 +1,28 @@ +import { Faker, pt_BR } from "@faker-js/faker"; +import { IUser } from "../interfaces/IUser.ts"; + +export function generateMockUser(): IUser { + const faker = new Faker({ + locale: pt_BR, + }) + + return { + id: faker.number.int({ min: 1, max: 1000 }), + name: faker.person.fullName(), + email: faker.internet.email(), + document: faker.string.numeric(11), + createdAt: faker.date.past(), + updatedAt: faker.date.recent(), + }; +} + +export function generateMockPassword(): string { + const faker = new Faker({ + locale: pt_BR, + }) + + return faker.internet.password({ + length: 8, + memorable: true, + }); +} \ No newline at end of file diff --git a/src/services/AuthenticationService.ts b/src/services/AuthenticationService.ts new file mode 100644 index 0000000..3969ebf --- /dev/null +++ b/src/services/AuthenticationService.ts @@ -0,0 +1,122 @@ +import bcrypt from "bcrypt"; +import { JWTPayload, jwtVerify, SignJWT } from "jose"; +import { JWSInvalid } from "jose/errors"; +import { password as passwordTable, salt as saltTable, user as userTable } from "../database/schema.ts"; +import { IDatabase } from "../interfaces/IDatabase.ts"; +import { IUser } from "../interfaces/IUser.ts"; + +export class AuthenticationService { + private db: IDatabase; + private secretKey: Uint8Array; + + constructor(db: IDatabase) { + this.db = db; + + this.secretKey = new TextEncoder().encode("poli_eats"); + } + + async registerUser(user: IUser, password: string): Promise { + const existingUser = await this.db.selectByField(userTable, "email", user.email); + if (existingUser.length > 0) { + throw new Error("User already exists"); + } + + const newUser = await this.db.insert(userTable, user); + + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + await this.db.insert(passwordTable, { + userId: newUser.id, + password: hashedPassword, + }); + + await this.db.insert(saltTable, { + userId: newUser.id, + salt: salt, + }); + return newUser.id; + } + + async createJWT(userId: number): Promise { + const user = await this.getUserById(userId); + + if (!user) { + throw new Error("User not found"); + } + + const payload = { + id: user.id, + name: user.name, + }; + + const jwt = await new SignJWT(payload) + .setProtectedHeader({ alg: "HS256" }) + .setIssuedAt() + .setExpirationTime("2h") + .sign(this.secretKey); + + return jwt; + } + + async verifyJWT(token: string): Promise { + try { + const { payload } = await jwtVerify(token, this.secretKey); + return payload; + } catch (error: unknown) { + return error instanceof JWSInvalid ? null : Promise.reject(error); + } + } + + async loginUser(email: string, password: string): Promise { + const user = await this.db.selectByField(userTable, "email", email); + + if (!user) { + throw new Error("User not found"); + } + + const userPassword = await this.db.selectByField(passwordTable, "userId", user[0].id); + if (!userPassword) { + throw new Error("User password not found"); + } + + const salt = await this.db.selectByField(saltTable, "userId", user[0].id); + + if (!salt) { + throw new Error("User salt not found"); + } + + const hashedPassword = await bcrypt.hash(password, salt[0].salt); + + if (userPassword[0].password !== hashedPassword) { + throw new Error("Invalid password"); + } + + return user[0]; + } + + async updateUser(id: number, user: IUser): Promise { + const existingUser = await this.db.select(userTable, id); + if (!existingUser) { + throw new Error("User not found"); + } + + const updatedUser = await this.db.update(userTable, id, user); + return updatedUser.id; + } + + async deleteUser(id: number): Promise { + const existingUser = await this.db.select(userTable, id); + if (!existingUser) { + throw new Error("User not found"); + } + + const deleted = await this.db.delete(userTable, id); + return deleted; + } + + async getUserById(id: number): Promise { + const user = await this.db.select(userTable, id); + return user; + } +} \ No newline at end of file diff --git a/tests/authentication.test.ts b/tests/authentication.test.ts new file mode 100644 index 0000000..9ba7a86 --- /dev/null +++ b/tests/authentication.test.ts @@ -0,0 +1,162 @@ +import { JWSInvalid } from "jose/errors"; +import { expect } from "jsr:@std/expect"; +import { describe, it } from "jsr:@std/testing/bdd"; +import { MockDatabase } from "../src/database/MockDatabase.ts"; +import { generateMockPassword, generateMockUser } from "../src/mocks/User.ts"; +import { AuthenticationService } from "../src/services/AuthenticationService.ts"; + +describe("User authentication", () => { + it("should register a new user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + const userId = await authService.registerUser(user, password); + const registeredUser = await authService.getUserById(userId); + + expect(registeredUser).toBeDefined(); + expect(registeredUser?.name).toBe(user.name); + expect(registeredUser?.email).toBe(user.email); + expect(registeredUser?.document).toBe(user.document); + expect(registeredUser?.createdAt).toBeDefined(); + expect(registeredUser?.updatedAt).toBeDefined(); + }); + + it("should not register a user with an existing email", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + await authService.registerUser(user, password); + + try { + await authService.registerUser(user, password); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + expect(error.message).toBe("User already exists"); + } + }); + + it("should login a user with valid credentials", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + await authService.registerUser(user, password); + + const loggedInUser = await authService.loginUser(user.email, password); + + expect(loggedInUser).toBeDefined(); + expect(loggedInUser?.email).toBe(user.email); + }); + + it("should not login a user with invalid credentials", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + await authService.registerUser(user, password); + + try { + await authService.loginUser(user.email, "wrongpassword"); + } catch (error: unknown) { + if (!(error instanceof Error)) { + throw error; + } + expect(error.message).toBe("Invalid password"); + } + }); + + it("should update an existing user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + const userId = await authService.registerUser(user, password); + + const updatedUser = { ...user, name: "Updated Name" }; + await authService.updateUser(userId, updatedUser); + + const fetchedUser = await authService.getUserById(userId); + + expect(fetchedUser).toBeDefined(); + expect(fetchedUser?.name).toBe(updatedUser.name); + }); + + it("should delete an existing user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + const userId = await authService.registerUser(user, password); + + const deleted = await authService.deleteUser(userId); + + expect(deleted).toBe(true); + + const fetchedUser = await authService.getUserById(userId); + + expect(fetchedUser).toBeNull(); + }); + + it("should generate a JWT token for a user", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + const userId = await authService.registerUser(user, password); + + const token = await authService.createJWT(userId); + + expect(token).toBeDefined(); + }); + + it("should verify a valid JWT token", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const user = generateMockUser(); + const password = generateMockPassword(); + + const userId = await authService.registerUser(user, password); + + const token = await authService.createJWT(userId); + + const payload = await authService.verifyJWT(token); + + expect(payload).toBeDefined(); + expect(payload?.id).toBe(userId); + }); + + it("should not verify an invalid JWT token", async () => { + const db = new MockDatabase(); + const authService = new AuthenticationService(db); + + const invalidToken = "invalidtoken"; + + try { + await authService.verifyJWT(invalidToken); + } catch (error: unknown) { + if (!(error instanceof JWSInvalid)) { + throw error; + } + expect(error.code).toBe("ERR_JWS_INVALID"); + } + }); +}) \ No newline at end of file diff --git a/tests/routes.test.ts b/tests/routes.test.ts new file mode 100644 index 0000000..7d1eab9 --- /dev/null +++ b/tests/routes.test.ts @@ -0,0 +1,142 @@ +import { expect } from "jsr:@std/expect/expect"; +import { describe, it } from "jsr:@std/testing/bdd"; +import request from "supertest"; +import { wsServer } from "../src/main.ts"; +import { generateMockPassword, generateMockUser } from "../src/mocks/User.ts"; + +describe("POST /auth/register", () => { + it("should register a new user", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + expect(res.status).toBe(201); + }); + + it("should not register a user with an existing email", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + expect(res.status).toBe(400); + }); + + it("should not register a user with missing fields", async () => { + const res = await request(wsServer) + .post("/auth/register") + .send({ + name: "John Doe", + email: "test@test.com", + document: "12345678901", + }) + expect(res.status).toBe(400); + }); +}) + +describe("POST /auth/login", () => { + it("should login a user with valid credentials", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: password, + }) + + expect(res.status).toBe(200); + }); + + it("should not login a user with invalid credentials", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const res = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: "wrongpassword", + }) + expect(res.status).toBe(401); + }); + + it("should be able to see hidden content with a valid JWT", async () => { + const newUser = generateMockUser(); + const password = generateMockPassword(); + + await request(wsServer) + .post("/auth/register") + .send({ + name: newUser.name, + email: newUser.email, + document: newUser.document, + password: password, + }) + + const loginRes = await request(wsServer) + .post("/auth/login") + .send({ + email: newUser.email, + password: password, + }) + + const token = loginRes.headers["set-cookie"][0].split("=")[1].split(";")[0]; + + const res = await request(wsServer) + .get("/hidden") + .set("Cookie", `token=${token}`) + + expect(res.status).toBe(200); + }); + + it("should not be able to see hidden content without a valid JWT", async () => { + const res = await request(wsServer) + .get("/hidden") + + expect(res.status).toBe(401); + }); +}); \ No newline at end of file