From db98b6198912e595d517c7068d9c65763cd9f085 Mon Sep 17 00:00:00 2001 From: Bruno Fernandes Date: Fri, 21 Jun 2024 21:46:52 -0300 Subject: [PATCH 1/2] infra: all routes settled, must test to verify --- .gitignore | 2 + client.http | 272 ++++++++++++++++++ package-lock.json | 149 ++++++++-- package.json | 9 +- src/core/entities/unique-entity-id.ts | 7 + .../order-already-collected-error.ts | 7 + .../courier/delete-courier-use-case.ts | 3 + .../order/collect-order-use-case.spec.ts | 16 +- .../use-cases/order/collect-order-use-case.ts | 2 + .../use-cases/order/delete-order-use-case.ts | 3 + .../order/deliver-order-use-case.spec.ts | 24 +- .../fetch-nearby-orders-use-case.spec.ts | 2 +- ...er-as-awaiting-for-pickup-use-case.spec.ts | 16 +- .../order/return-order-use-case.spec.ts | 20 +- .../recipient/delete-recipient-use-case.ts | 3 + .../update-recipient-use-case.spec.ts | 6 +- .../recipient/update-recipient-use-case.ts | 8 +- .../enterprise/entities/abstract/user.ts | 4 +- .../enterprise/entities/order-attachment.ts | 7 + .../enterprise/entities/order.ts | 110 ++++--- .../entities/value-objects/address.ts | 67 +++-- .../enterprise/entities/value-objects/cep.ts | 7 + .../entities/value-objects/coordinates.ts | 31 +- .../enterprise/entities/value-objects/cpf.ts | 7 + .../entities/value-objects/state.ts | 126 ++++---- .../subscribers/on-order-delivered.ts | 37 --- src/infra/app.module.ts | 2 + src/infra/auth/auth.module.ts | 29 ++ src/infra/auth/current-user-decorator.ts | 10 + src/infra/auth/jwt-auth.guard.ts | 22 ++ src/infra/auth/jwt.strategy.ts | 29 ++ src/infra/auth/public.ts | 4 + src/infra/cryptography/bcrypt-encrypter.ts | 14 + src/infra/cryptography/cryptography.module.ts | 14 + src/infra/cryptography/jwt-encoder.ts | 13 + .../mappers/prisma-update-order-mapper.ts | 5 +- .../prisma-orders-repository.ts | 2 +- .../@exports/controllers.exports.ts | 48 ++++ .../authenticate.controller.ts | 75 +++++ .../register.controller.ts | 79 +++++ .../collect-order.controller.e2e-spec.ts | 120 ++++++++ .../collect-order.controller.ts | 89 ++++++ .../create-order.controller.e2e-spec.ts | 0 .../create-order.controller.ts | 87 ++++++ .../delete-order.controller.e2e-spec.ts | 0 .../delete-order.controller.ts | 68 +++++ .../deliver-order.controller.e2e-spec.ts | 0 .../deliver-order.controller.ts | 77 +++++ ...fetch-nearby-orders.controller.e2e-spec.ts | 0 .../fetch-nearby-orders.controller.ts | 89 ++++++ .../fetch-orders.controller.e2e-spec.ts | 0 .../fetch-orders.controller.ts | 110 +++++++ .../find-order.controller.e2e-spec.ts | 0 .../find-order.controller.ts | 78 +++++ ...awaiting-for-pickup.controller.e2e-spec.ts | 0 ...order-as-awaiting-for-pickup.controller.ts | 79 +++++ .../return-order.controller.e2e-spec.ts | 0 .../return-order.controller.ts | 78 +++++ .../delete-user.controller.ts | 78 +++++ .../fetch-users.controller.ts | 84 ++++++ .../user-controllers/find-user.controller.ts | 87 ++++++ .../update-user-password.controller.ts | 88 ++++++ .../update-user.controller.ts | 90 ++++++ .../make-authenticate-request.ts | 20 ++ .../make-register-request.ts | 24 ++ .../make-collect-order-request.ts | 18 ++ .../make-create-order-request.ts | 24 ++ .../make-delete-order-request.ts | 18 ++ .../make-deliver-order-request.ts | 19 ++ .../make-fetch-nearby-orders-request.ts | 26 ++ .../make-fetch-orders-request.ts | 20 ++ .../make-find-order-request.ts | 18 ++ ...rk-order-as-awaiting-for-pickup-request.ts | 18 ++ .../return-order-request copy.ts | 18 ++ .../user-factories/delete-user-request.ts | 20 ++ .../user-factories/fetch-users-request.ts | 19 ++ .../user-factories/find-user-request.ts | 20 ++ .../update-user-password-request.ts | 24 ++ .../user-factories/update-user-request.ts | 25 ++ src/infra/http/http.module.ts | 8 +- src/infra/http/utils/isCPF.ts | 61 ++++ src/infra/pipes/zod-validation-pipe.ts | 23 ++ test/factories/entities/makeOrder.ts | 10 +- utils/generate-async-crypto-keys.ts | 243 ++++++++++++++++ utils/generate-env-file-with-env-example.ts | 8 + 85 files changed, 3038 insertions(+), 239 deletions(-) create mode 100644 client.http create mode 100644 src/core/errors/errors/order-errors/order-already-collected-error.ts delete mode 100644 src/domain/generic/notification/application/subscribers/on-order-delivered.ts create mode 100644 src/infra/auth/auth.module.ts create mode 100644 src/infra/auth/current-user-decorator.ts create mode 100644 src/infra/auth/jwt-auth.guard.ts create mode 100644 src/infra/auth/jwt.strategy.ts create mode 100644 src/infra/auth/public.ts create mode 100644 src/infra/cryptography/bcrypt-encrypter.ts create mode 100644 src/infra/cryptography/cryptography.module.ts create mode 100644 src/infra/cryptography/jwt-encoder.ts create mode 100644 src/infra/http/controllers/auth-and-register-controllers/authenticate.controller.ts create mode 100644 src/infra/http/controllers/auth-and-register-controllers/register.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/collect-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/collect-order.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/create-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/create-order.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/delete-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/delete-order.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/deliver-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/deliver-order.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/fetch-orders.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/fetch-orders.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/find-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/find-order.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.ts create mode 100644 src/infra/http/controllers/order-controllers/return-order.controller.e2e-spec.ts create mode 100644 src/infra/http/controllers/order-controllers/return-order.controller.ts create mode 100644 src/infra/http/controllers/user-controllers/delete-user.controller.ts create mode 100644 src/infra/http/controllers/user-controllers/fetch-users.controller.ts create mode 100644 src/infra/http/controllers/user-controllers/find-user.controller.ts create mode 100644 src/infra/http/controllers/user-controllers/update-user-password.controller.ts create mode 100644 src/infra/http/controllers/user-controllers/update-user.controller.ts create mode 100644 src/infra/http/factories/requests/auth-and-register-factories/make-authenticate-request.ts create mode 100644 src/infra/http/factories/requests/auth-and-register-factories/make-register-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-collect-order-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-create-order-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-delete-order-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-deliver-order-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-fetch-nearby-orders-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-fetch-orders-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-find-order-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/make-mark-order-as-awaiting-for-pickup-request.ts create mode 100644 src/infra/http/factories/requests/order-request-factories/return-order-request copy.ts create mode 100644 src/infra/http/factories/requests/user-factories/delete-user-request.ts create mode 100644 src/infra/http/factories/requests/user-factories/fetch-users-request.ts create mode 100644 src/infra/http/factories/requests/user-factories/find-user-request.ts create mode 100644 src/infra/http/factories/requests/user-factories/update-user-password-request.ts create mode 100644 src/infra/http/factories/requests/user-factories/update-user-request.ts create mode 100644 src/infra/http/utils/isCPF.ts create mode 100644 src/infra/pipes/zod-validation-pipe.ts create mode 100644 utils/generate-async-crypto-keys.ts create mode 100644 utils/generate-env-file-with-env-example.ts diff --git a/.gitignore b/.gitignore index 010935a..fcab905 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ pids report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json videos +*.pem +data diff --git a/client.http b/client.http new file mode 100644 index 0000000..c3da943 --- /dev/null +++ b/client.http @@ -0,0 +1,272 @@ +@baseUrl = http://localhost:3000 +@authToken = {{authenticate.response.body.token}} + + +@firstCourierId = {{fetchCourier.response.body.couriers[0]._id._value}} +@firstRecipientId = {{fetchRecipient.response.body.recipients[0]._id._value}} + +@firstOrderId = {{fetchAllOrders.response.body.orders[0]._id._value}} + +@currentLatitude = -23.3963853 +@currentLongitude = -46.3086881 + +@nearbyLatitude = -23.3798813 +@nearbyLongitude = -46.2576877 + +@farAwayLatitude = -23.3571925 +@farAwayLongitude = -46.2076257 + + +### + + +# @name authenticate +POST {{baseUrl}}/sessions +Content-Type: application/json + +{ + "cpf": "45618677830", + "password": "123", + "role": "adm" +} + + +### + +# @name registerCourier +POST {{baseUrl}}/register +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "cpf": "24247258028", + "name": "courier teste", + "password": "123", + "requestResponsibleId": "02e3db39-2855-4d57-86bf-8a10364d9804", + "role": "courier" +} + +### + +# @name registerRecipient +POST {{baseUrl}}/register +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "cpf": "67131743020", + "name": "courier teste", + "password": "123", + "requestResponsibleId": "02e3db39-2855-4d57-86bf-8a10364d9804", + "role": "recipient" +} + + +### + +# @name deleteUser +DELETE {{baseUrl}}/users/courier/a95c748a-dbbb-456f-9d6c-a91a584d2ca9 +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchCourier +GET {{baseUrl}}/users/courier +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchRecipient +GET {{baseUrl}}/users/recipient +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name findCourier +GET {{baseUrl}}/users/courier/{{firstCourierId}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name findRecipient +GET {{baseUrl}}/users/recipient/{{firstRecipientId}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name updateCourierPassword +PATCH {{baseUrl}}/users/courier/{{firstCourierId}}/update-password +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "password": "Courier pass", +} + + +### + +# @name updateRecipientPassword +PATCH {{baseUrl}}/users/recipient/{{firstRecipientId}}/update-password +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "password": "Recipient pass", +} + + + +### + +# @name updateUser +PUT {{baseUrl}}/users/courier/6726449a-790d-40e9-a5fe-6d725066c335 +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "cpf": "48995844078", + "name": "otavio 2" +} + + +### + +# @name collectOrder +PATCH {{baseUrl}}/orders/{{firstOrderId}}/collect +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name createNearbyOrder +POST {{baseUrl}}/orders +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "courierId": "{{firstCourierId}}", + "recipientId": "{{firstRecipientId}}", + "address": { + "coordinates": { + "latitude": "{{nearbyLatitude}}", + "longitude": "{{nearbyLongitude}}" + }, + "cep": "77777-888", + "number": "Rua dos bobos", + "street": "número zero", + "neighborhood": "Não tinha bairro", + "city": "Não tinha cidade", + "state": "SP" + } +} + + +### + +# @name createFarAwayOrder +POST {{baseUrl}}/orders +Content-Type: application/json +Authorization: Bearer {{authToken}} + +{ + "courierId": "{{firstCourierId}}", + "recipientId": "{{firstRecipientId}}", + "address": { + "coordinates": { + "latitude": "{{farAwayLatitude}}", + "longitude": "{{farAwayLongitude}}" + }, + "cep": "77777-888", + "number": "Rua dos bobos", + "street": "número zero", + "neighborhood": "Não tinha bairro", + "city": "Não tinha cidade", + "state": "SP" + } +} + + +### + +# @name deleteOrder +DELETE {{baseUrl}}/orders/{{firstOrderId}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name deliverOrder +PATCH {{baseUrl}}/orders/{{firstOrderId}}/deliver +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchNearbyOrders +GET {{baseUrl}}/orders/{{firstCourierId}}/nearby?latitude={{currentLatitude}}&longitude={{currentLongitude}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchAllOrders +GET {{baseUrl}}/orders/all +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchCourierOrders +GET {{baseUrl}}/orders/courier?userId={{firstCourierId}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name fetchRecipientOrders +GET {{baseUrl}}/orders/recipent?userId={{firstRecipientId}} +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name findOrder +GET {{baseUrl}}/orders/{{firstOrderId}}/find +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name markOrderAsAwaitingForPickup +PATCH {{baseUrl}}/orders/{{firstOrderId}}/awaiting +Content-Type: application/json +Authorization: Bearer {{authToken}} + + +### + +# @name returnOrder +PATCH {{baseUrl}}/orders/{{firstOrderId}}/return?returnCause=porque sim +Content-Type: application/json +Authorization: Bearer {{authToken}} + + diff --git a/package-lock.json b/package-lock.json index f41b442..b1beda5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@nestjs/common": "10.3.9", "@nestjs/config": "3.2.2", "@nestjs/core": "10.3.9", + "@nestjs/jwt": "10.2.0", + "@nestjs/passport": "10.0.3", "@nestjs/platform-express": "10.3.9", "@prisma/client": "5.15.0", "bcryptjs": "2.4.3", @@ -20,7 +22,9 @@ "reflect-metadata": "0.2.2", "remask": "1.2.2", "rxjs": "7.8.1", - "zod": "3.23.8" + "tsx": "4.15.6", + "zod": "3.23.8", + "zod-validation-error": "3.3.0" }, "devDependencies": { "@faker-js/faker": "8.4.1", @@ -32,6 +36,7 @@ "@types/express": "4.17.21", "@types/multer": "1.4.11", "@types/node": "20.14.2", + "@types/passport-jwt": "4.0.1", "@types/supertest": "6.0.2", "@typescript-eslint/eslint-plugin": "7.13.0", "@typescript-eslint/parser": "7.13.0", @@ -382,7 +387,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "aix" @@ -398,7 +402,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -414,7 +417,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -430,7 +432,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -446,7 +447,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -462,7 +462,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -478,7 +477,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -494,7 +492,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -510,7 +507,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -526,7 +522,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -542,7 +537,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -558,7 +552,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -574,7 +567,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -590,7 +582,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -606,7 +597,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -622,7 +612,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -638,7 +627,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -654,7 +642,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -670,7 +657,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -686,7 +672,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -702,7 +687,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -718,7 +702,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -734,7 +717,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1312,6 +1294,27 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", + "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "dependencies": { + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@nestjs/passport": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.3.tgz", + "integrity": "sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "passport": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "10.3.9", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.9.tgz", @@ -2354,6 +2357,14 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -2379,11 +2390,39 @@ "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/passport": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz", + "integrity": "sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -4453,7 +4492,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -5698,7 +5736,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -5791,7 +5828,6 @@ "version": "4.7.5", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", - "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -7505,6 +7541,24 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "peer": true, + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", @@ -7609,6 +7663,12 @@ "node": "*" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", + "peer": true + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -8037,7 +8097,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -9201,6 +9260,24 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsx": { + "version": "4.15.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.15.6.tgz", + "integrity": "sha512-is0VQQlfNZRHEuSSTKA6m4xw74IU4AizmuB6lAYLRt9XtuyeQnyJYexhNZOPCB59SqC4JzmSzPnHGBXxf3k0hA==", + "dependencies": { + "esbuild": "~0.21.4", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -9398,8 +9475,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universalify": { "version": "2.0.1", @@ -10148,6 +10224,17 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-validation-error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz", + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } } } } diff --git a/package.json b/package.json index 3ec1cd6..09cb5ab 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "build": "nest build", "start": "nest start", "dev": "nest start --watch", + "dev:gen-env": "tsx ./utils/generate-env-file-with-env-example.ts", + "ci:gen-jwt-keys": "tsx ./utils/generate-async-crypto-keys.ts", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", @@ -28,6 +30,7 @@ "@types/express": "4.17.21", "@types/multer": "1.4.11", "@types/node": "20.14.2", + "@types/passport-jwt": "4.0.1", "@types/supertest": "6.0.2", "@typescript-eslint/eslint-plugin": "7.13.0", "@typescript-eslint/parser": "7.13.0", @@ -50,6 +53,8 @@ "@nestjs/common": "10.3.9", "@nestjs/config": "3.2.2", "@nestjs/core": "10.3.9", + "@nestjs/jwt": "10.2.0", + "@nestjs/passport": "10.0.3", "@nestjs/platform-express": "10.3.9", "@prisma/client": "5.15.0", "bcryptjs": "2.4.3", @@ -58,6 +63,8 @@ "reflect-metadata": "0.2.2", "remask": "1.2.2", "rxjs": "7.8.1", - "zod": "3.23.8" + "tsx": "4.15.6", + "zod": "3.23.8", + "zod-validation-error": "3.3.0" } } diff --git a/src/core/entities/unique-entity-id.ts b/src/core/entities/unique-entity-id.ts index 8e95b06..813c07e 100644 --- a/src/core/entities/unique-entity-id.ts +++ b/src/core/entities/unique-entity-id.ts @@ -1,4 +1,11 @@ import { randomUUID } from 'node:crypto' +import z from 'zod' + +// eslint-disable-next-line +export const uniqueEntityIdInstanceSchema = z.custom( + (data) => data instanceof UniqueEntityId, + 'must be an UniqueEntityId', +) export default class UniqueEntityId { private _value: string diff --git a/src/core/errors/errors/order-errors/order-already-collected-error.ts b/src/core/errors/errors/order-errors/order-already-collected-error.ts new file mode 100644 index 0000000..b6cd78d --- /dev/null +++ b/src/core/errors/errors/order-errors/order-already-collected-error.ts @@ -0,0 +1,7 @@ +import { UseCaseError } from '@/core/errors/use-case-errors' + +export class OrderAlreadyCollectedError extends Error implements UseCaseError { + constructor() { + super(`this order was already collected`) + } +} diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/courier/delete-courier-use-case.ts b/src/domain/core/deliveries-and-orders/application/use-cases/courier/delete-courier-use-case.ts index 5f9a263..a716f81 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/courier/delete-courier-use-case.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/courier/delete-courier-use-case.ts @@ -33,6 +33,9 @@ export class DeleteCourierUseCase { const hasPermission = Permissions.hasPermission('read_courier', adm.role) if (!hasPermission) return left(new UnauthorizedError()) + const courier = await this.couriersRepository.findById(courierId) + if (!courier) return left(new ResourceNotFoundError()) + await this.couriersRepository.delete(courierId) return right(null) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts index ff06fee..c9c46ca 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts @@ -40,7 +40,7 @@ describe('collect order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -113,11 +113,11 @@ describe('collect order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -157,13 +157,13 @@ describe('collect order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // fake return - order.actions.adm.admReturned() + order.actions.adm.admReturned('sei la', adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.ts index 568d272..9bed7cd 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.ts @@ -10,6 +10,7 @@ import { OrderAwaitingPickupError } from '@/core/errors/errors/order-errors/orde import { OrderIsClosedError } from '@/core/errors/errors/order-errors/order-is-closed-error' import { InternalServerError } from '@/core/errors/errors/internal-server-error' import { Injectable } from '@nestjs/common' +import { OrderAlreadyCollectedError } from '@/core/errors/errors/order-errors/order-already-collected-error' export interface CollectOrderUseCaseRequest { orderId: string @@ -21,6 +22,7 @@ export type CollectOrderUseCaseResponse = Either< | UnauthorizedError | OrderAwaitingPickupError | OrderIsClosedError + | OrderAlreadyCollectedError | InternalServerError, null > diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/delete-order-use-case.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/delete-order-use-case.ts index ffe6ae8..179d399 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/delete-order-use-case.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/delete-order-use-case.ts @@ -33,6 +33,9 @@ export class DeleteOrderUseCase { const hasPermission = Permissions.hasPermission('read_order', adm.role) if (!hasPermission) return left(new UnauthorizedError()) + const order = await this.ordersRepository.findById(orderId) + if (!order) return left(new ResourceNotFoundError()) + await this.ordersRepository.delete(orderId) return right(null) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/deliver-order-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/deliver-order-use-case.spec.ts index 76c718d..60ab9a6 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/deliver-order-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/deliver-order-use-case.spec.ts @@ -44,10 +44,10 @@ describe('deliver order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -94,10 +94,10 @@ describe('deliver order use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -145,7 +145,7 @@ describe('deliver order use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -195,13 +195,13 @@ describe('deliver order use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // fake return - order.actions.adm.admReturned() + order.actions.adm.admReturned('sei la', adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -252,11 +252,11 @@ describe('deliver order use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // update changes createOrder.dependencies.ordersRepository.update(order) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/fetch-nearby-orders-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/fetch-nearby-orders-use-case.spec.ts index 4944c72..35589e8 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/fetch-nearby-orders-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/fetch-nearby-orders-use-case.spec.ts @@ -127,7 +127,7 @@ describe('fetch nearby orders use case', () => { longitude: -46.3086881, }, }) - // courier ccordinates + // courier coordinates // -23.3963853,-46.3086881 expect(sutResp.isRight()).toBeTruthy() diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/mark-order-as-awaiting-for-pickup-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/mark-order-as-awaiting-for-pickup-use-case.spec.ts index ab89061..ccb666f 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/mark-order-as-awaiting-for-pickup-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/mark-order-as-awaiting-for-pickup-use-case.spec.ts @@ -105,7 +105,7 @@ describe('mark order as awaiting for pickup use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -145,13 +145,13 @@ describe('mark order as awaiting for pickup use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // fake return - order.actions.adm.admReturned() + order.actions.adm.admReturned('sei la', adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -190,11 +190,11 @@ describe('mark order as awaiting for pickup use case', () => { const order = (await sut.dependencies.ordersRepository.findMany())[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // update changes createOrder.dependencies.ordersRepository.update(order) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/return-order-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/return-order-use-case.spec.ts index 30bc8eb..08d9121 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/return-order-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/return-order-use-case.spec.ts @@ -41,11 +41,11 @@ describe('return order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -121,9 +121,9 @@ describe('return order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) const sutResp = await sut.useCase.execute({ orderId: order.id.value, @@ -163,13 +163,13 @@ describe('return order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // fake collected - order.actions.adm.admCollected() + order.actions.adm.admCollected(adm.id) // fake delivery - order.actions.courier.courierDeliver() + order.actions.courier.courierDeliver(order.courierId!) // fake return - order.actions.adm.admReturned() + order.actions.adm.admReturned('sei la', adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) @@ -212,7 +212,7 @@ describe('return order use case', () => { )[0] // fake awaiting for pickup - order.actions.adm.admSetAwaitingPickup() + order.actions.adm.admSetAwaitingPickup(adm.id) // update changes createOrder.dependencies.ordersRepository.update(order) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/delete-recipient-use-case.ts b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/delete-recipient-use-case.ts index 0d9ee99..0e7b294 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/delete-recipient-use-case.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/delete-recipient-use-case.ts @@ -33,6 +33,9 @@ export class DeleteRecipientUseCase { const hasPermission = Permissions.hasPermission('read_recipient', adm.role) if (!hasPermission) return left(new UnauthorizedError()) + const recipient = await this.recipientsRepository.findById(recipientId) + if (!recipient) return left(new ResourceNotFoundError()) + await this.recipientsRepository.delete(recipientId) return right(null) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.spec.ts index 253397f..6f7dc29 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.spec.ts @@ -39,7 +39,7 @@ describe('update recipient use case', () => { recipientId: recipient.id.value, cpf: '44411132112', name: 'bruno-2', - admId: adm.id.value, + requestResponsibleId: adm.id.value, }) const recipients = await sut.dependencies.recipientsRepository.findMany() @@ -74,7 +74,7 @@ describe('update recipient use case', () => { recipientId: recipient.id.value, cpf: '44411132112', name: 'bruno-2', - admId: adm.id.value, + requestResponsibleId: adm.id.value, }) const updates = await sut.dependencies.recipientUpdatesRepository.findMany() @@ -104,7 +104,7 @@ describe('update recipient use case', () => { recipientId: recipient.id.value, cpf: '44411132112', name: 'bruno-2', - admId: 'any Id', + requestResponsibleId: 'any Id', }) const recipients = await sut.dependencies.recipientsRepository.findMany() diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.ts b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.ts index f172080..6e728d5 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case.ts @@ -12,7 +12,7 @@ export interface UpdateRecipientUseCaseRequest { recipientId: string name: string cpf: string - admId: string + requestResponsibleId: string } export type UpdateRecipientUseCaseResponse = Either< @@ -32,9 +32,9 @@ export class UpdateRecipientUseCase { recipientId, cpf, name, - admId, + requestResponsibleId, }: UpdateRecipientUseCaseRequest): Promise { - const adm = await this.admsRepository.findById(admId) + const adm = await this.admsRepository.findById(requestResponsibleId) if (!adm) return left(new ResourceNotFoundError()) const hasPermission = Permissions.hasPermission( @@ -48,7 +48,7 @@ export class UpdateRecipientUseCase { const { data: update } = recipient.changeData( { cpf, name }, - new UniqueEntityId(admId), + new UniqueEntityId(requestResponsibleId), ) await this.recipientsRepository.update(recipient) await this.recipientUpdatesRepository.create(update) diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/abstract/user.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/abstract/user.ts index 6ee3ec6..6caf89e 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/abstract/user.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/abstract/user.ts @@ -2,8 +2,10 @@ import Entity from '@/core/entities/entity' import { Cpf } from '../value-objects/cpf' import { Coordinates } from '../value-objects/coordinates' import UniqueEntityId from '@/core/entities/unique-entity-id' +import z from 'zod' -export type UserRoles = 'recipient' | 'adm' | 'courier' +export const userRoleSchema = z.enum(['recipient', 'adm', 'courier']) +export type UserRoles = z.infer export interface UserProps { name: string diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/order-attachment.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/order-attachment.ts index 21d3525..5cf42f5 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/order-attachment.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/order-attachment.ts @@ -1,5 +1,12 @@ import UniqueEntityId from '@/core/entities/unique-entity-id' import Entity from '@/core/entities/entity' +import z from 'zod' + +// eslint-disable-next-line +export const orderAttachmentInstanceSchema = z.custom( + (data) => data instanceof OrderAttachment, + 'must be a valide OrderAttachment', +) export interface OrderAttachmentProps { orderId: UniqueEntityId diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/order.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/order.ts index 24f3596..5569b65 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/order.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/order.ts @@ -1,6 +1,7 @@ -import UniqueEntityId from '@/core/entities/unique-entity-id' -import { Address } from './value-objects/address' -import { Optional } from '@/core/types/optional' +import UniqueEntityId, { + uniqueEntityIdInstanceSchema, +} from '@/core/entities/unique-entity-id' +import { Address, addressInstanceSchema } from './value-objects/address' import { AggregateRoot } from '@/core/entities/aggregate-root' import { OrderAlreadyAcceptedError } from '@/core/errors/errors/order-errors/order-already-accepted-error' import { OrderIsClosedError } from '@/core/errors/errors/order-errors/order-is-closed-error' @@ -13,39 +14,76 @@ import { OrderAwaitingForPickupEvent } from '../events/order-awaiting-for-pickup import { OrderNotDeliveredError } from '@/core/errors/errors/order-errors/order-not-delivered-error' import { OrderCourierReturnedEvent } from '../events/order-courier-returned-event' import { UpdateOrder } from './update-order' -import { OrderAttachment } from './order-attachment' +import { + OrderAttachment, + orderAttachmentInstanceSchema, +} from './order-attachment' import { OrderAlreadyReturnedError } from '@/core/errors/errors/order-errors/order-already-returned-error copy' import { OrderNotAwaitingPickupError } from '@/core/errors/errors/order-errors/order-not-awaiting-for-pickup-error' import { OrderWasNotCollectedError } from '@/core/errors/errors/order-errors/order-was-not-collected-error' import { OrderCourierCollectedEvent } from '../events/order-courier-collected-event' - -export interface OrderProps { - recipientId: UniqueEntityId - courierId: UniqueEntityId | null - address: Address - delivered: Date | null - deliveredPhoto: OrderAttachment | null - awaitingPickup: Date | null - collected: Date | null - returned: Date | null - returnCause: string | null - createdAt: Date - updatedAt: Date | null -} - -export type OrderCreateProps = Omit< - Optional< - OrderProps, - | 'awaitingPickup' - | 'collected' - | 'returned' - | 'returnCause' - | 'delivered' - | 'createdAt' - | 'updatedAt' - >, - 'deliveredPhoto' -> & { deliveredPhoto?: string | null } +import z from 'zod' +import { OrderAlreadyCollectedError } from '@/core/errors/errors/order-errors/order-already-collected-error' + +export const orderPropsSchema = z.object({ + recipientId: uniqueEntityIdInstanceSchema, + courierId: uniqueEntityIdInstanceSchema.nullable(), + address: addressInstanceSchema, + delivered: z.date().nullable(), + deliveredPhoto: orderAttachmentInstanceSchema.nullable(), + awaitingPickup: z.date().nullable(), + collected: z.date().nullable(), + returned: z.date().nullable(), + returnCause: z.string().nullable(), + createdAt: z.date(), + updatedAt: z.date().nullable(), +}) + +export type OrderProps = z.infer + +// export interface OrderProps { +// recipientId: UniqueEntityId +// courierId: UniqueEntityId | null +// address: Address +// delivered: Date | null +// deliveredPhoto: OrderAttachment | null +// awaitingPickup: Date | null +// collected: Date | null +// returned: Date | null +// returnCause: string | null +// createdAt: Date +// updatedAt: Date | null +// } + +export const orderCreatePropsSchema = z.object({ + recipientId: uniqueEntityIdInstanceSchema, + courierId: uniqueEntityIdInstanceSchema.nullable(), + address: addressInstanceSchema, + delivered: z.date().nullable().optional(), + deliveredPhoto: z.string().nullable().optional(), + awaitingPickup: z.date().nullable().optional(), + collected: z.date().nullable().optional(), + returned: z.date().nullable().optional(), + returnCause: z.string().nullable().optional(), + createdAt: z.date().optional(), + updatedAt: z.date().nullable().optional(), +}) + +export type OrderCreateProps = z.infer + +// export type OrderCreateProps = Omit< +// Optional< +// OrderProps, +// | 'awaitingPickup' +// | 'collected' +// | 'returned' +// | 'returnCause' +// | 'delivered' +// | 'createdAt' +// | 'updatedAt' +// >, +// 'deliveredPhoto' +// > & { deliveredPhoto?: string | null } export type UpdateOrderReturn = { data?: UpdateOrder; error?: errors } @@ -171,10 +209,16 @@ export class Order extends AggregateRoot { private admCollected( updatedBy: UniqueEntityId, - ): UpdateOrderReturn { + ): UpdateOrderReturn< + | OrderNotAwaitingPickupError + | OrderIsClosedError + | OrderAlreadyCollectedError + > { + const collected = !!this.props.collected const awaitingPickup = !!this.props.awaitingPickup const isOrderClosed = this.isOrderClosed + if (collected) return { error: new OrderAlreadyCollectedError() } if (isOrderClosed) return { error: new OrderIsClosedError() } if (!awaitingPickup) return { error: new OrderNotAwaitingPickupError() } diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address.ts index c64d97e..71be4a0 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address.ts @@ -1,21 +1,56 @@ import { ValueObject } from '@/core/entities/value-objects' import { Cep } from './cep' -import { Coordinates } from './coordinates' -import { State, StatesShort } from './state' - -export interface AddressProps { - coordinates: Coordinates - cep: string - number: string - street: string - neighborhood: string - city: string - state: StatesShort -} - -export type AddressCreationProps = Omit & { - coordinates: Coordinates['raw'] -} +import { + Coordinates, + coordinatesInstanceSchema, + coordinatesSchema, +} from './coordinates' +import { State, StatesShort, statesShortSchema } from './state' +import z from 'zod' + +// eslint-disable-next-line +export const addressInstanceSchema = z.custom
( + (data) => data instanceof Address, + 'must be a valide Address', +) + +export const addressPropsSchema = z.object({ + coordinates: coordinatesInstanceSchema, + cep: z.string(), + number: z.string(), + street: z.string(), + neighborhood: z.string(), + city: z.string(), + state: statesShortSchema, +}) + +export type AddressProps = z.infer + +// export interface AddressProps { +// coordinates: Coordinates +// cep: string +// number: string +// street: string +// neighborhood: string +// city: string +// state: StatesShort +// } + +export const addressCreationPropsSchema = z.object({ + coordinates: coordinatesSchema, + cep: z.string(), + number: z.string(), + street: z.string(), + neighborhood: z.string(), + city: z.string(), + state: statesShortSchema, +}) + +export type AddressCreationProps = z.infer + +// export type AddressCreationProps = Omit & { +// coordinates: Coordinates['raw'] +// } export type AddressRaw = { coordinates: Coordinates['raw'] diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cep.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cep.ts index 242c6f8..a09dd32 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cep.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cep.ts @@ -1,5 +1,12 @@ import { DataValidationError } from '@/core/errors/errors/data-validation-error' import { mask } from 'remask' +import z from 'zod' + +// eslint-disable-next-line +export const cepInstanceSchema = z.custom( + (data) => data instanceof Cep, + 'must be a valide Cep', +) export class Cep { private cep: string diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates.ts index c8e9199..2a14f39 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates.ts @@ -1,19 +1,26 @@ import { ValueObject } from '@/core/entities/value-objects' import z from 'zod' +// eslint-disable-next-line +export const coordinatesInstanceSchema = z.custom( + (data) => data instanceof Coordinates, + 'must be a valide Coordinates', +) + +export const latitudeSchema = z.coerce + .number() + .refine((lat) => Math.abs(lat) <= 90, 'latitude must be between -90 and 90') + +export const longitudeSchema = z.coerce + .number() + .refine( + (lat) => Math.abs(lat) <= 180, + 'longitude must be between -180 and 180', + ) + export const coordinatesSchema = z.object({ - latitude: z.coerce - .number() - .refine( - (lat) => Math.abs(lat) <= 90, - 'latitude must be between -90 and 90', - ), - longitude: z.coerce - .number() - .refine( - (lat) => Math.abs(lat) <= 180, - 'longitude must be between -180 and 180', - ), + latitude: latitudeSchema, + longitude: longitudeSchema, }) export type CoordinatesProps = z.infer diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cpf.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cpf.ts index 9af026a..afcbcff 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cpf.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cpf.ts @@ -1,5 +1,12 @@ import { DataValidationError } from '@/core/errors/errors/data-validation-error' import { mask } from 'remask' +import z from 'zod' + +// eslint-disable-next-line +export const cpfInstanceSchema = z.custom( + (data) => data instanceof Cpf, + 'must be a valide Cpf', +) export class Cpf { private cpf: string diff --git a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/state.ts b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/state.ts index 8a38219..d9ee8bf 100644 --- a/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/state.ts +++ b/src/domain/core/deliveries-and-orders/enterprise/entities/value-objects/state.ts @@ -1,59 +1,73 @@ -export type StatesShort = - | 'AM' - | 'PA' - | 'RR' - | 'AP' - | 'AC' - | 'RO' - | 'TO' - | 'MA' - | 'PI' - | 'CE' - | 'RN' - | 'PB' - | 'PE' - | 'AL' - | 'SE' - | 'BA' - | 'MG' - | 'ES' - | 'RJ' - | 'SP' - | 'PR' - | 'SC' - | 'RS' - | 'MS' - | 'MT' - | 'GO' - | 'DF' -type StatesVerbose = - | 'Amazonas' - | 'Pará' - | 'Roraima' - | 'Amapá' - | 'Acre' - | 'Rondônia' - | 'Tocantins' - | 'Maranhão' - | 'Piauí' - | 'Ceará' - | 'Rio Grande do Norte' - | 'Paraíba' - | 'Pernambuco' - | 'Alagoas' - | 'Sergipe' - | 'Bahia' - | 'Minas Gerais' - | 'Espírito Santo' - | 'Rio de Janeiro' - | 'São Paulo' - | 'Paraná' - | 'Santa Catarina' - | 'Rio Grande do Sul' - | 'Mato Grosso do Sul' - | 'Mato Grosso' - | 'Goiás' - | 'Distrito Federal' +import z from 'zod' + +export const statesShortSchema = z.enum([ + 'AM', + 'PA', + 'RR', + 'AP', + 'AC', + 'RO', + 'TO', + 'MA', + 'PI', + 'CE', + 'RN', + 'PB', + 'PE', + 'AL', + 'SE', + 'BA', + 'MG', + 'ES', + 'RJ', + 'SP', + 'PR', + 'SC', + 'RS', + 'MS', + 'MT', + 'GO', + 'DF', +]) +export type StatesShort = z.infer + +export const statesStatesVerboseSchema = z.enum([ + 'Amazonas', + 'Pará', + 'Roraima', + 'Amapá', + 'Acre', + 'Rondônia', + 'Tocantins', + 'Maranhão', + 'Piauí', + 'Ceará', + 'Rio Grande do Norte', + 'Paraíba', + 'Pernambuco', + 'Alagoas', + 'Sergipe', + 'Bahia', + 'Minas Gerais', + 'Espírito Santo', + 'Rio de Janeiro', + 'São Paulo', + 'Paraná', + 'Santa Catarina', + 'Rio Grande do Sul', + 'Mato Grosso do Sul', + 'Mato Grosso', + 'Goiás', + 'Distrito Federal', +]) + +type StatesVerbose = z.infer + +// eslint-disable-next-line +export const stateInstanceSchema = z.custom( + (data) => data instanceof State, + 'must be a valide State', +) export class State { private state: StatesShort diff --git a/src/domain/generic/notification/application/subscribers/on-order-delivered.ts b/src/domain/generic/notification/application/subscribers/on-order-delivered.ts deleted file mode 100644 index 25d8ab7..0000000 --- a/src/domain/generic/notification/application/subscribers/on-order-delivered.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { DomainEvents } from '@/core/events/domain-events' -import { EventHandler } from '@/core/events/event-handler' -import SendNotificationUseCase from '../use-cases/send-notification' -import { left } from '@/core/either' -import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' -import { Injectable } from '@nestjs/common' -import { OrdersRepository } from '@/domain/core/deliveries-and-orders/application/repositories/order-repositories/orders-repository' -import { OrderDeliveredEvent } from '@/domain/core/deliveries-and-orders/enterprise/events/order-delivered-event' - -@Injectable() -export class OnOrderDelivered implements EventHandler { - constructor( - private ordersRepository: OrdersRepository, - private sendNotificationUseCase: SendNotificationUseCase, - ) { - this.setupSubscriptions() - } - - setupSubscriptions(): void { - DomainEvents.register( - this.sendNewAnswerNotification.bind(this), - OrderDeliveredEvent.name, - ) - } - - private async sendNewAnswerNotification({ order }: OrderDeliveredEvent) { - const currentOrder = await this.ordersRepository.findById(order.id.value) - - if (!currentOrder) return left(new ResourceNotFoundError()) - - await this.sendNotificationUseCase.execute({ - recipientId: currentOrder.recipientId.value, - title: `Um entregador aceitou realizar seu pedido ${currentOrder.id.value}`, - content: `Em breve o entregador irá retirar seu pedido`, - }) - } -} diff --git a/src/infra/app.module.ts b/src/infra/app.module.ts index 5da5ac0..e6227c8 100644 --- a/src/infra/app.module.ts +++ b/src/infra/app.module.ts @@ -3,6 +3,7 @@ import { EnvModule } from './env/env.module' import { ConfigModule } from '@nestjs/config' import { envSchema } from './env/env' import { HttpModule } from './http/http.module' +import { AuthModule } from './auth/auth.module' @Module({ imports: [ @@ -12,6 +13,7 @@ import { HttpModule } from './http/http.module' }), EnvModule, HttpModule, + AuthModule, ], controllers: [], providers: [], diff --git a/src/infra/auth/auth.module.ts b/src/infra/auth/auth.module.ts new file mode 100644 index 0000000..af1567a --- /dev/null +++ b/src/infra/auth/auth.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common' +import { JwtModule } from '@nestjs/jwt' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { JwtStrategy } from './jwt.strategy' +import { PassportModule } from '@nestjs/passport' +import { APP_GUARD } from '@nestjs/core' +import { JwtAuthGuard } from './jwt-auth.guard' + +@Module({ + imports: [ + JwtModule.registerAsync({ + global: true, + useFactory() { + const pathToKeys = './keys' + const pathToPrivateKey = resolve(pathToKeys, 'private_key.pem') + const pathToPublicKey = resolve(pathToKeys, 'public_key.pem') + return { + signOptions: { algorithm: 'RS256' }, + privateKey: readFileSync(pathToPrivateKey, 'utf8'), + publicKey: readFileSync(pathToPublicKey, 'utf8'), + } + }, + }), + PassportModule, + ], + providers: [JwtStrategy, { provide: APP_GUARD, useClass: JwtAuthGuard }], +}) +export class AuthModule {} diff --git a/src/infra/auth/current-user-decorator.ts b/src/infra/auth/current-user-decorator.ts new file mode 100644 index 0000000..3198435 --- /dev/null +++ b/src/infra/auth/current-user-decorator.ts @@ -0,0 +1,10 @@ +import { ExecutionContext, createParamDecorator } from '@nestjs/common' +import { TokenPayload } from './jwt.strategy' + +export const CurrentUser = createParamDecorator( + (_: never, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest() + + return request.user as TokenPayload + }, +) diff --git a/src/infra/auth/jwt-auth.guard.ts b/src/infra/auth/jwt-auth.guard.ts new file mode 100644 index 0000000..fedb164 --- /dev/null +++ b/src/infra/auth/jwt-auth.guard.ts @@ -0,0 +1,22 @@ +import { ExecutionContext, Injectable } from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { AuthGuard } from '@nestjs/passport' +import { IS_PUBLIC_KEY } from './public' + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private reflector: Reflector) { + super() + } + + canActivate(context: ExecutionContext) { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]) + if (isPublic) { + return true + } + return super.canActivate(context) + } +} diff --git a/src/infra/auth/jwt.strategy.ts b/src/infra/auth/jwt.strategy.ts new file mode 100644 index 0000000..7b8d22c --- /dev/null +++ b/src/infra/auth/jwt.strategy.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { readFileSync } from 'node:fs' +import { ExtractJwt, Strategy } from 'passport-jwt' +import { z } from 'zod' + +const tokenSchema = z.object({ + sub: z.string().uuid(), + role: z.enum(['recipient', 'adm', 'courier']), +}) + +export type TokenPayload = z.infer + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + const publicKey = readFileSync('./keys/public_key.pem') + + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: publicKey, + algorithms: ['RS256'], + }) + } + + async validate(payload: TokenPayload) { + return tokenSchema.parse(payload) + } +} diff --git a/src/infra/auth/public.ts b/src/infra/auth/public.ts new file mode 100644 index 0000000..91c2399 --- /dev/null +++ b/src/infra/auth/public.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common' + +export const IS_PUBLIC_KEY = 'isPublic' +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true) diff --git a/src/infra/cryptography/bcrypt-encrypter.ts b/src/infra/cryptography/bcrypt-encrypter.ts new file mode 100644 index 0000000..43ecea8 --- /dev/null +++ b/src/infra/cryptography/bcrypt-encrypter.ts @@ -0,0 +1,14 @@ +import { Encrypter } from '@/domain/core/deliveries-and-orders/application/cryptography/encrypter' +import { Injectable } from '@nestjs/common' +import { hash, compare } from 'bcryptjs' + +@Injectable() +export class BcryptEncrypter implements Encrypter { + hash(painText: string): Promise { + return hash(painText, 8) + } + + compare(painText: string, hash: string): Promise { + return compare(painText, hash) + } +} diff --git a/src/infra/cryptography/cryptography.module.ts b/src/infra/cryptography/cryptography.module.ts new file mode 100644 index 0000000..dd58299 --- /dev/null +++ b/src/infra/cryptography/cryptography.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common' +import { BcryptEncrypter } from './bcrypt-encrypter' +import { JwtEncoder } from './jwt-encoder' +import { Encrypter } from '@/domain/core/deliveries-and-orders/application/cryptography/encrypter' +import { Encoder } from '@/domain/core/deliveries-and-orders/application/cryptography/encoder' + +@Module({ + providers: [ + { provide: Encrypter, useClass: BcryptEncrypter }, + { provide: Encoder, useClass: JwtEncoder }, + ], + exports: [Encrypter, Encoder], +}) +export class CryptographyModule {} diff --git a/src/infra/cryptography/jwt-encoder.ts b/src/infra/cryptography/jwt-encoder.ts new file mode 100644 index 0000000..b696ed4 --- /dev/null +++ b/src/infra/cryptography/jwt-encoder.ts @@ -0,0 +1,13 @@ +import { Encoder } from '@/domain/core/deliveries-and-orders/application/cryptography/encoder' +import { JwtService } from '@nestjs/jwt' +import { TokenPayload } from '../auth/jwt.strategy' +import { Injectable } from '@nestjs/common' + +@Injectable() +export class JwtEncoder implements Encoder { + constructor(private jwt: JwtService) {} + + async encode(payload: TokenPayload): Promise { + return this.jwt.sign(payload) + } +} diff --git a/src/infra/database/prisma/mappers/prisma-update-order-mapper.ts b/src/infra/database/prisma/mappers/prisma-update-order-mapper.ts index a7f76ec..181055a 100644 --- a/src/infra/database/prisma/mappers/prisma-update-order-mapper.ts +++ b/src/infra/database/prisma/mappers/prisma-update-order-mapper.ts @@ -38,7 +38,10 @@ export class PrismaUpdateOrderMapper { } static domainToPrisma(update: UpdateOrder): PrismaUpdates { - const changes = JSON.stringify(update.changes) + const changes = JSON.stringify({ + before: update.changes.before.toJson(), + after: update.changes.after.toJson(), + }) const prismaUpdate: PrismaUpdates = { changes, diff --git a/src/infra/database/prisma/repositories/prisma-order-repositories/prisma-orders-repository.ts b/src/infra/database/prisma/repositories/prisma-order-repositories/prisma-orders-repository.ts index 4762a93..ced76f0 100644 --- a/src/infra/database/prisma/repositories/prisma-order-repositories/prisma-orders-repository.ts +++ b/src/infra/database/prisma/repositories/prisma-order-repositories/prisma-orders-repository.ts @@ -60,7 +60,7 @@ export class PrismaOrdersRepository implements OrdersRepository { courierId: string, ): Promise { const data = await this.prisma.$queryRaw` - SELECT * IN orders + SELECT * from orders WHERE ( 6371 * acos( cos( radians(${latitude}) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(${longitude}) ) + sin( radians(${latitude}) ) * sin( radians( latitude ) ) ) ) <= 10 AND courier_id = ${courierId} ` diff --git a/src/infra/http/controllers/@exports/controllers.exports.ts b/src/infra/http/controllers/@exports/controllers.exports.ts index e69de29..85001d1 100644 --- a/src/infra/http/controllers/@exports/controllers.exports.ts +++ b/src/infra/http/controllers/@exports/controllers.exports.ts @@ -0,0 +1,48 @@ +import { ModuleMetadata } from '@nestjs/common' +import { AuthenticateController } from '../auth-and-register-controllers/authenticate.controller' +import { RegisterController } from '../auth-and-register-controllers/register.controller' +import { DeleteUserController } from '../user-controllers/delete-user.controller' +import { FetchUsersController } from '../user-controllers/fetch-users.controller' +import { FindUserController } from '../user-controllers/find-user.controller' +import { UpdateUserPasswordController } from '../user-controllers/update-user-password.controller' +import { UpdateUserController } from '../user-controllers/update-user.controller' +import { CollectOrderController } from '../order-controllers/collect-order.controller' +import { CreateOrderController } from '../order-controllers/create-order.controller' +import { DeleteOrderController } from '../order-controllers/delete-order.controller' +import { DeliverOrderController } from '../order-controllers/deliver-order.controller' +import { FetchNearbyOrdersController } from '../order-controllers/fetch-nearby-orders.controller' +import { FetchOrdersController } from '../order-controllers/fetch-orders.controller' +import { FindOrderController } from '../order-controllers/find-order.controller' +import { MarkOrderAsAwaitingForPickupController } from '../order-controllers/mark-order-as-awaiting-for-pickup.controller' +import { ReturnOrderController } from '../order-controllers/return-order.controller' + +const authAndRegisterControllers: NonNullable = [ + AuthenticateController, + RegisterController, +] + +const userControllers: NonNullable = [ + DeleteUserController, + FetchUsersController, + FindUserController, + UpdateUserPasswordController, + UpdateUserController, +] + +const orderControllers: NonNullable = [ + CollectOrderController, + CreateOrderController, + DeleteOrderController, + DeliverOrderController, + FetchNearbyOrdersController, + FetchOrdersController, + FindOrderController, + MarkOrderAsAwaitingForPickupController, + ReturnOrderController, +] + +export const controllers: NonNullable = [ + ...authAndRegisterControllers, + ...userControllers, + ...orderControllers, +] diff --git a/src/infra/http/controllers/auth-and-register-controllers/authenticate.controller.ts b/src/infra/http/controllers/auth-and-register-controllers/authenticate.controller.ts new file mode 100644 index 0000000..a6392c1 --- /dev/null +++ b/src/infra/http/controllers/auth-and-register-controllers/authenticate.controller.ts @@ -0,0 +1,75 @@ +import { + BadRequestException, + Body, + Controller, + HttpCode, + NotFoundException, + Post, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { validaCPF } from '../../utils/isCPF' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { Public } from '@/infra/auth/public' +import { Either } from '@/core/either' +import { AuthenticateAdmUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/auth-and-register/adm/authenticate-adm-use-case' +import { AuthenticateRecipientUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/auth-and-register/recipient/authenticate-recipient-use-case' +import { AuthenticateCourierUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/auth-and-register/courier/authenticate-courier-use-case' + +const bodySchema = z.object({ + cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), + password: z.string(), + role: z.enum(['recipient', 'adm', 'courier']), +}) + +type BodySchema = z.infer + +const pipe = new ZodValidationPipe(bodySchema) + +@Controller('/sessions') +@Public() +export class AuthenticateController { + constructor( + private authenticateCourier: AuthenticateCourierUseCase, + private authenticateRecipient: AuthenticateRecipientUseCase, + private authenticateAdm: AuthenticateAdmUseCase, + ) {} + + @Post() + @HttpCode(200) + async handle(@Body(pipe) { role, ...body }: BodySchema) { + let resp: Either< + ResourceNotFoundError | UnauthorizedError, + { + token: string + } + > + if (role === 'courier') { + resp = await this.authenticateCourier.execute(body) + } else if (role === 'recipient') { + resp = await this.authenticateRecipient.execute(body) + } else { + resp = await this.authenticateAdm.execute(body) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const token = resp.value.token + + return { token } + } + } +} diff --git a/src/infra/http/controllers/auth-and-register-controllers/register.controller.ts b/src/infra/http/controllers/auth-and-register-controllers/register.controller.ts new file mode 100644 index 0000000..6f13c91 --- /dev/null +++ b/src/infra/http/controllers/auth-and-register-controllers/register.controller.ts @@ -0,0 +1,79 @@ +import { RegisterCourierUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/auth-and-register/courier/register-courier-use-case' +import { + BadRequestException, + Body, + Controller, + HttpCode, + NotFoundException, + Post, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { validaCPF } from '../../utils/isCPF' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { RegisterRecipientUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/auth-and-register/recipient/register-recipient-use-case' +import { Either } from '@/core/either' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' + +const bodySchema = z.object({ + cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), + name: z.string(), + password: z.string(), + role: z.enum(['recipient', 'adm', 'courier']), +}) + +type BodySchema = z.infer + +const pipe = new ZodValidationPipe(bodySchema) + +@Controller('/register') +// @Public() +export class RegisterController { + constructor( + private registerCourier: RegisterCourierUseCase, + private registerRecipient: RegisterRecipientUseCase, + ) {} + + @Post() + @HttpCode(201) + async handle( + @Body(pipe) { role, ...body }: BodySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + let resp: Either + if (role === 'courier') { + resp = await this.registerCourier.execute({ + ...body, + requestResponsibleId, + }) + } else if (role === 'recipient') { + resp = await this.registerRecipient.execute({ + ...body, + requestResponsibleId, + }) + } else { + throw new BadRequestException(`cannot register an admin`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/order-controllers/collect-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/collect-order.controller.e2e-spec.ts new file mode 100644 index 0000000..fc79e5c --- /dev/null +++ b/src/infra/http/controllers/order-controllers/collect-order.controller.e2e-spec.ts @@ -0,0 +1,120 @@ +import { AppModule } from '@/infra/app.module' +import { PrismaAdmMapper } from '@/infra/database/prisma/mappers/prisma-adm-mapper' +import { PrismaService } from '@/infra/database/prisma/prisma.service' +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import { makeAdm } from 'test/factories/entities/makeAdm' +import { makeAuthenticateRequest } from '../../factories/requests/auth-and-register-factories/make-authenticate-request' +import { Cpf } from '@/domain/core/deliveries-and-orders/enterprise/entities/value-objects/cpf' +import { makeRegisterRequest } from '../../factories/requests/auth-and-register-factories/make-register-request' +import { makeCreateOrderRequest } from '../../factories/requests/order-request-factories/make-create-order-request' +import { makeCollectOrderRequest } from '../../factories/requests/order-request-factories/make-collect-order-request' + +describe('collect order controller', () => { + let app: INestApplication + let prisma: PrismaService + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule], + }).compile() + + app = moduleRef.createNestApplication() + prisma = moduleRef.get(PrismaService) + + await app.init() + }) + + test('PATCH /orders/:id/collect', async () => { + await prisma.prismaUser.create({ + data: PrismaAdmMapper.domainToPrisma( + makeAdm({ + cpf: new Cpf('20635940078'), + password: '123', + }), + ), + }) + + const authAdmResp = await makeAuthenticateRequest(app, { + body: { + cpf: '20635940078', + password: '123', + role: 'adm', + }, + }) + + const token = authAdmResp.body.token + + const createRecipientResp = await makeRegisterRequest(app, { + token, + body: { + cpf: '66967772023', + name: 'recipient teste', + password: '123', + role: 'recipient', + }, + }) + + const createCourierResp = await makeRegisterRequest(app, { + token, + body: { + cpf: '29241359072', + name: 'courier teste', + password: '123', + role: 'courier', + }, + }) + + const recipient = ( + await prisma.prismaUser.findMany({ + where: { + role: 'recipient', + }, + }) + )[0] + + const courier = ( + await prisma.prismaUser.findMany({ + where: { + role: 'courier', + }, + }) + )[0] + + const createOrderResp = await makeCreateOrderRequest(app, { + token, + body: { + address: { + cep: '004475900', + street: 'rua dos bobos', + number: 'numero zero', + city: 'nao tinha cidade', + neighborhood: 'nao tinha bairro', + state: 'SP', + coordinates: { + latitude: 1, + longitude: 1, + }, + }, + courierId: courier.id, + recipientId: recipient.id, + }, + }) + + const order = (await prisma.prismaOrder.findMany())[0] + + const collectOrderResp = await makeCollectOrderRequest(app, { + orderId: order.id, + token, + }) + + const orderUpdated = (await prisma.prismaOrder.findMany())[0] + + expect(authAdmResp.statusCode).toEqual(200) + expect(createRecipientResp.statusCode).toEqual(201) + expect(createCourierResp.statusCode).toEqual(201) + expect(createOrderResp.statusCode).toEqual(201) + expect(collectOrderResp.statusCode).toEqual(204) + expect(orderUpdated.collected).toEqual(expect.any(Date)) + }) +}) diff --git a/src/infra/http/controllers/order-controllers/collect-order.controller.ts b/src/infra/http/controllers/order-controllers/collect-order.controller.ts new file mode 100644 index 0000000..a4ef387 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/collect-order.controller.ts @@ -0,0 +1,89 @@ +import { + BadRequestException, + Controller, + HttpCode, + InternalServerErrorException, + NotFoundException, + Param, + Patch, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { CollectOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case' +import { OrderAwaitingPickupError } from '@/core/errors/errors/order-errors/order-awaiting-for-pickup-error' +import { OrderIsClosedError } from '@/core/errors/errors/order-errors/order-is-closed-error' +import { InternalServerError } from '@/core/errors/errors/internal-server-error' +import { OrderAlreadyCollectedError } from '@/core/errors/errors/order-errors/order-already-collected-error' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id/collect') +// @Public() +export class CollectOrderController { + constructor(private collectOrder: CollectOrderUseCase) {} + + @Patch() + @HttpCode(204) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.collectOrder.execute({ + requestResponsibleId, + orderId: id, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + if (value instanceof OrderAlreadyCollectedError) { + throw new BadRequestException({ message: value.message }) + } + + const isAwaitingError = value instanceof OrderAwaitingPickupError + const isClosedError = value instanceof OrderIsClosedError + + const isOrderError = isAwaitingError || isClosedError + + if (isOrderError) { + throw new BadRequestException({ message: value.message }) + } + + if (value instanceof InternalServerError) { + throw new InternalServerErrorException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/order-controllers/create-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/create-order.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/create-order.controller.ts b/src/infra/http/controllers/order-controllers/create-order.controller.ts new file mode 100644 index 0000000..5095036 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/create-order.controller.ts @@ -0,0 +1,87 @@ +import { + BadRequestException, + Body, + Controller, + HttpCode, + NotFoundException, + Post, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { CreateOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/create-order-use-case' + +import { + Address, + addressCreationPropsSchema, +} from '@/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address' +import UniqueEntityId from '@/core/entities/unique-entity-id' + +// const paramsSchema = z.object({ +// id: z.string().uuid(), +// }) + +/* + bodySchema example + + address: makeAddress(), + courierId: new UniqueEntityId('123'), + recipientId: new UniqueEntityId('1188'), +*/ + +const bodySchema = z.object({ + address: addressCreationPropsSchema, + courierId: z.string(), + recipientId: z.string(), +}) + +// type ParamsSchema = z.infer +type BodySchema = z.infer + +// const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders') +// @Public() +export class CreateOrderController { + constructor(private createOrder: CreateOrderUseCase) {} + + @Post() + @HttpCode(201) + async handle( + @Body(pipeBody) body: BodySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.createOrder.execute({ + requestResponsibleId, + creationProps: { + address: new Address(body.address), + courierId: new UniqueEntityId(body.courierId), + recipientId: new UniqueEntityId(body.recipientId), + }, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/order-controllers/delete-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/delete-order.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/delete-order.controller.ts b/src/infra/http/controllers/order-controllers/delete-order.controller.ts new file mode 100644 index 0000000..39b8484 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/delete-order.controller.ts @@ -0,0 +1,68 @@ +import { + BadRequestException, + Controller, + Delete, + HttpCode, + NotFoundException, + Param, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { DeleteOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/delete-order-use-case' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id') +// @Public() +export class DeleteOrderController { + constructor(private deleteOrder: DeleteOrderUseCase) {} + + @Delete() + @HttpCode(204) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.deleteOrder.execute({ + requestResponsibleId, + orderId: id, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/order-controllers/deliver-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/deliver-order.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/deliver-order.controller.ts b/src/infra/http/controllers/order-controllers/deliver-order.controller.ts new file mode 100644 index 0000000..20c1134 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/deliver-order.controller.ts @@ -0,0 +1,77 @@ +import { + BadRequestException, + Controller, + HttpCode, + NotFoundException, + Param, + Patch, + Query, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { DeliverOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/deliver-order-use-case' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +const querySchema = z.object({ + attachmentId: z.string().uuid(), +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id/deliver') +// @Public() +export class DeliverOrderController { + constructor(private deliverOrder: DeliverOrderUseCase) {} + + @Patch() + @HttpCode(204) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + @Query(pipeQuery) { attachmentId }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.deliverOrder.execute({ + requestResponsibleId, + orderId: id, + attachmentId, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.ts b/src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.ts new file mode 100644 index 0000000..ab1cb32 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/fetch-nearby-orders.controller.ts @@ -0,0 +1,89 @@ +import { + BadRequestException, + Controller, + Get, + HttpCode, + NotFoundException, + Param, + Query, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' + +import { FetchNearbyOrdersUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/fetch-nearby-orders-use-case' +import { + latitudeSchema, + longitudeSchema, +} from '@/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates' + +const paramsSchema = z.object({ + courierId: z.string().uuid(), +}) + +const querySchema = z.object({ + latitude: latitudeSchema, + longitude: longitudeSchema, +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:courierId/nearby') +// @Public() +export class FetchNearbyOrdersController { + constructor(private fetchNearbyOrders: FetchNearbyOrdersUseCase) {} + + @Get() + @HttpCode(200) + async handle( + @Param(pipeParams) { courierId }: ParamsSchema, + @Query(pipeQuery) { latitude, longitude }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.fetchNearbyOrders.execute({ + requestResponsibleId, + coordinates: { + latitude, + longitude, + }, + courierId, + requestResponsibleRole: user.role, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const value = resp.value + + return { orders: value.orders } + } + } +} diff --git a/src/infra/http/controllers/order-controllers/fetch-orders.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/fetch-orders.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/fetch-orders.controller.ts b/src/infra/http/controllers/order-controllers/fetch-orders.controller.ts new file mode 100644 index 0000000..956c069 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/fetch-orders.controller.ts @@ -0,0 +1,110 @@ +import { + BadRequestException, + Controller, + Get, + HttpCode, + NotFoundException, + Param, + Query, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { FetchCourierOrdersUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/fetch-courier-orders-use-case' +import { FetchRecipientOrdersUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/fetch-recipient-orders-use-case' +import { FetchOrdersUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/fetch-orders-use-case' +import { userRoleSchema } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { Either } from '@/core/either' +import { Order } from '@/domain/core/deliveries-and-orders/enterprise/entities/order' + +const paramsSchema = z.object({ + role: z.union([userRoleSchema, z.enum(['all'])]), +}) + +const querySchema = z.object({ + userId: z.string().uuid().optional(), +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:role') +// @Public() +export class FetchOrdersController { + constructor( + private fetchOrders: FetchOrdersUseCase, + private fetchCourierOrders: FetchCourierOrdersUseCase, + private fetchRecipientOrders: FetchRecipientOrdersUseCase, + ) {} + + @Get() + @HttpCode(200) + async handle( + @Param(pipeParams) { role }: ParamsSchema, + @Query(pipeQuery) { userId }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + console.log('requestResponsibleId', requestResponsibleId) + + let resp: Either< + ResourceNotFoundError | UnauthorizedError, + { orders: Order[] } + > + + if (role === 'all') { + resp = await this.fetchOrders.execute({ + requestResponsibleId, + }) + } else if (role === 'courier') { + if (!userId) throw new BadRequestException('param id was not passed') + resp = await this.fetchCourierOrders.execute({ + requestResponsibleId, + courierId: userId, + requestResponsibleRole: user.role, + }) + } else if (role === 'recipient') { + if (!userId) throw new BadRequestException('param id was not passed') + resp = await this.fetchRecipientOrders.execute({ + requestResponsibleId, + recipientId: userId, + requestResponsibleRole: user.role, + }) + } else { + throw new BadRequestException('admins have not orders linked to them') + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const value = resp.value + + return { orders: value.orders } + } + } +} diff --git a/src/infra/http/controllers/order-controllers/find-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/find-order.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/find-order.controller.ts b/src/infra/http/controllers/order-controllers/find-order.controller.ts new file mode 100644 index 0000000..1d6be76 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/find-order.controller.ts @@ -0,0 +1,78 @@ +import { + BadRequestException, + Controller, + Get, + HttpCode, + NotFoundException, + Param, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { FindOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/find-order-use-case' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +// const querySchema = z.object({ +// latitude: latitudeSchema, +// longitude: longitudeSchema, +// }) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +// type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +// const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id/find') +// @Public() +export class FindOrderController { + constructor(private findOrder: FindOrderUseCase) {} + + @Get() + @HttpCode(200) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + // @Query(pipeQuery) { latitude, longitude }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.findOrder.execute({ + requestResponsibleId, + orderId: id, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const value = resp.value + + return { order: value.order } + } + } +} diff --git a/src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.ts b/src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.ts new file mode 100644 index 0000000..ea3cd4e --- /dev/null +++ b/src/infra/http/controllers/order-controllers/mark-order-as-awaiting-for-pickup.controller.ts @@ -0,0 +1,79 @@ +import { + BadRequestException, + Controller, + HttpCode, + NotFoundException, + Param, + Patch, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { MarkOrderAsAwaitingForPickupUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/mark-order-as-awaiting-for-pickup-use-case' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +// const querySchema = z.object({ +// latitude: latitudeSchema, +// longitude: longitudeSchema, +// }) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +// type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +// const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id/awaiting') +// @Public() +export class MarkOrderAsAwaitingForPickupController { + constructor( + private markOrderAsAwaitingForPickup: MarkOrderAsAwaitingForPickupUseCase, + ) {} + + @Patch() + @HttpCode(204) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + // @Query(pipeQuery) { latitude, longitude }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.markOrderAsAwaitingForPickup.execute({ + requestResponsibleId, + orderId: id, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + // return { order: value.order } + } + } +} diff --git a/src/infra/http/controllers/order-controllers/return-order.controller.e2e-spec.ts b/src/infra/http/controllers/order-controllers/return-order.controller.e2e-spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/http/controllers/order-controllers/return-order.controller.ts b/src/infra/http/controllers/order-controllers/return-order.controller.ts new file mode 100644 index 0000000..d50ef54 --- /dev/null +++ b/src/infra/http/controllers/order-controllers/return-order.controller.ts @@ -0,0 +1,78 @@ +import { + BadRequestException, + Controller, + HttpCode, + NotFoundException, + Param, + Patch, + Query, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { ReturnOrderUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/order/return-order-use-case' + +const paramsSchema = z.object({ + id: z.string().uuid(), +}) + +const querySchema = z.object({ + returnCause: z.string().min(10), +}) + +// const bodySchema = z.object({ +// cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), +// name: z.string(), +// }) + +type ParamsSchema = z.infer +type QuerySchema = z.infer +// type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeQuery = new ZodValidationPipe(querySchema) +// const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/orders/:id/return') +// @Public() +export class ReturnOrderController { + constructor(private returnOrder: ReturnOrderUseCase) {} + + @Patch() + @HttpCode(204) + async handle( + @Param(pipeParams) { id }: ParamsSchema, + @Query(pipeQuery) { returnCause }: QuerySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + const resp = await this.returnOrder.execute({ + requestResponsibleId, + orderId: id, + returnCause, + }) + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + // return { order: value.order } + } + } +} diff --git a/src/infra/http/controllers/user-controllers/delete-user.controller.ts b/src/infra/http/controllers/user-controllers/delete-user.controller.ts new file mode 100644 index 0000000..dc64775 --- /dev/null +++ b/src/infra/http/controllers/user-controllers/delete-user.controller.ts @@ -0,0 +1,78 @@ +import { + BadRequestException, + Controller, + Delete, + HttpCode, + NotFoundException, + Param, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { DeleteCourierUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/courier/delete-courier-use-case' +import { Either } from '@/core/either' +import { DeleteRecipientUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/recipient/delete-recipient-use-case' + +const paramsSchema = z.object({ + role: z.enum(['recipient', 'adm', 'courier']), + id: z.string().uuid(), +}) + +type ParamsSchema = z.infer + +const pipe = new ZodValidationPipe(paramsSchema) + +@Controller('/users/:role/:id') +// @Public() +export class DeleteUserController { + constructor( + private deleteCourier: DeleteCourierUseCase, + private deleteRecipient: DeleteRecipientUseCase, + ) {} + + @Delete() + @HttpCode(204) + async handle( + @Param(pipe) { id, role }: ParamsSchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + let resp: Either + + if (role === 'courier') { + resp = await this.deleteCourier.execute({ + requestResponsibleId, + courierId: id, + }) + } else if (role === 'recipient') { + resp = await this.deleteRecipient.execute({ + requestResponsibleId, + recipientId: id, + }) + } else { + throw new BadRequestException(`cannot delete an admin`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/user-controllers/fetch-users.controller.ts b/src/infra/http/controllers/user-controllers/fetch-users.controller.ts new file mode 100644 index 0000000..90c1c9d --- /dev/null +++ b/src/infra/http/controllers/user-controllers/fetch-users.controller.ts @@ -0,0 +1,84 @@ +import { + BadRequestException, + Controller, + Get, + HttpCode, + NotFoundException, + Param, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { Either } from '@/core/either' +import { FetchCouriersUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/courier/fetch-couriers-use-case' +import { FetchRecipientsUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/recipient/fetch-recipients-use-case' +import { Courier } from '@/domain/core/deliveries-and-orders/enterprise/entities/courier' +import { Recipient } from '@/domain/core/deliveries-and-orders/enterprise/entities/recipient' + +const paramsSchema = z.object({ + role: z.enum(['recipient', 'adm', 'courier']), +}) + +type ParamsSchema = z.infer + +const pipe = new ZodValidationPipe(paramsSchema) + +@Controller('/users/:role') +// @Public() +export class FetchUsersController { + constructor( + private fetchCouriers: FetchCouriersUseCase, + private fetchRecipients: FetchRecipientsUseCase, + ) {} + + @Get() + @HttpCode(200) + async handle( + @Param(pipe) { role }: ParamsSchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + let resp: Either< + ResourceNotFoundError | UnauthorizedError, + { couriers: Courier[] } | { recipients: Recipient[] } + > + + if (role === 'courier') { + resp = await this.fetchCouriers.execute({ + requestResponsibleId, + }) + } else if (role === 'recipient') { + resp = await this.fetchRecipients.execute({ + requestResponsibleId, + }) + } else { + throw new BadRequestException(`cannot fetch admins`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const value = resp.value[role + 's'] + + return { + [role + 's']: value, + } + } + } +} diff --git a/src/infra/http/controllers/user-controllers/find-user.controller.ts b/src/infra/http/controllers/user-controllers/find-user.controller.ts new file mode 100644 index 0000000..bac508d --- /dev/null +++ b/src/infra/http/controllers/user-controllers/find-user.controller.ts @@ -0,0 +1,87 @@ +import { + BadRequestException, + Controller, + Get, + HttpCode, + NotFoundException, + Param, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { Either } from '@/core/either' +import { Courier } from '@/domain/core/deliveries-and-orders/enterprise/entities/courier' +import { Recipient } from '@/domain/core/deliveries-and-orders/enterprise/entities/recipient' +import { FindCourierUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/courier/find-courier-use-case' +import { FindRecipientUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/recipient/find-recipient-use-case' + +const paramsSchema = z.object({ + role: z.enum(['recipient', 'adm', 'courier']), + id: z.string().uuid(), +}) + +type ParamsSchema = z.infer + +const pipe = new ZodValidationPipe(paramsSchema) + +@Controller('/users/:role/:id') +// @Public() +export class FindUserController { + constructor( + private findCourier: FindCourierUseCase, + private findRecipient: FindRecipientUseCase, + ) {} + + @Get() + @HttpCode(200) + async handle( + @Param(pipe) { role, id }: ParamsSchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + let resp: Either< + ResourceNotFoundError | UnauthorizedError, + { courier: Courier } | { recipient: Recipient } + > + + if (role === 'courier') { + resp = await this.findCourier.execute({ + requestResponsibleId, + courierId: id, + }) + } else if (role === 'recipient') { + resp = await this.findRecipient.execute({ + requestResponsibleId, + recipientId: id, + }) + } else { + throw new BadRequestException(`cannot find an admin`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + const value = resp.value[role] + + return { + [role]: value, + } + } + } +} diff --git a/src/infra/http/controllers/user-controllers/update-user-password.controller.ts b/src/infra/http/controllers/user-controllers/update-user-password.controller.ts new file mode 100644 index 0000000..b07b459 --- /dev/null +++ b/src/infra/http/controllers/user-controllers/update-user-password.controller.ts @@ -0,0 +1,88 @@ +import { + BadRequestException, + Body, + Controller, + HttpCode, + NotFoundException, + Param, + Patch, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { Either } from '@/core/either' +import { UpdateCourierPasswordUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/courier/update-courier-password-use-case' +import { UpdateRecipientPasswordUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-password-use-case' + +const paramsSchema = z.object({ + role: z.enum(['recipient', 'adm', 'courier']), + id: z.string().uuid(), +}) + +const bodySchema = z.object({ + password: z.string(), +}) + +type ParamsSchema = z.infer +type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/users/:role/:id/update-password') +// @Public() +export class UpdateUserPasswordController { + constructor( + private updateCourierPassword: UpdateCourierPasswordUseCase, + private updateRecipientPassword: UpdateRecipientPasswordUseCase, + ) {} + + @Patch() + @HttpCode(204) + async handle( + @Param(pipeParams) { role, id }: ParamsSchema, + @Body(pipeBody) body: BodySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + let resp: Either + + if (role === 'courier') { + resp = await this.updateCourierPassword.execute({ + requestResponsibleId, + courierId: id, + ...body, + }) + } else if (role === 'recipient') { + resp = await this.updateRecipientPassword.execute({ + requestResponsibleId, + recipientId: id, + ...body, + }) + } else { + throw new BadRequestException(`cannot update an admin password`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/controllers/user-controllers/update-user.controller.ts b/src/infra/http/controllers/user-controllers/update-user.controller.ts new file mode 100644 index 0000000..ff41bb6 --- /dev/null +++ b/src/infra/http/controllers/user-controllers/update-user.controller.ts @@ -0,0 +1,90 @@ +import { + BadRequestException, + Body, + Controller, + HttpCode, + NotFoundException, + Param, + Put, + UnauthorizedException, +} from '@nestjs/common' +import z from 'zod' +import { ZodValidationPipe } from '@/infra/pipes/zod-validation-pipe' +import { UnauthorizedError } from '@/core/errors/errors/unauthorized-error' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +// import { Public } from '@/infra/auth/public' +import { CurrentUser } from '@/infra/auth/current-user-decorator' +import { TokenPayload } from '@/infra/auth/jwt.strategy' +import { Either } from '@/core/either' +import { UpdateCourierUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/courier/update-courier-use-case' +import { UpdateRecipientUseCase } from '@/domain/core/deliveries-and-orders/application/use-cases/recipient/update-recipient-use-case' +import { validaCPF } from '../../utils/isCPF' + +const paramsSchema = z.object({ + role: z.enum(['recipient', 'adm', 'courier']), + id: z.string().uuid(), +}) + +const bodySchema = z.object({ + cpf: z.string().refine((cpf) => validaCPF(cpf), 'must be a valid cpf'), + name: z.string(), +}) + +type ParamsSchema = z.infer +type BodySchema = z.infer + +const pipeParams = new ZodValidationPipe(paramsSchema) +const pipeBody = new ZodValidationPipe(bodySchema) + +@Controller('/users/:role/:id') +// @Public() +export class UpdateUserController { + constructor( + private updateCourier: UpdateCourierUseCase, + private updateRecipient: UpdateRecipientUseCase, + ) {} + + @Put() + @HttpCode(204) + async handle( + @Param(pipeParams) { role, id }: ParamsSchema, + @Body(pipeBody) body: BodySchema, + @CurrentUser() user: TokenPayload, + ) { + const requestResponsibleId = user.sub + + let resp: Either + + if (role === 'courier') { + resp = await this.updateCourier.execute({ + requestResponsibleId, + courierId: id, + ...body, + }) + } else if (role === 'recipient') { + resp = await this.updateRecipient.execute({ + requestResponsibleId, + recipientId: id, + ...body, + }) + } else { + throw new BadRequestException(`cannot update an admin`) + } + + if (resp.isLeft()) { + const value = resp.value + if (value instanceof ResourceNotFoundError) { + throw new NotFoundException({ message: value.message }) + } + if (value instanceof UnauthorizedError) { + throw new UnauthorizedException({ message: value.message }) + } + + throw new BadRequestException() + } + + if (resp.isRight()) { + // const value = resp.value + } + } +} diff --git a/src/infra/http/factories/requests/auth-and-register-factories/make-authenticate-request.ts b/src/infra/http/factories/requests/auth-and-register-factories/make-authenticate-request.ts new file mode 100644 index 0000000..8e6c949 --- /dev/null +++ b/src/infra/http/factories/requests/auth-and-register-factories/make-authenticate-request.ts @@ -0,0 +1,20 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeAuthenticateRequestProps { + body: { + cpf: string + password: string + role: UserRoles + } +} + +export async function makeAuthenticateRequest( + app: INestApplication, + { body }: MakeAuthenticateRequestProps, +) { + const resp = await request(app.getHttpServer()).post(`/sessions`).send(body) + + return resp +} diff --git a/src/infra/http/factories/requests/auth-and-register-factories/make-register-request.ts b/src/infra/http/factories/requests/auth-and-register-factories/make-register-request.ts new file mode 100644 index 0000000..536f684 --- /dev/null +++ b/src/infra/http/factories/requests/auth-and-register-factories/make-register-request.ts @@ -0,0 +1,24 @@ +import { INestApplication } from '@nestjs/common' +import { UserRoles } from '@prisma/client' +import request from 'supertest' + +export interface MakeRegisterRequestProps { + body: { + cpf: string + name: string + password: string + role: UserRoles + } + token: string +} + +export async function makeRegisterRequest( + app: INestApplication, + { token }: MakeRegisterRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/register`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-collect-order-request.ts b/src/infra/http/factories/requests/order-request-factories/make-collect-order-request.ts new file mode 100644 index 0000000..02b781d --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-collect-order-request.ts @@ -0,0 +1,18 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeCollectOrderRequestProps { + orderId: string + token: string +} + +export async function makeCollectOrderRequest( + app: INestApplication, + { orderId, token }: MakeCollectOrderRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/orders/${orderId}/collect`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-create-order-request.ts b/src/infra/http/factories/requests/order-request-factories/make-create-order-request.ts new file mode 100644 index 0000000..0bb6403 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-create-order-request.ts @@ -0,0 +1,24 @@ +import { AddressCreationProps } from '@/domain/core/deliveries-and-orders/enterprise/entities/value-objects/address' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeCreateOrderRequestProps { + body: { + address: AddressCreationProps + courierId: string + recipientId: string + } + token: string +} + +export async function makeCreateOrderRequest( + app: INestApplication, + { body, token }: MakeCreateOrderRequestProps, +) { + const resp = await request(app.getHttpServer()) + .post(`/orders/`) + .set('Authorization', `Bearer ${token}`) + .send(body) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-delete-order-request.ts b/src/infra/http/factories/requests/order-request-factories/make-delete-order-request.ts new file mode 100644 index 0000000..0ea551b --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-delete-order-request.ts @@ -0,0 +1,18 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeDeleteOrderRequestProps { + orderId: string + token: string +} + +export async function makeDeleteOrderRequest( + app: INestApplication, + { orderId, token }: MakeDeleteOrderRequestProps, +) { + const resp = await request(app.getHttpServer()) + .delete(`/orders/${orderId}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-deliver-order-request.ts b/src/infra/http/factories/requests/order-request-factories/make-deliver-order-request.ts new file mode 100644 index 0000000..ca25e61 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-deliver-order-request.ts @@ -0,0 +1,19 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeDeliverOrderRequestProps { + orderId: string + attachmentId: string + token: string +} + +export async function makeDeliverOrderRequest( + app: INestApplication, + { orderId, attachmentId, token }: MakeDeliverOrderRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/orders/${orderId}/deliver?attachmentId=${attachmentId}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-fetch-nearby-orders-request.ts b/src/infra/http/factories/requests/order-request-factories/make-fetch-nearby-orders-request.ts new file mode 100644 index 0000000..3c6daa0 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-fetch-nearby-orders-request.ts @@ -0,0 +1,26 @@ +import { Coordinates } from '@/domain/core/deliveries-and-orders/enterprise/entities/value-objects/coordinates' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeFetchNearbyOrdersRequestProps { + courierId: string + coordinates: Coordinates['raw'] + token: string +} + +export async function makeFetchNearbyOrdersRequest( + app: INestApplication, + { + courierId, + token, + coordinates: { latitude, longitude }, + }: MakeFetchNearbyOrdersRequestProps, +) { + const resp = await request(app.getHttpServer()) + .get( + `/orders/${courierId}/nearby?latitude=${latitude}&longitude=${longitude}`, + ) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-fetch-orders-request.ts b/src/infra/http/factories/requests/order-request-factories/make-fetch-orders-request.ts new file mode 100644 index 0000000..e9c764d --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-fetch-orders-request.ts @@ -0,0 +1,20 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeFetchOrdersRequestProps { + role: UserRoles | 'all' + userId?: string + token: string +} + +export async function makeFetchOrdersRequest( + app: INestApplication, + { role, token, userId }: MakeFetchOrdersRequestProps, +) { + const resp = await request(app.getHttpServer()) + .get(`/orders/${role}/${userId ? '?userId=' + userId : ''}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-find-order-request.ts b/src/infra/http/factories/requests/order-request-factories/make-find-order-request.ts new file mode 100644 index 0000000..eaabda1 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-find-order-request.ts @@ -0,0 +1,18 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeFindOrdersRequestProps { + orderId: string + token: string +} + +export async function makeFindOrdersRequest( + app: INestApplication, + { token, orderId }: MakeFindOrdersRequestProps, +) { + const resp = await request(app.getHttpServer()) + .get(`/orders/${orderId}/find`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/make-mark-order-as-awaiting-for-pickup-request.ts b/src/infra/http/factories/requests/order-request-factories/make-mark-order-as-awaiting-for-pickup-request.ts new file mode 100644 index 0000000..3713e37 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/make-mark-order-as-awaiting-for-pickup-request.ts @@ -0,0 +1,18 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeMarkOrderAsAwaitingForPickupRequestProps { + orderId: string + token: string +} + +export async function makeMarkOrderAsAwaitingForPickupRequest( + app: INestApplication, + { token, orderId }: MakeMarkOrderAsAwaitingForPickupRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/orders/${orderId}/awaiting`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/order-request-factories/return-order-request copy.ts b/src/infra/http/factories/requests/order-request-factories/return-order-request copy.ts new file mode 100644 index 0000000..6242ea6 --- /dev/null +++ b/src/infra/http/factories/requests/order-request-factories/return-order-request copy.ts @@ -0,0 +1,18 @@ +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeReturnOrderRequestProps { + orderId: string + token: string +} + +export async function makeReturnOrderRequest( + app: INestApplication, + { token, orderId }: MakeReturnOrderRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/orders/${orderId}/return`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/user-factories/delete-user-request.ts b/src/infra/http/factories/requests/user-factories/delete-user-request.ts new file mode 100644 index 0000000..4b2cbee --- /dev/null +++ b/src/infra/http/factories/requests/user-factories/delete-user-request.ts @@ -0,0 +1,20 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeDeleteUserRequestProps { + role: UserRoles + userId: string + token: string +} + +export async function makeDeleteUserRequest( + app: INestApplication, + { role, userId, token }: MakeDeleteUserRequestProps, +) { + const resp = await request(app.getHttpServer()) + .delete(`/users/${role}/${userId}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/user-factories/fetch-users-request.ts b/src/infra/http/factories/requests/user-factories/fetch-users-request.ts new file mode 100644 index 0000000..d18450b --- /dev/null +++ b/src/infra/http/factories/requests/user-factories/fetch-users-request.ts @@ -0,0 +1,19 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeFetchUsersRequestProps { + role: UserRoles + token: string +} + +export async function makeFetchUsersRequest( + app: INestApplication, + { role, token }: MakeFetchUsersRequestProps, +) { + const resp = await request(app.getHttpServer()) + .get(`/users/${role}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/user-factories/find-user-request.ts b/src/infra/http/factories/requests/user-factories/find-user-request.ts new file mode 100644 index 0000000..65a5a5d --- /dev/null +++ b/src/infra/http/factories/requests/user-factories/find-user-request.ts @@ -0,0 +1,20 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeFindUserRequestProps { + role: UserRoles + userId: string + token: string +} + +export async function makeFindUserRequest( + app: INestApplication, + { role, userId, token }: MakeFindUserRequestProps, +) { + const resp = await request(app.getHttpServer()) + .get(`/user/${role}/${userId}`) + .set('Authorization', `Bearer ${token}`) + + return resp +} diff --git a/src/infra/http/factories/requests/user-factories/update-user-password-request.ts b/src/infra/http/factories/requests/user-factories/update-user-password-request.ts new file mode 100644 index 0000000..a96ce22 --- /dev/null +++ b/src/infra/http/factories/requests/user-factories/update-user-password-request.ts @@ -0,0 +1,24 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeUpdateUserPasswordRequestProps { + role: UserRoles + userId: string + body: { + password: string + } + token: string +} + +export async function makeUpdateUserPasswordRequest( + app: INestApplication, + { role, userId, token, body }: MakeUpdateUserPasswordRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/users/${role}/${userId}/update-password`) + .set('Authorization', `Bearer ${token}`) + .send(body) + + return resp +} diff --git a/src/infra/http/factories/requests/user-factories/update-user-request.ts b/src/infra/http/factories/requests/user-factories/update-user-request.ts new file mode 100644 index 0000000..53c1ae0 --- /dev/null +++ b/src/infra/http/factories/requests/user-factories/update-user-request.ts @@ -0,0 +1,25 @@ +import { UserRoles } from '@/domain/core/deliveries-and-orders/enterprise/entities/abstract/user' +import { INestApplication } from '@nestjs/common' +import request from 'supertest' + +export interface MakeUpdateUserRequestProps { + role: UserRoles + userId: string + body: { + cpf: string + name: string + } + token: string +} + +export async function makeUpdateUserRequest( + app: INestApplication, + { role, userId, token, body }: MakeUpdateUserRequestProps, +) { + const resp = await request(app.getHttpServer()) + .patch(`/users/${role}/${userId}`) + .set('Authorization', `Bearer ${token}`) + .send(body) + + return resp +} diff --git a/src/infra/http/http.module.ts b/src/infra/http/http.module.ts index c17fda2..c373867 100644 --- a/src/infra/http/http.module.ts +++ b/src/infra/http/http.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common' import { DatabaseModule } from '../database/database.module' -import { useCases } from './controllers/use-cases.exports' +import { useCases } from './controllers/@exports/use-cases.exports' +import { AuthModule } from '../auth/auth.module' +import { controllers } from './controllers/@exports/controllers.exports' +import { CryptographyModule } from '../cryptography/cryptography.module' @Module({ - imports: [DatabaseModule], + imports: [DatabaseModule, AuthModule, CryptographyModule], providers: [...useCases], + controllers: [...controllers], }) export class HttpModule {} diff --git a/src/infra/http/utils/isCPF.ts b/src/infra/http/utils/isCPF.ts new file mode 100644 index 0000000..4455948 --- /dev/null +++ b/src/infra/http/utils/isCPF.ts @@ -0,0 +1,61 @@ +// import { randint } from './numbers' + +export const criaDigitos: (num: string) => string = (num: string) => { + const arrNum = num.split('') + const length = arrNum.length + + const digito = arrNum + .map((letter, index) => { + const number = Number(letter) + return number * (length + 1 - index) + }) + .reduce((acc, num) => acc + num, 0) + + const digitoCalc1 = 11 - (digito % 11) + const digitoCalc2 = digitoCalc1 > 9 ? '0' : String(digitoCalc1) + + if (arrNum.length === 11) { + const result = arrNum.join('') + return result + } + const arrNum2 = [...arrNum, digitoCalc2] + const result = arrNum2.join('') + + return criaDigitos(result) +} + +export const formataCPF = (cpf: string) => { + cpf = String(cpf) + const parte1 = cpf.slice(0, 3) + const parte2 = cpf.slice(3, 6) + const parte3 = cpf.slice(6, 9) + const digitos = cpf.slice(-2) + + cpf = `${parte1}.${parte2}.${parte3}-${digitos}` + return cpf +} + +export const validaCPF = (text: string) => { + if (!text) return false + const cpfClear = text.replaceAll(/\D+/g, '') + if (cpfClear.length !== 11) return false + if (cpfClear[0].repeat(cpfClear.length) === cpfClear) return false + + const cpfArray = cpfClear.split('') as string[] + const cpfFiltered = cpfArray + .filter((n) => { + if (!/\W/g.exec(n)) return n + return false + }) + .filter((n) => { + if (Number.isInteger(Number(n))) return n + return false + }) + + const cpfJoined = cpfFiltered.join('') + const parte1 = cpfJoined.slice(0, -2) + const cpfValido = criaDigitos(parte1) + + const cpf = cpfJoined + return cpf === cpfValido +} diff --git a/src/infra/pipes/zod-validation-pipe.ts b/src/infra/pipes/zod-validation-pipe.ts new file mode 100644 index 0000000..94cb43d --- /dev/null +++ b/src/infra/pipes/zod-validation-pipe.ts @@ -0,0 +1,23 @@ +import { BadRequestException, PipeTransform } from '@nestjs/common' +import { ZodError, ZodSchema } from 'zod' +import { fromZodError } from 'zod-validation-error' + +export class ZodValidationPipe implements PipeTransform { + constructor(private schema: ZodSchema) {} + + transform(value: unknown) { + try { + this.schema.parse(value) + } catch (error) { + if (error instanceof ZodError) { + throw new BadRequestException({ + errors: fromZodError(error), + message: 'validation failed', + statusCode: 400, + }) + } + throw new BadRequestException('validation failed') + } + return value + } +} diff --git a/test/factories/entities/makeOrder.ts b/test/factories/entities/makeOrder.ts index ee7d620..d673695 100644 --- a/test/factories/entities/makeOrder.ts +++ b/test/factories/entities/makeOrder.ts @@ -1,8 +1,14 @@ import UniqueEntityId from '@/core/entities/unique-entity-id' -import { Order } from '@/domain/core/deliveries-and-orders/enterprise/entities/order' +import { + Order, + OrderCreateProps, +} from '@/domain/core/deliveries-and-orders/enterprise/entities/order' import { makeAddress } from './value-objects/makeAddress' -export function makeOrder(override?: Partial, id?: UniqueEntityId) { +export function makeOrder( + override?: Partial, + id?: UniqueEntityId, +) { const result = Order.create( { address: makeAddress(override?.address), diff --git a/utils/generate-async-crypto-keys.ts b/utils/generate-async-crypto-keys.ts new file mode 100644 index 0000000..c98e1b3 --- /dev/null +++ b/utils/generate-async-crypto-keys.ts @@ -0,0 +1,243 @@ +import { exec } from 'child_process' +import path from 'node:path' +import fs from 'node:fs' + +class AsyncCryptoKeys { + private _publicKey: string | null = null + private _privateKey: string | null = null + + private _publicKeyBase64: string | null = null + private _privateKeyBase64: string | null = null + + get publicKey() { + return this._publicKey + } + + get privateKey() { + return this._privateKey + } + + get publicKeyBase64() { + return this._publicKeyBase64 + } + + get privateKeyBase64() { + return this._privateKeyBase64 + } + + // Função para executar os comandos + async generate() { + const baseDir = path.resolve(__dirname, '..') + const keysDir = path.resolve(baseDir, 'keys') + + const privateKeyPath = path.resolve(keysDir, 'private_key.pem') + const publicKeyPath = path.resolve(keysDir, 'public_key.pem') + + if (!this.fileExists(keysDir)) { + fs.mkdir(keysDir, { recursive: true }, (err) => + console.log('erro ao criar o arquivo', err?.message), + ) + } + + // const password = randomUUID() + + // const privateKeyCommand = `openssl genpkey -algorithm RSA -out ${privateKeyPath} -aes256 -pass pass:${password}` + const privateKeyCommand = `openssl genpkey -algorithm RSA -out ${privateKeyPath} -pkeyopt rsa_keygen_bits:2048` + // const publicKeyCommand = `openssl rsa -pubout -in ${privateKeyPath} -out ${publicKeyPath} -passin pass:${password}` + const publicKeyCommand = `openssl rsa -pubout -in ${privateKeyPath} -out ${publicKeyPath}` + + this.execute( + privateKeyCommand, + 'private', + async () => + await this.waitFor(() => this.fileExists(privateKeyPath, true)), + () => this.execute(publicKeyCommand, 'public'), + ) + + const privatePathExists = await this.waitFor(() => + this.fileExists(privateKeyPath, true), + ) + const publicPathExists = await this.waitFor(() => + this.fileExists(publicKeyPath, true), + ) + + const isCreated = privatePathExists && publicPathExists + + console.log('isCreated - openssl', isCreated) + if (isCreated) { + const { privateKeyContent, publicKeyContent } = await this.getKeys({ + privateKeyPath, + publicKeyPath, + }) + + this._privateKey = privateKeyContent + this._publicKey = publicKeyContent + + console.log('generating base64') + const privateKeyBse64Path = path.resolve( + keysDir, + 'private_key_base64.pem', + ) + const publicKeyBse64Path = path.resolve(keysDir, 'public_key_base64.pem') + + const privateKeyContentBase64 = Buffer.from( + privateKeyContent ?? '', + )?.toString('base64') + const publicKeyContentBase64 = Buffer.from( + publicKeyContent ?? '', + )?.toString('base64') + + fs.writeFileSync(privateKeyBse64Path, privateKeyContentBase64) + fs.writeFileSync(publicKeyBse64Path, publicKeyContentBase64) + + this._privateKeyBase64 = privateKeyContentBase64 + this._publicKeyBase64 = publicKeyContentBase64 + + // const privateKeyBase64Command = `openssl base64 -in ${privateKeyPath} -out ${privateKeyBse64Path} -pass pass:${password}` + // const publicKeyBase64Command = `openssl base64 -in ${publicKeyPath} -out ${publicKeyBse64Path} -pass pass:${password}` + + // this.execute(privateKeyBase64Command, 'private') + // this.execute(publicKeyBase64Command, 'public') + + // const privatePathExists = await this.waitFor(() => + // this.fileExists(privateKeyBse64Path, true), + // ) + // const publicPathExists = await this.waitFor(() => + // this.fileExists(publicKeyBse64Path, true), + // ) + + // const isCreated = privatePathExists && publicPathExists + + // if (isCreated) { + // const keys = await this.getKeys({ + // privateKeyPath: privateKeyBse64Path, + // publicKeyPath: publicKeyBse64Path, + // }) + + // this._privateKeyBase64 = keys.privateKeyContent + // this._publicKey = keys.publicKeyContent + // } + } + } + + fileExists(path: string, throwError?: boolean) { + const info = fs.existsSync(path) + + if (!info && throwError) throw new Error('file does not exist') + return info + } + + /** + * This function loops through a function rerunning all assertions + * inside of it until it gets a truthy result. + * + * If the maximum duration is reached, it then rejects. + * + * @param expectations A function containing all tests assertions + * @param maxDuration Maximum wait time before rejecting + */ + async waitFor(assertions: () => void, maxDuration = 1000): Promise { + return new Promise((resolve) => { + let elapsedTime = 0 + + const interval = setInterval(() => { + elapsedTime += 10 + + try { + assertions() + clearInterval(interval) + resolve(true) + } catch (err) { + if (elapsedTime >= maxDuration) { + resolve(false) + } + } + }, 10) + }) + } + + private execute( + command: string, + key: 'public' | 'private', + fileCreation?: () => Promise, + callback?: () => { execOperation: boolean; callbackOperation: boolean }, + ) { + let execOperation: boolean = false + let callbackOperation: boolean = false + + console.log('\nexecuting', command) + exec(command, async (error) => { + if (error) { + console.error( + `Erro ao extrair chave ${key === 'public' ? 'pública' : 'privada'}: ${error.message}`, + ) + return + } + + console.log( + '- ', + `Chave ${key === 'public' ? 'pública' : 'privada'} gerada com sucesso.`, + ) + execOperation = true + + const isCreated = fileCreation ? await fileCreation() : false + + if (callback && isCreated) { + const { execOperation } = callback() + callbackOperation = execOperation + } + }) + + return { execOperation, callbackOperation } + } + + private readKeyFile(filePath: string) { + try { + const content = fs.readFileSync(filePath, 'utf8') + return content + } catch (error: any) { //eslint-disable-line + console.error(`Erro ao ler o arquivo ${filePath}: ${error.message}`) + return null + } + } + + private async getKeys({ + privateKeyPath, + publicKeyPath, + }: { + privateKeyPath: string + publicKeyPath: string + }) { + const privatePathExists = await this.waitFor(() => + this.fileExists(privateKeyPath, true), + ) + const publicPathExists = await this.waitFor(() => + this.fileExists(publicKeyPath, true), + ) + + if (!privatePathExists || !publicPathExists) + throw new Error('files dont exist') + + // Leitura da chave privada + const privateKeyContent = this.readKeyFile(privateKeyPath) + + // if (privateKeyContent) { + // console.log('Conteúdo da chave privada:') + // console.log(privateKeyContent) + // } + + // Leitura da chave pública + const publicKeyContent = this.readKeyFile(publicKeyPath) + + // if (publicKeyContent) { + // console.log('Conteúdo da chave pública:') + // console.log(publicKeyContent) + // } + + return { privateKeyContent, publicKeyContent } + } +} + +const keys = new AsyncCryptoKeys() + +keys.generate() diff --git a/utils/generate-env-file-with-env-example.ts b/utils/generate-env-file-with-env-example.ts new file mode 100644 index 0000000..8412140 --- /dev/null +++ b/utils/generate-env-file-with-env-example.ts @@ -0,0 +1,8 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' + +const envExamplePath = resolve(__dirname, '..', '.env.example') +const envContent = readFileSync(envExamplePath) + +const envPath = resolve(__dirname, '..', '.env') +writeFileSync(envPath, envContent) From b513679c3308015ce029d21b5d2f819173160383 Mon Sep 17 00:00:00 2001 From: Bruno Fernandes Date: Fri, 21 Jun 2024 21:51:17 -0300 Subject: [PATCH 2/2] fix: unit-test error on collect-order-use-case --- .../use-cases/order/collect-order-use-case.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts index c9c46ca..6c9b9d9 100644 --- a/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts +++ b/src/domain/core/deliveries-and-orders/application/use-cases/order/collect-order-use-case.spec.ts @@ -4,8 +4,8 @@ import { makeCreateOrderUseCase } from 'test/factories/use-cases/order/make-crea import { makeAddress } from 'test/factories/entities/value-objects/makeAddress' import UniqueEntityId from '@/core/entities/unique-entity-id' import { makeCollectOrderUseCase } from 'test/factories/use-cases/order/make-collect-order-use-case' -import { OrderIsClosedError } from '@/core/errors/errors/order-errors/order-is-closed-error' import { OrderNotAwaitingPickupError } from '@/core/errors/errors/order-errors/order-not-awaiting-for-pickup-error' +import { OrderAlreadyCollectedError } from '@/core/errors/errors/order-errors/order-already-collected-error' describe('collect order use case', () => { let createOrder = makeCreateOrderUseCase() @@ -129,7 +129,7 @@ describe('collect order use case', () => { const orders = await sut.dependencies.ordersRepository.findMany() expect(sutResp.isLeft()).toBeTruthy() - expect(sutResp.value).toBeInstanceOf(OrderIsClosedError) + expect(sutResp.value).toBeInstanceOf(OrderAlreadyCollectedError) expect(orders).toHaveLength(1) expect(orders[0]).toEqual( expect.objectContaining({ @@ -176,7 +176,7 @@ describe('collect order use case', () => { const orders = await sut.dependencies.ordersRepository.findMany() expect(sutResp.isLeft()).toBeTruthy() - expect(sutResp.value).toBeInstanceOf(OrderIsClosedError) + expect(sutResp.value).toBeInstanceOf(OrderAlreadyCollectedError) expect(orders).toHaveLength(1) expect(orders[0]).toEqual( expect.objectContaining({