From d99b4c2c44613c38f624d419d008c3745a314bdf Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Fri, 4 Jul 2025 21:13:47 -0300 Subject: [PATCH 1/8] =?UTF-8?q?WIP:=20Funcionalidade=20de=20cadastro=20de?= =?UTF-8?q?=20usu=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- biome.json | 3 +- docker-compose.yml | 12 + package.json | 21 +- pnpm-lock.yaml | 779 +++++++++++++++++- .../20250704005931_create_orgs/migration.sql | 21 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 29 + src/env.ts | 1 + .../controllers/orgs/createOrgsController.ts | 39 + src/http/controllers/orgs/orgsRoutes.ts | 6 + src/http/routes.ts | 8 + src/http/server.ts | 22 + src/libs/prisma.ts | 6 + src/repositories/IOrgsRepository.ts | 7 + .../in-memory/orgsRepositoryInMemory.ts | 43 + .../prisma/orgsRepositoryPrisma.ts | 31 + src/use-cases/errors/orgAlreadyExistsError.ts | 5 + src/use-cases/orgs/createOrgsUseCase.spec.ts | 92 +++ src/use-cases/orgs/createOrgsUseCase.ts | 51 ++ vite.config.ts | 6 + 21 files changed, 1181 insertions(+), 9 deletions(-) create mode 100644 docker-compose.yml create mode 100644 prisma/migrations/20250704005931_create_orgs/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 src/http/controllers/orgs/createOrgsController.ts create mode 100644 src/http/controllers/orgs/orgsRoutes.ts create mode 100644 src/http/routes.ts create mode 100644 src/libs/prisma.ts create mode 100644 src/repositories/IOrgsRepository.ts create mode 100644 src/repositories/in-memory/orgsRepositoryInMemory.ts create mode 100644 src/repositories/prisma/orgsRepositoryPrisma.ts create mode 100644 src/use-cases/errors/orgAlreadyExistsError.ts create mode 100644 src/use-cases/orgs/createOrgsUseCase.spec.ts create mode 100644 src/use-cases/orgs/createOrgsUseCase.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 71e430a..579557b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,7 @@ coverage/ # Misc *.local -*.cache \ No newline at end of file +*.cache + +#prisma +prisma/generated/prisma/ diff --git a/biome.json b/biome.json index 08ce118..589f33d 100644 --- a/biome.json +++ b/biome.json @@ -30,7 +30,8 @@ "semicolons": "always", "arrowParentheses": "asNeeded", "jsxQuoteStyle": "double", - "trailingCommas": "es5" + "trailingCommas": "es5", + "bracketSameLine": true } } } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9b06e85 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + pg: + image: bitnami/postgresql:latest + ports: + - '5432:5432' + environment: + - POSTGRES_USER=docker + - POSTGRES_PASSWORD=docker + - POSTGRES_DB=api-findAFriend + \ No newline at end of file diff --git a/package.json b/package.json index 8c349e9..e0c0842 100644 --- a/package.json +++ b/package.json @@ -4,27 +4,42 @@ "description": "", "main": "index.js", "scripts": { - "dev": "tsx watch src/http/server.ts", + "dev": "tsx watch --env-file .env src/http/server.ts", "start": "node build/server.js", "build": "tsup src --out-dir build", "check": "biome check", "format": "biome format", - "lint": "biome lint" + "lint": "biome lint", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:ui": "vitest --ui", + "db:generate": "prisma generate", + "db:migrate:dev": "prisma migrate dev", + "db:studio": "prisma studio" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@fastify/cors": "^11.0.1", + "@prisma/client": "6.10.1", + "@types/bcryptjs": "^3.0.0", + "bcryptjs": "^3.0.2", "fastify": "^5.4.0", "zod": "^3.25.67" }, "devDependencies": { "@types/node": "^24.0.4", + "@vitest/coverage-v8": "3.2.4", + "@vitest/ui": "3.2.4", "biome": "^0.3.3", + "prisma": "^6.10.1", "tsup": "^8.5.0", "tsx": "^4.20.3", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" }, "packageManager": "pnpm@10.11.1", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4331b2..5d75d35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,15 @@ importers: '@fastify/cors': specifier: ^11.0.1 version: 11.0.1 + '@prisma/client': + specifier: 6.10.1 + version: 6.10.1(prisma@6.10.1(typescript@5.8.3))(typescript@5.8.3) + '@types/bcryptjs': + specifier: ^3.0.0 + version: 3.0.0 + bcryptjs: + specifier: ^3.0.2 + version: 3.0.2 fastify: specifier: ^5.4.0 version: 5.4.0 @@ -21,21 +30,61 @@ importers: '@types/node': specifier: ^24.0.4 version: 24.0.4 + '@vitest/coverage-v8': + specifier: 3.2.4 + version: 3.2.4(vitest@3.2.4) + '@vitest/ui': + specifier: 3.2.4 + version: 3.2.4(vitest@3.2.4) biome: specifier: ^0.3.3 version: 0.3.3 + prisma: + specifier: ^6.10.1 + version: 6.10.1(typescript@5.8.3) tsup: specifier: ^8.5.0 - version: 8.5.0(tsx@4.20.3)(typescript@5.8.3) + version: 8.5.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) tsx: specifier: ^4.20.3 version: 4.20.3 typescript: specifier: ^5.8.3 version: 5.8.3 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.0.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -211,6 +260,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -233,6 +286,39 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@prisma/client@6.10.1': + resolution: {integrity: sha512-Re4pMlcUsQsUTAYMK7EJ4Bw2kg3WfZAAlr8GjORJaK4VOP6LxRQUQ1TuLnxcF42XqGkWQ36q5CQF1yVadANQ6w==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.10.1': + resolution: {integrity: sha512-kz4/bnqrOrzWo8KzYguN0cden4CzLJJ+2VSpKtF8utHS3l1JS0Lhv6BLwpOX6X9yNreTbZQZwewb+/BMPDCIYQ==} + + '@prisma/debug@6.10.1': + resolution: {integrity: sha512-k2YT53cWxv9OLjW4zSYTZ6Z7j0gPfCzcr2Mj99qsuvlxr8WAKSZ2NcSR0zLf/mP4oxnYG842IMj3utTgcd7CaA==} + + '@prisma/engines-version@6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c': + resolution: {integrity: sha512-ZJFTsEqapiTYVzXya6TUKYDFnSWCNegfUiG5ik9fleQva5Sk3DNyyUi7X1+0ZxWFHwHDr6BZV5Vm+iwP+LlciA==} + + '@prisma/engines@6.10.1': + resolution: {integrity: sha512-Q07P5rS2iPwk2IQr/rUQJ42tHjpPyFcbiH7PXZlV81Ryr9NYIgdxcUrwgVOWVm5T7ap02C0dNd1dpnNcSWig8A==} + + '@prisma/fetch-engine@6.10.1': + resolution: {integrity: sha512-clmbG/Jgmrc/n6Y77QcBmAUlq9LrwI9Dbgy4pq5jeEARBpRCWJDJ7PWW1P8p0LfFU0i5fsyO7FqRzRB8mkdS4g==} + + '@prisma/get-platform@6.10.1': + resolution: {integrity: sha512-4CY5ndKylcsce9Mv+VWp5obbR2/86SHOLVV053pwIkhVtT9C9A83yqiqI/5kJM9T1v1u1qco/bYjDKycmei9HA==} + '@rollup/rollup-android-arm-eabi@4.44.0': resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} cpu: [arm] @@ -333,12 +419,65 @@ packages: cpu: [x64] os: [win32] + '@types/bcryptjs@3.0.0': + resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} + deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/node@24.0.4': resolution: {integrity: sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==} + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/ui@3.2.4': + resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} + peerDependencies: + vitest: 3.2.4 + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -399,6 +538,13 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@0.3.3: + resolution: {integrity: sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -421,6 +567,10 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bcryptjs@3.0.2: + resolution: {integrity: sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==} + hasBin: true + biome@0.3.3: resolution: {integrity: sha512-4LXjrQYbn9iTXu9Y4SKT7ABzTV0WnLDHCVSd2fPUOKsy1gQ+E4xPFmlY1zcWexoi0j7fGHItlL6OWA2CZ/yYAQ==} hasBin: true @@ -447,10 +597,18 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -522,6 +680,10 @@ packages: supports-color: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -548,6 +710,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -557,10 +722,17 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + exit-hook@1.1.1: resolution: {integrity: sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==} engines: {node: '>=0.10.0'} + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -607,6 +779,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@1.7.0: resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} engines: {node: '>=0.10.0'} @@ -618,6 +793,9 @@ packages: fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -658,6 +836,9 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -674,6 +855,13 @@ packages: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-signature@1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} @@ -712,13 +900,36 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -773,12 +984,22 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -801,6 +1022,10 @@ packages: mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -810,6 +1035,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + number-is-nan@1.0.1: resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} engines: {node: '>=0.10.0'} @@ -854,6 +1084,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} @@ -899,6 +1133,20 @@ packages: yaml: optional: true + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prisma@6.10.1: + resolution: {integrity: sha512-khhlC/G49E4+uyA3T3H5PRBut486HD2bDqE2+rvkU0pwk9IAqGFacLFUyIx9Uw+W2eCtf6XGwsp+/strUwMNPw==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + process-warning@4.0.1: resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} @@ -1017,13 +1265,24 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -1037,6 +1296,12 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + string-width@1.0.2: resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} engines: {node: '>=0.10.0'} @@ -1061,6 +1326,9 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1070,6 +1338,14 @@ packages: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1083,6 +1359,9 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -1090,10 +1369,26 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + toad-cache@3.7.0: resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} engines: {node: '>=12'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@2.5.0: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} @@ -1108,6 +1403,16 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsup@8.5.0: resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} engines: {node: '>=18'} @@ -1169,6 +1474,87 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@7.0.1: + resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -1180,6 +1566,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1191,11 +1582,36 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.0 + + '@babel/types@7.28.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@1.0.2': {} + '@esbuild/aix-ppc64@0.25.5': optional: true @@ -1308,6 +1724,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -1328,6 +1746,38 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + + '@prisma/client@6.10.1(prisma@6.10.1(typescript@5.8.3))(typescript@5.8.3)': + optionalDependencies: + prisma: 6.10.1(typescript@5.8.3) + typescript: 5.8.3 + + '@prisma/config@6.10.1': + dependencies: + jiti: 2.4.2 + + '@prisma/debug@6.10.1': {} + + '@prisma/engines-version@6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c': {} + + '@prisma/engines@6.10.1': + dependencies: + '@prisma/debug': 6.10.1 + '@prisma/engines-version': 6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c + '@prisma/fetch-engine': 6.10.1 + '@prisma/get-platform': 6.10.1 + + '@prisma/fetch-engine@6.10.1': + dependencies: + '@prisma/debug': 6.10.1 + '@prisma/engines-version': 6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c + '@prisma/get-platform': 6.10.1 + + '@prisma/get-platform@6.10.1': + dependencies: + '@prisma/debug': 6.10.1 + '@rollup/rollup-android-arm-eabi@4.44.0': optional: true @@ -1388,12 +1838,94 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.44.0': optional: true + '@types/bcryptjs@3.0.0': + dependencies: + bcryptjs: 3.0.2 + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/node@24.0.4': dependencies: undici-types: 7.8.0 + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.3 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.0.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/ui@3.2.4(vitest@3.2.4)': + dependencies: + '@vitest/utils': 3.2.4 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.14 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.0.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + abstract-logging@2.0.1: {} acorn@8.15.0: {} @@ -1440,6 +1972,14 @@ snapshots: assert-plus@1.0.0: {} + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -1459,6 +1999,8 @@ snapshots: dependencies: tweetnacl: 0.14.5 + bcryptjs@3.0.2: {} + biome@0.3.3: dependencies: bluebird: 3.7.2 @@ -1491,6 +2033,14 @@ snapshots: caseless@0.12.0: {} + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.1 + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -1499,6 +2049,8 @@ snapshots: strip-ansi: 3.0.1 supports-color: 2.0.0 + check-error@2.1.1: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -1551,6 +2103,8 @@ snapshots: dependencies: ms: 2.1.3 + deep-eql@5.0.2: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -1575,6 +2129,8 @@ snapshots: emoji-regex@9.2.2: {} + es-module-lexer@1.7.0: {} + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -1605,8 +2161,14 @@ snapshots: escape-string-regexp@1.0.5: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + exit-hook@1.1.1: {} + expect-type@1.2.1: {} + extend@3.0.2: {} extsprintf@1.3.0: {} @@ -1662,6 +2224,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + figures@1.7.0: dependencies: escape-string-regexp: 1.0.5 @@ -1679,6 +2243,8 @@ snapshots: mlly: 1.7.4 rollup: 4.44.0 + flatted@3.3.3: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -1738,6 +2304,8 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + globrex@0.1.2: {} + graceful-fs@4.2.11: {} har-schema@2.0.0: {} @@ -1751,6 +2319,10 @@ snapshots: dependencies: ansi-regex: 2.1.1 + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + http-signature@1.2.0: dependencies: assert-plus: 1.0.0 @@ -1799,14 +2371,39 @@ snapshots: isstream@0.1.2: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jiti@2.4.2: {} + joycon@3.1.1: {} + js-tokens@9.0.1: {} + jsbn@0.1.1: {} json-schema-ref-resolver@2.0.1: @@ -1858,12 +2455,24 @@ snapshots: lodash@4.17.21: {} + loupe@3.1.4: {} + lru-cache@10.4.3: {} magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + mime-db@1.52.0: {} mime-types@2.1.35: @@ -1887,6 +2496,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + mrmime@2.0.1: {} + ms@2.1.3: {} mute-stream@0.0.5: {} @@ -1897,6 +2508,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.11: {} + number-is-nan@1.0.1: {} oauth-sign@0.9.0: {} @@ -1926,6 +2539,8 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + performance-now@2.1.0: {} picocolors@1.1.1: {} @@ -1960,11 +2575,27 @@ snapshots: mlly: 1.7.4 pathe: 2.0.3 - postcss-load-config@6.0.1(tsx@4.20.3): + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(yaml@2.8.0): dependencies: lilconfig: 3.1.3 optionalDependencies: + jiti: 2.4.2 + postcss: 8.5.6 tsx: 4.20.3 + yaml: 2.8.0 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prisma@6.10.1(typescript@5.8.3): + dependencies: + '@prisma/config': 6.10.1 + '@prisma/engines': 6.10.1 + optionalDependencies: + typescript: 5.8.3 process-warning@4.0.1: {} @@ -2096,12 +2727,22 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 @@ -2120,6 +2761,10 @@ snapshots: safer-buffer: 2.1.2 tweetnacl: 0.14.5 + stackback@0.0.2: {} + + std-env@3.9.0: {} + string-width@1.0.2: dependencies: code-point-at: 1.1.0 @@ -2150,6 +2795,10 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -2162,6 +2811,16 @@ snapshots: supports-color@2.0.0: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -2176,6 +2835,8 @@ snapshots: through@2.3.8: {} + tinybench@2.9.0: {} + tinyexec@0.3.2: {} tinyglobby@0.2.14: @@ -2183,8 +2844,16 @@ snapshots: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + toad-cache@3.7.0: {} + totalist@3.0.1: {} + tough-cookie@2.5.0: dependencies: psl: 1.15.0 @@ -2198,7 +2867,11 @@ snapshots: ts-interface-checker@0.1.13: {} - tsup@8.5.0(tsx@4.20.3)(typescript@5.8.3): + tsconfck@3.1.6(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + + tsup@8.5.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0): dependencies: bundle-require: 5.1.0(esbuild@0.25.5) cac: 6.7.14 @@ -2209,7 +2882,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(tsx@4.20.3) + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(yaml@2.8.0) resolve-from: 5.0.0 rollup: 4.44.0 source-map: 0.8.0-beta.0 @@ -2218,6 +2891,7 @@ snapshots: tinyglobby: 0.2.14 tree-kill: 1.2.2 optionalDependencies: + postcss: 8.5.6 typescript: 5.8.3 transitivePeerDependencies: - jiti @@ -2262,6 +2936,95 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 + vite-node@3.2.4(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)): + dependencies: + debug: 4.4.1 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.8.3) + optionalDependencies: + vite: 7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + - typescript + + vite@7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.4 + fsevents: 2.3.3 + jiti: 2.4.2 + tsx: 4.20.3 + yaml: 2.8.0 + + vitest@3.2.4(@types/node@24.0.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.1(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.0.4)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.0.4 + '@vitest/ui': 3.2.4(vitest@3.2.4) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + webidl-conversions@4.0.2: {} whatwg-url@7.1.0: @@ -2274,6 +3037,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -2288,4 +3056,7 @@ snapshots: wrappy@1.0.2: {} + yaml@2.8.0: + optional: true + zod@3.25.67: {} diff --git a/prisma/migrations/20250704005931_create_orgs/migration.sql b/prisma/migrations/20250704005931_create_orgs/migration.sql new file mode 100644 index 0000000..a72a89a --- /dev/null +++ b/prisma/migrations/20250704005931_create_orgs/migration.sql @@ -0,0 +1,21 @@ +-- CreateTable +CREATE TABLE "orgs" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "author_name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "whatsapp" TEXT NOT NULL, + "password" TEXT NOT NULL, + "cep" TEXT NOT NULL, + "state" TEXT NOT NULL, + "city" TEXT NOT NULL, + "neighborhood" TEXT NOT NULL, + "street" TEXT NOT NULL, + "latitude" DECIMAL(65,30) NOT NULL, + "longitude" DECIMAL(65,30) NOT NULL, + + CONSTRAINT "orgs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "orgs_email_key" ON "orgs"("email"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..86bb614 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,29 @@ +generator client { + provider = "prisma-client-js" + output = "./generated/prisma" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Orgs { + id String @id @default(uuid()) + name String + author_name String + email String @unique + whatsapp String + password String + + cep String + state String + city String + neighborhood String + street String + + latitude Decimal + longitude Decimal + + @@map("orgs") +} diff --git a/src/env.ts b/src/env.ts index 2ef823a..093bb1f 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,6 +3,7 @@ import { z } from 'zod/v4'; const envSchema = z.object({ PORT: z.coerce.number().default(3000), NODE_ENV: z.enum(['dev', 'test', 'prod']).default('prod'), + DATABASE_URL: z.string() }); const _env = envSchema.safeParse(process.env) diff --git a/src/http/controllers/orgs/createOrgsController.ts b/src/http/controllers/orgs/createOrgsController.ts new file mode 100644 index 0000000..58bf1ef --- /dev/null +++ b/src/http/controllers/orgs/createOrgsController.ts @@ -0,0 +1,39 @@ +import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; +import { CreateOrgsUseCase } from "@/use-cases/orgs/createOrgsUseCase"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const createOrgsController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const createOrgsBodySchema = z.object({ + name: z.string(), + author_name: z.string(), + email: z.string(), + whatsapp: z.string(), + password: z.string(), + cep: z.string(), + state: z.string(), + city: z.string(), + neighborhood: z.string(), + street: z.string(), + latitude: z.number(), + longitude: z.number(), + }); + + const bodyData = createOrgsBodySchema.parse(request.body); + + const orgsRepository = new OrgsRepositoryPrisma() + + const orgWithSameEmail = await orgsRepository.findByEmail(bodyData.email) + if (orgWithSameEmail) + { + return reply.status(409).send() + } + + const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository) + createOrgsUseCase.execute(bodyData); + + reply.status(201).send(); +} diff --git a/src/http/controllers/orgs/orgsRoutes.ts b/src/http/controllers/orgs/orgsRoutes.ts new file mode 100644 index 0000000..e5f7ae8 --- /dev/null +++ b/src/http/controllers/orgs/orgsRoutes.ts @@ -0,0 +1,6 @@ +import { FastifyInstance } from "fastify"; +import { createOrgsController } from "./createOrgsController"; + +export const orgsRoutes = async (app: FastifyInstance) => { + app.post('/orgs', createOrgsController); +} \ No newline at end of file diff --git a/src/http/routes.ts b/src/http/routes.ts new file mode 100644 index 0000000..58ebba8 --- /dev/null +++ b/src/http/routes.ts @@ -0,0 +1,8 @@ +import { FastifyInstance } from "fastify"; +import { statusRoutes } from "./controllers/status/statusRouts"; +import { orgsRoutes } from "./controllers/orgs/orgsRoutes"; + +export const appRoutes = async (app: FastifyInstance) => { + app.register(statusRoutes); + app.register(orgsRoutes) +} \ No newline at end of file diff --git a/src/http/server.ts b/src/http/server.ts index b041200..654525b 100644 --- a/src/http/server.ts +++ b/src/http/server.ts @@ -2,12 +2,34 @@ import fastify from "fastify"; import { appRoutes } from "./routes"; import { fastifyCors } from '@fastify/cors'; import { env } from "@/env"; +import { ZodError } from "zod/v4"; export const app = fastify(); app.register(fastifyCors, { origin: '*' }); app.register(appRoutes); +app.setErrorHandler((error, _, reply) => { + if (error instanceof ZodError) + { + return reply.status(400).send({ + message: 'Validation error', + issues: error.format(), + }); + } + + if (env.NODE_ENV !== 'prod') + { + console.error(error); + } + else + { + // Envia o erro p/ alguma ferramenta de observabilidade (Sentry/DataDog/Grafana/OTel) + } + + return reply.status(500).send({ message: 'Internal server error.' }); +}); + app.listen({ port: env.PORT, host: '0.0.0.0' }).then(val => { console.log('HTTP Server running!'); diff --git a/src/libs/prisma.ts b/src/libs/prisma.ts new file mode 100644 index 0000000..bfccb9c --- /dev/null +++ b/src/libs/prisma.ts @@ -0,0 +1,6 @@ +import { env } from "@/env"; +import { PrismaClient } from "../../prisma/generated/prisma"; + +export const prisma = new PrismaClient({ + log: env.NODE_ENV === 'dev' ? ['query'] : [] +}); diff --git a/src/repositories/IOrgsRepository.ts b/src/repositories/IOrgsRepository.ts new file mode 100644 index 0000000..ae3538a --- /dev/null +++ b/src/repositories/IOrgsRepository.ts @@ -0,0 +1,7 @@ +import { Prisma, Orgs } from "prisma/generated/prisma" + +export interface IOrgsRepository { + findById(id: string): Promise + findByEmail(email: string): Promise + create(data: Prisma.OrgsCreateInput): Promise +} \ No newline at end of file diff --git a/src/repositories/in-memory/orgsRepositoryInMemory.ts b/src/repositories/in-memory/orgsRepositoryInMemory.ts new file mode 100644 index 0000000..1acab9f --- /dev/null +++ b/src/repositories/in-memory/orgsRepositoryInMemory.ts @@ -0,0 +1,43 @@ + + +import { randomUUID } from 'node:crypto' +import { IOrgsRepository } from '@/repositories/IOrgsRepository' +import { Orgs, Prisma } from 'prisma/generated/prisma' + +export class OrgsRepositoryInMemory implements IOrgsRepository { + public items: Orgs[] = [] + + async create(data: Prisma.OrgsCreateInput) { + const org: Orgs = { + ...data, + id: randomUUID(), + } + + this.items.push(org) + + return org; + } + + async findById(id: string) { + const org = this.items.find((item) => item.id === id) + + if (!org) + { + return null + } + + return org + } + + async findByEmail(email: string) { + const org = this.items.find((item) => item.email === email) + + if (!org) + { + return null + } + + return org + } + +} \ No newline at end of file diff --git a/src/repositories/prisma/orgsRepositoryPrisma.ts b/src/repositories/prisma/orgsRepositoryPrisma.ts new file mode 100644 index 0000000..4243ea0 --- /dev/null +++ b/src/repositories/prisma/orgsRepositoryPrisma.ts @@ -0,0 +1,31 @@ +import { prisma } from '@/libs/prisma'; +import { IOrgsRepository } from '../IOrgsRepository'; +import { Prisma, Orgs } from "prisma/generated/prisma" + +export class OrgsRepositoryPrisma implements IOrgsRepository { + async create(data: Prisma.OrgsCreateInput): Promise { + const ret = await prisma.orgs.create({ data }); + + return ret; + } + + async findByEmail(email: string): Promise { + const ret = await prisma.orgs.findUnique({ + where: { + email + } + }); + + return ret; + } + + async findById(id: string): Promise { + const ret = await prisma.orgs.findUnique({ + where: { + id + } + }); + + return ret; + } +} \ No newline at end of file diff --git a/src/use-cases/errors/orgAlreadyExistsError.ts b/src/use-cases/errors/orgAlreadyExistsError.ts new file mode 100644 index 0000000..06bcf60 --- /dev/null +++ b/src/use-cases/errors/orgAlreadyExistsError.ts @@ -0,0 +1,5 @@ +export class OrgAlreadyExistsError extends Error { + constructor() { + super('E-mail already exists.') + } +} \ No newline at end of file diff --git a/src/use-cases/orgs/createOrgsUseCase.spec.ts b/src/use-cases/orgs/createOrgsUseCase.spec.ts new file mode 100644 index 0000000..96c8727 --- /dev/null +++ b/src/use-cases/orgs/createOrgsUseCase.spec.ts @@ -0,0 +1,92 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { CreateOrgsUseCase } from './createOrgsUseCase'; +import { IOrgsRepository } from '@/repositories/IOrgsRepository'; +import { compare } from 'bcryptjs'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { OrgAlreadyExistsError } from '../errors/orgAlreadyExistsError'; + +describe("Caso de Uso: Criação de Organizações", () => { + + it("Não deve ser possivel cadastrar um email existente", async () => { + const orgsRepository = new OrgsRepositoryInMemory(); + const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); + + const input = { + name: "Org Test", + author_name: "John Doe", + email: "john@org.com", + whatsapp: "123456789", + password: "123456", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }; + + const { org } = await createOrgsUseCase.execute(input); + + expect( + org.id + ).toEqual(expect.any(String)); + }); + + it("A senha da Organização deve ser criptografada ao ser cadastrada", async () => { + const orgsRepository = new OrgsRepositoryInMemory(); + const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); + + const input = { + name: "Org Test", + author_name: "John Doe", + email: "john@org.com", + whatsapp: "123456789", + password: "123456", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }; + + const { org } = await createOrgsUseCase.execute(input); + + const isPasswordCorretlyHashed = await compare( + input.password, + org.password + ); + + expect(isPasswordCorretlyHashed).toBe(true) + }); + + it("Não deve ser possivel cadastrar um email existente", async () => { + const orgsRepository = new OrgsRepositoryInMemory(); + const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); + + const input = { + name: "Org Test", + author_name: "John Doe", + email: "john@org.com", + whatsapp: "123456789", + password: "123456", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }; + + await createOrgsUseCase.execute(input); + + await expect( + createOrgsUseCase.execute(input) + ).rejects.toBeInstanceOf(OrgAlreadyExistsError) + + }); + +}); diff --git a/src/use-cases/orgs/createOrgsUseCase.ts b/src/use-cases/orgs/createOrgsUseCase.ts new file mode 100644 index 0000000..8e1e119 --- /dev/null +++ b/src/use-cases/orgs/createOrgsUseCase.ts @@ -0,0 +1,51 @@ +import { IOrgsRepository } from "@/repositories/IOrgsRepository"; +import { hash } from "bcryptjs"; +import { Orgs } from "prisma/generated/prisma"; +import { OrgAlreadyExistsError } from "../errors/orgAlreadyExistsError"; + +interface CreateOrgsCaseRequest { + name: string + author_name: string + email: string + whatsapp: string + password: string + cep: string + state: string + city: string + neighborhood: string + street: string + latitude: number + longitude: number +} + +interface CreateOrgsCaseResponse { + org: Orgs +} + +export class CreateOrgsUseCase { + + constructor( + private orgsRepository: IOrgsRepository + ) { } + + async execute({ + password, + email, + ...data + }: CreateOrgsCaseRequest): Promise { + const orgByEmail = await this.orgsRepository.findByEmail(email) + if (orgByEmail) + { + throw new OrgAlreadyExistsError(); + } + const password_hash = await hash(password, 6); + + const org = await this.orgsRepository.create({ + ...data, + email, + password: password_hash, + }) + + return { org }; + } +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..9a92dda --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vitest/config'; +import tsConfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsConfigPaths()], +}); \ No newline at end of file From 37c2817b426900d861ea41dcf78b422f8a8d9374 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Sun, 6 Jul 2025 23:43:45 -0300 Subject: [PATCH 2/8] =?UTF-8?q?WIP:=20Funcionalidade=20de=20autentica?= =?UTF-8?q?=C3=A7=C3=A3o=20da=20organiza=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/orgs/authOrgsController.ts | 33 ++++++++ .../controllers/orgs/createOrgsController.ts | 22 +++--- src/http/controllers/orgs/orgsRoutes.ts | 2 + src/repositories/IOrgsRepository.ts | 2 +- .../in-memory/orgsRepositoryInMemory.ts | 6 ++ .../errors/invalidCredentialsError.ts | 5 ++ .../factories/makeAuthOrgsUseCase.ts | 9 +++ .../factories/makeCreateOrgsUseCase.ts | 9 +++ src/use-cases/orgs/authOrgsUseCase.spec.ts | 75 +++++++++++++++++++ src/use-cases/orgs/authOrgsUseCase.ts | 41 ++++++++++ src/use-cases/orgs/createOrgsUseCase.spec.ts | 36 ++++----- 11 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 src/http/controllers/orgs/authOrgsController.ts create mode 100644 src/use-cases/errors/invalidCredentialsError.ts create mode 100644 src/use-cases/factories/makeAuthOrgsUseCase.ts create mode 100644 src/use-cases/factories/makeCreateOrgsUseCase.ts create mode 100644 src/use-cases/orgs/authOrgsUseCase.spec.ts create mode 100644 src/use-cases/orgs/authOrgsUseCase.ts diff --git a/src/http/controllers/orgs/authOrgsController.ts b/src/http/controllers/orgs/authOrgsController.ts new file mode 100644 index 0000000..83d30af --- /dev/null +++ b/src/http/controllers/orgs/authOrgsController.ts @@ -0,0 +1,33 @@ +import { InvalidCredentialsError } from "@/use-cases/errors/invalidCredentialsError"; +import { makeAuthOrgsUseCase } from "@/use-cases/factories/makeAuthOrgsUseCase"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const authOrgsController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const authOrgsBodySchema = z.object({ + email: z.string(), + password: z.string(), + }); + + const bodyData = authOrgsBodySchema.parse(request.body); + + try + { + const authOrgsUseCase = makeAuthOrgsUseCase(); + authOrgsUseCase.execute(bodyData); + + } catch (error) + { + if (error instanceof InvalidCredentialsError) + { + return reply.status(400).send(error.message); + } + + throw error; + } + + reply.status(200).send(); +} diff --git a/src/http/controllers/orgs/createOrgsController.ts b/src/http/controllers/orgs/createOrgsController.ts index 58bf1ef..1a7f189 100644 --- a/src/http/controllers/orgs/createOrgsController.ts +++ b/src/http/controllers/orgs/createOrgsController.ts @@ -1,5 +1,5 @@ -import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; -import { CreateOrgsUseCase } from "@/use-cases/orgs/createOrgsUseCase"; +import { OrgAlreadyExistsError } from "@/use-cases/errors/orgAlreadyExistsError"; +import { makeCreateOrgsUseCase } from "@/use-cases/factories/makeCreateOrgsUseCase"; import { FastifyReply, FastifyRequest } from "fastify"; import { z } from "zod/v4"; @@ -24,16 +24,20 @@ export const createOrgsController = async ( const bodyData = createOrgsBodySchema.parse(request.body); - const orgsRepository = new OrgsRepositoryPrisma() + try + { + const createOrgsUseCase = makeCreateOrgsUseCase(); + createOrgsUseCase.execute(bodyData); - const orgWithSameEmail = await orgsRepository.findByEmail(bodyData.email) - if (orgWithSameEmail) + } catch (error) { - return reply.status(409).send() - } + if (error instanceof OrgAlreadyExistsError) + { + return reply.status(409).send(error.message); + } - const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository) - createOrgsUseCase.execute(bodyData); + throw error; + } reply.status(201).send(); } diff --git a/src/http/controllers/orgs/orgsRoutes.ts b/src/http/controllers/orgs/orgsRoutes.ts index e5f7ae8..134da3a 100644 --- a/src/http/controllers/orgs/orgsRoutes.ts +++ b/src/http/controllers/orgs/orgsRoutes.ts @@ -1,6 +1,8 @@ import { FastifyInstance } from "fastify"; import { createOrgsController } from "./createOrgsController"; +import { authOrgsController } from "./authOrgsController"; export const orgsRoutes = async (app: FastifyInstance) => { app.post('/orgs', createOrgsController); + app.post('/orgs/auth', authOrgsController); } \ No newline at end of file diff --git a/src/repositories/IOrgsRepository.ts b/src/repositories/IOrgsRepository.ts index ae3538a..b8317e3 100644 --- a/src/repositories/IOrgsRepository.ts +++ b/src/repositories/IOrgsRepository.ts @@ -1,7 +1,7 @@ import { Prisma, Orgs } from "prisma/generated/prisma" export interface IOrgsRepository { + create(data: Prisma.OrgsCreateInput): Promise findById(id: string): Promise findByEmail(email: string): Promise - create(data: Prisma.OrgsCreateInput): Promise } \ No newline at end of file diff --git a/src/repositories/in-memory/orgsRepositoryInMemory.ts b/src/repositories/in-memory/orgsRepositoryInMemory.ts index 1acab9f..039bb8f 100644 --- a/src/repositories/in-memory/orgsRepositoryInMemory.ts +++ b/src/repositories/in-memory/orgsRepositoryInMemory.ts @@ -11,6 +11,12 @@ export class OrgsRepositoryInMemory implements IOrgsRepository { const org: Orgs = { ...data, id: randomUUID(), + latitude: Prisma.Decimal( + data.latitude.toString() + ), + longitude: Prisma.Decimal( + data.longitude.toString() + ) } this.items.push(org) diff --git a/src/use-cases/errors/invalidCredentialsError.ts b/src/use-cases/errors/invalidCredentialsError.ts new file mode 100644 index 0000000..5e4dd95 --- /dev/null +++ b/src/use-cases/errors/invalidCredentialsError.ts @@ -0,0 +1,5 @@ +export class InvalidCredentialsError extends Error { + constructor() { + super('Invalid Credentials.') + } +} \ No newline at end of file diff --git a/src/use-cases/factories/makeAuthOrgsUseCase.ts b/src/use-cases/factories/makeAuthOrgsUseCase.ts new file mode 100644 index 0000000..f4b2813 --- /dev/null +++ b/src/use-cases/factories/makeAuthOrgsUseCase.ts @@ -0,0 +1,9 @@ +import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; +import { AuthOrgsUseCase } from "../orgs/authOrgsUseCase"; + +export function makeAuthOrgsUseCase() { + const orgsRepository = new OrgsRepositoryPrisma() + const createAuthUseCase = new AuthOrgsUseCase(orgsRepository) + + return createAuthUseCase; +} \ No newline at end of file diff --git a/src/use-cases/factories/makeCreateOrgsUseCase.ts b/src/use-cases/factories/makeCreateOrgsUseCase.ts new file mode 100644 index 0000000..aa398df --- /dev/null +++ b/src/use-cases/factories/makeCreateOrgsUseCase.ts @@ -0,0 +1,9 @@ +import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; +import { CreateOrgsUseCase } from "../orgs/createOrgsUseCase"; + +export function makeCreateOrgsUseCase() { + const orgsRepository = new OrgsRepositoryPrisma() + const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository) + + return createOrgsUseCase; +} \ No newline at end of file diff --git a/src/use-cases/orgs/authOrgsUseCase.spec.ts b/src/use-cases/orgs/authOrgsUseCase.spec.ts new file mode 100644 index 0000000..8cfeaca --- /dev/null +++ b/src/use-cases/orgs/authOrgsUseCase.spec.ts @@ -0,0 +1,75 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { AuthOrgsUseCase } from './authOrgsUseCase'; +import { hash } from 'bcryptjs'; +import { InvalidCredentialsError } from '../errors/invalidCredentialsError'; + +describe("Caso de Uso: Autenticação da Organizações", () => { + let orgsRepository: OrgsRepositoryInMemory; + let sut: AuthOrgsUseCase; + + beforeAll(() => { + orgsRepository = new OrgsRepositoryInMemory(); + sut = new AuthOrgsUseCase(orgsRepository); + }) + + it("Auntenticação com sucesso", async () => { + + await orgsRepository.create({ + email: 'teste@teste.com', + password: await hash('123456789', 6), + name: "Org Test", + author_name: "John Doe", + whatsapp: "99999999", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }); + + const { org } = await sut.execute({ + email: 'teste@teste.com', + password: '123456789' + }); + + expect( + org.id + ).toEqual(expect.any(String)); + }); + + it("Auntenticação com falha (e-mail incorreto)", async () => { + + await expect( + sut.execute({ + email: 'teste@teste.com', + password: '456789' + }) + ).rejects.toBeInstanceOf(InvalidCredentialsError); + }); + + it("Auntenticação com falha (senha incorreta)", async () => { + + await orgsRepository.create({ + email: 'teste@teste.com', + password: await hash('12346', 6), + name: "Org Test", + author_name: "John Doe", + whatsapp: "123456789", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }); + + await expect( + sut.execute({ email: 'teste@teste.com', password: '456789' }) + ).rejects.toBeInstanceOf(InvalidCredentialsError); + }); + +}); diff --git a/src/use-cases/orgs/authOrgsUseCase.ts b/src/use-cases/orgs/authOrgsUseCase.ts new file mode 100644 index 0000000..b05209d --- /dev/null +++ b/src/use-cases/orgs/authOrgsUseCase.ts @@ -0,0 +1,41 @@ +import { IOrgsRepository } from "@/repositories/IOrgsRepository"; +import { compare } from "bcryptjs"; +import { Orgs } from "prisma/generated/prisma"; +import { InvalidCredentialsError } from "../errors/invalidCredentialsError"; + +interface AuthOrgsCaseRequest { + email: string + password: string + +} + +interface AuthOrgsCaseResponse { + org: Orgs +} + +export class AuthOrgsUseCase { + + constructor( + private orgsRepository: IOrgsRepository + ) { } + + async execute({ email, password }: AuthOrgsCaseRequest): Promise { + const org = await this.orgsRepository.findByEmail(email) + + if (org === null) + { + throw new InvalidCredentialsError(); + } + + const doesPasswordMatches = await compare(password, org.password) + + if (!doesPasswordMatches) + { + throw new InvalidCredentialsError(); + } + + return { + org: org + } + } +} \ No newline at end of file diff --git a/src/use-cases/orgs/createOrgsUseCase.spec.ts b/src/use-cases/orgs/createOrgsUseCase.spec.ts index 96c8727..2a18402 100644 --- a/src/use-cases/orgs/createOrgsUseCase.spec.ts +++ b/src/use-cases/orgs/createOrgsUseCase.spec.ts @@ -1,16 +1,20 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { CreateOrgsUseCase } from './createOrgsUseCase'; -import { IOrgsRepository } from '@/repositories/IOrgsRepository'; import { compare } from 'bcryptjs'; import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; import { OrgAlreadyExistsError } from '../errors/orgAlreadyExistsError'; describe("Caso de Uso: Criação de Organizações", () => { - + let orgsRepository: OrgsRepositoryInMemory; + let sut: CreateOrgsUseCase; + + beforeEach(() => { + orgsRepository = new OrgsRepositoryInMemory(); + sut = new CreateOrgsUseCase(orgsRepository); + }) + it("Não deve ser possivel cadastrar um email existente", async () => { - const orgsRepository = new OrgsRepositoryInMemory(); - const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); - + const input = { name: "Org Test", author_name: "John Doe", @@ -26,7 +30,7 @@ describe("Caso de Uso: Criação de Organizações", () => { longitude: -46.6, }; - const { org } = await createOrgsUseCase.execute(input); + const { org } = await sut.execute(input); expect( org.id @@ -34,9 +38,7 @@ describe("Caso de Uso: Criação de Organizações", () => { }); it("A senha da Organização deve ser criptografada ao ser cadastrada", async () => { - const orgsRepository = new OrgsRepositoryInMemory(); - const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); - + const input = { name: "Org Test", author_name: "John Doe", @@ -52,19 +54,17 @@ describe("Caso de Uso: Criação de Organizações", () => { longitude: -46.6, }; - const { org } = await createOrgsUseCase.execute(input); + const { org } = await sut.execute(input); const isPasswordCorretlyHashed = await compare( - input.password, - org.password - ); - + input.password, + org.password + ); + expect(isPasswordCorretlyHashed).toBe(true) }); it("Não deve ser possivel cadastrar um email existente", async () => { - const orgsRepository = new OrgsRepositoryInMemory(); - const createOrgsUseCase = new CreateOrgsUseCase(orgsRepository); const input = { name: "Org Test", @@ -81,10 +81,10 @@ describe("Caso de Uso: Criação de Organizações", () => { longitude: -46.6, }; - await createOrgsUseCase.execute(input); + await sut.execute(input); await expect( - createOrgsUseCase.execute(input) + sut.execute(input) ).rejects.toBeInstanceOf(OrgAlreadyExistsError) }); From d877c55706bd4c84ebcfdc04713225b6301d0975 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 17:15:32 -0300 Subject: [PATCH 3/8] WIP: Funcionalidade de busca de perfil --- .../orgs/getOrgsProfileController.ts | 34 ++++++++++++++ src/http/controllers/orgs/orgsRoutes.ts | 2 + src/use-cases/errors/resourceNotFound.ts | 5 +++ .../factories/makeGetProfileOrgsUseCase.ts | 9 ++++ .../orgs/getOrgsProfileUseCase.spec.ts | 44 +++++++++++++++++++ src/use-cases/orgs/getOrgsProfileUseCase.ts | 32 ++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 src/http/controllers/orgs/getOrgsProfileController.ts create mode 100644 src/use-cases/errors/resourceNotFound.ts create mode 100644 src/use-cases/factories/makeGetProfileOrgsUseCase.ts create mode 100644 src/use-cases/orgs/getOrgsProfileUseCase.spec.ts create mode 100644 src/use-cases/orgs/getOrgsProfileUseCase.ts diff --git a/src/http/controllers/orgs/getOrgsProfileController.ts b/src/http/controllers/orgs/getOrgsProfileController.ts new file mode 100644 index 0000000..93bb72a --- /dev/null +++ b/src/http/controllers/orgs/getOrgsProfileController.ts @@ -0,0 +1,34 @@ +import { ResourceNotFoundErrors } from '@/use-cases/errors/resourceNotFound'; +import { GetOrgsProfileUseCase } from './../../../use-cases/orgs/getOrgsProfileUseCase'; +import { InvalidCredentialsError } from "@/use-cases/errors/invalidCredentialsError"; +import { makeAuthOrgsUseCase } from "@/use-cases/factories/makeAuthOrgsUseCase"; +import { makeGetOrgsProfileUseCase } from "@/use-cases/factories/makeGetProfileOrgsUseCase"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod/v4"; + +export const getOrgsProfileController = async ( + request: FastifyRequest, + reply: FastifyReply, +) => { + const getOrgsProfileBodySchema = z.object({ + orgId: z.string() + }); + + const bodyData = getOrgsProfileBodySchema.parse(request.body); + + try + { + const getOrgsProfileUseCase = makeGetOrgsProfileUseCase(); + const org = getOrgsProfileUseCase.execute(bodyData); + + reply.status(200).send(org); + } catch (error) + { + if (error instanceof ResourceNotFoundErrors) + { + return reply.status(404).send(error.message); + } + + throw error; + } +} diff --git a/src/http/controllers/orgs/orgsRoutes.ts b/src/http/controllers/orgs/orgsRoutes.ts index 134da3a..19948af 100644 --- a/src/http/controllers/orgs/orgsRoutes.ts +++ b/src/http/controllers/orgs/orgsRoutes.ts @@ -1,8 +1,10 @@ import { FastifyInstance } from "fastify"; import { createOrgsController } from "./createOrgsController"; import { authOrgsController } from "./authOrgsController"; +import { getOrgsProfileController } from "./getOrgsProfileController"; export const orgsRoutes = async (app: FastifyInstance) => { app.post('/orgs', createOrgsController); app.post('/orgs/auth', authOrgsController); + app.get('/orgs', getOrgsProfileController); } \ No newline at end of file diff --git a/src/use-cases/errors/resourceNotFound.ts b/src/use-cases/errors/resourceNotFound.ts new file mode 100644 index 0000000..267bd8f --- /dev/null +++ b/src/use-cases/errors/resourceNotFound.ts @@ -0,0 +1,5 @@ +export class ResourceNotFoundError extends Error { + constructor() { + super('Resource not found.') + } +} \ No newline at end of file diff --git a/src/use-cases/factories/makeGetProfileOrgsUseCase.ts b/src/use-cases/factories/makeGetProfileOrgsUseCase.ts new file mode 100644 index 0000000..75f24e2 --- /dev/null +++ b/src/use-cases/factories/makeGetProfileOrgsUseCase.ts @@ -0,0 +1,9 @@ +import { OrgsRepositoryPrisma } from "@/repositories/prisma/orgsRepositoryPrisma"; +import { GetOrgsProfileUseCase } from "../orgs/getOrgsProfileUseCase"; + +export function makeGetOrgsProfileUseCase() { + const orgsRepository = new OrgsRepositoryPrisma() + const getOrgsProfileUseCase = new GetOrgsProfileUseCase(orgsRepository) + + return getOrgsProfileUseCase; +} \ No newline at end of file diff --git a/src/use-cases/orgs/getOrgsProfileUseCase.spec.ts b/src/use-cases/orgs/getOrgsProfileUseCase.spec.ts new file mode 100644 index 0000000..181ca84 --- /dev/null +++ b/src/use-cases/orgs/getOrgsProfileUseCase.spec.ts @@ -0,0 +1,44 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { hash } from 'bcryptjs'; +import { OrgsRepositoryInMemory } from '@/repositories/in-memory/orgsRepositoryInMemory'; +import { GetOrgsProfileUseCase } from './getOrgsProfileUseCase'; +import { ResourceNotFoundError } from '../errors/resourceNotFound'; + +describe("Caso de Uso: Obter dados da Organização", () => { + let orgsRepository: OrgsRepositoryInMemory; + let sut: GetOrgsProfileUseCase; + + beforeEach(() => { + orgsRepository = new OrgsRepositoryInMemory(); + sut = new GetOrgsProfileUseCase(orgsRepository); + }) + + it("Deve ser possivel obter os dados de perfil da organização", async () => { + const createOrg = await orgsRepository.create({ + email: 'teste@teste.com', + password: await hash('123456789', 6), + name: "Org Test", + author_name: "John Doe", + whatsapp: "99999999", + cep: "12345-000", + state: "SP", + city: "São Paulo", + neighborhood: "Centro", + street: "Rua A", + latitude: -23.5, + longitude: -46.6, + }); + + const { org } = await sut.execute({ + orgId: createOrg.id + }); + + expect(org.email).toEqual('teste@teste.com'); + }); + + it("Não deve ser possivel obter os dados de perfil da organização", async () => { + await expect(sut.execute({ + orgId: 'non-existing-id' + })).rejects.toBeInstanceOf(ResourceNotFoundError) + }); +}); diff --git a/src/use-cases/orgs/getOrgsProfileUseCase.ts b/src/use-cases/orgs/getOrgsProfileUseCase.ts new file mode 100644 index 0000000..0c09d7e --- /dev/null +++ b/src/use-cases/orgs/getOrgsProfileUseCase.ts @@ -0,0 +1,32 @@ +import { IOrgsRepository } from "@/repositories/IOrgsRepository"; +import { Orgs } from "prisma/generated/prisma"; +import { ResourceNotFoundError } from "../errors/resourceNotFound"; + +interface GetOrgsProfileCaseRequest { + orgId: string; + +} + +interface GetOrgsProfileUseCaseResponse { + org: Orgs +} + +export class GetOrgsProfileUseCase { + + constructor( + private orgsRepository: IOrgsRepository + ) { } + + async execute({ orgId }: GetOrgsProfileCaseRequest): Promise { + const org = await this.orgsRepository.findById(orgId) + + if (org === null) + { + throw new ResourceNotFoundError(); + } + + return { + org: org + } + } +} \ No newline at end of file From 8d794a3dccdec2c126c31160bf7db733f3f845c4 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 17:20:20 -0300 Subject: [PATCH 4/8] test: Ajustes dos testes existentes --- src/use-cases/orgs/authOrgsUseCase.spec.ts | 6 ++-- src/use-cases/orgs/createOrgsUseCase.spec.ts | 32 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/use-cases/orgs/authOrgsUseCase.spec.ts b/src/use-cases/orgs/authOrgsUseCase.spec.ts index 8cfeaca..2654654 100644 --- a/src/use-cases/orgs/authOrgsUseCase.spec.ts +++ b/src/use-cases/orgs/authOrgsUseCase.spec.ts @@ -13,7 +13,7 @@ describe("Caso de Uso: Autenticação da Organizações", () => { sut = new AuthOrgsUseCase(orgsRepository); }) - it("Auntenticação com sucesso", async () => { + it("Deve ser possivel autenticar com sucesso", async () => { await orgsRepository.create({ email: 'teste@teste.com', @@ -40,7 +40,7 @@ describe("Caso de Uso: Autenticação da Organizações", () => { ).toEqual(expect.any(String)); }); - it("Auntenticação com falha (e-mail incorreto)", async () => { + it("Não deve ser possivel autenticar (e-mail incorreto)", async () => { await expect( sut.execute({ @@ -50,7 +50,7 @@ describe("Caso de Uso: Autenticação da Organizações", () => { ).rejects.toBeInstanceOf(InvalidCredentialsError); }); - it("Auntenticação com falha (senha incorreta)", async () => { + it("Não deve ser possivel autenticar (senha incorreta)", async () => { await orgsRepository.create({ email: 'teste@teste.com', diff --git a/src/use-cases/orgs/createOrgsUseCase.spec.ts b/src/use-cases/orgs/createOrgsUseCase.spec.ts index 2a18402..e97f19d 100644 --- a/src/use-cases/orgs/createOrgsUseCase.spec.ts +++ b/src/use-cases/orgs/createOrgsUseCase.spec.ts @@ -13,7 +13,7 @@ describe("Caso de Uso: Criação de Organizações", () => { sut = new CreateOrgsUseCase(orgsRepository); }) - it("Não deve ser possivel cadastrar um email existente", async () => { + it("Deve ser possivel cadastrar uma organização", async () => { const input = { name: "Org Test", @@ -32,12 +32,12 @@ describe("Caso de Uso: Criação de Organizações", () => { const { org } = await sut.execute(input); - expect( - org.id - ).toEqual(expect.any(String)); + expect(org).toHaveProperty('id') + expect(org.id).toEqual(expect.any(String)) + }); - it("A senha da Organização deve ser criptografada ao ser cadastrada", async () => { + it("Não deve ser possivel cadastrar um email existente", async () => { const input = { name: "Org Test", @@ -56,15 +56,12 @@ describe("Caso de Uso: Criação de Organizações", () => { const { org } = await sut.execute(input); - const isPasswordCorretlyHashed = await compare( - input.password, - org.password - ); - - expect(isPasswordCorretlyHashed).toBe(true) + expect( + org.id + ).toEqual(expect.any(String)); }); - it("Não deve ser possivel cadastrar um email existente", async () => { + it("A senha da Organização deve ser criptografada ao ser cadastrada", async () => { const input = { name: "Org Test", @@ -81,12 +78,15 @@ describe("Caso de Uso: Criação de Organizações", () => { longitude: -46.6, }; - await sut.execute(input); + const { org } = await sut.execute(input); - await expect( - sut.execute(input) - ).rejects.toBeInstanceOf(OrgAlreadyExistsError) + const isPasswordCorretlyHashed = await compare( + input.password, + org.password + ); + expect(isPasswordCorretlyHashed).toBe(true) }); + }); From 81c4f2103ebfbaaa6c9001f5640eeb5ad8944369 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 18:36:30 -0300 Subject: [PATCH 5/8] CI: Ajuste do workflow --- .github/workflows/tests.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d14d6c..32e34eb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: - master types: [opened, reopened, labeled, unlabeled, synchronize] - workflow_dispatch: # Permite execução manual pelo GitHub + workflow_dispatch: jobs: run-ci: @@ -30,21 +30,20 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v4 - + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.16.0 + - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10.11.1 run_install: false - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies run: pnpm install --frozen-lockfile - + - name: Run tests run: pnpm test From 9c01aa1e2542ee85418aa20064deca27d0578107 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 18:44:16 -0300 Subject: [PATCH 6/8] CI: troca da imagem base do postgresql --- .github/workflows/tests.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 32e34eb..314e58e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,18 +14,13 @@ jobs: services: postgres: - image: postgres:13 + image: bitnami/postgresql ports: - 5432:5432 env: - POSTGRES_USER: docker - POSTGRES_PASSWORD: docker - POSTGRES_DB: upload_test - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + POSTGRESQL_USERNAME: docker + POSTGRESQL_PASSWORD: docker + POSTGRES_DB: api-findAFriend steps: - name: Checkout Code From 56cdf8fc4982f5b10524f8367ecd3646c9c843c0 Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 18:46:38 -0300 Subject: [PATCH 7/8] CI: config db test --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 314e58e..8ddcc62 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,5 +40,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Prisma Generate + run: db:generate + - name: Run tests run: pnpm test + env: + DATABASE_URL: "postgresql://docker:docker@localhost:5432/api-findAFriend?schema=public" From d3144656eba6db432fbc2dbc221dc94280f2eceb Mon Sep 17 00:00:00 2001 From: Murilo Morandi Alexandre Date: Thu, 10 Jul 2025 18:49:56 -0300 Subject: [PATCH 8/8] fix: ci run prisma generate --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ddcc62..bae9d5e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Prisma Generate - run: db:generate + run: pnpm db:generate - name: Run tests run: pnpm test