From ed6ca41b2d5ec38c189d6761c63972158da7ac22 Mon Sep 17 00:00:00 2001 From: Everton Pavan Date: Tue, 4 Jun 2024 15:15:31 -0300 Subject: [PATCH] feat: add notifications service to send password to new users via whatsapp --- .env.example | 5 + .env.local | 4 + package-lock.json | 110 ++++++++++++++++----- package.json | 1 + src/app.module.ts | 2 + src/dashboard/dashboard.controller.spec.ts | 4 +- src/dashboard/dashboard.service.spec.ts | 4 +- src/dashboard/dashboard.service.ts | 6 +- src/notifications/notifications.module.ts | 8 ++ src/notifications/notifications.service.ts | 28 ++++++ src/users/users.controller.spec.ts | 5 +- src/users/users.module.ts | 3 +- src/users/users.service.spec.ts | 3 +- src/users/users.service.ts | 21 +++- src/utils/index.ts | 2 + src/utils/utils.ts | 14 +++ 16 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 src/notifications/notifications.module.ts create mode 100644 src/notifications/notifications.service.ts diff --git a/.env.example b/.env.example index aecf6a1e..883b74a1 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,13 @@ DB_USER= DB_PASSWORD= DB_DATABASE_NAME= DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE_NAME}?schema=public" + SECRET_KEY= HMAC_SECRET_KEY= +TWILIO_ACCOUNT_SID= +TWILIO_AUTH_TOKEN= +TWILIO_WHATSAPP_NUMBER= + HOST=::0.0.0.0 PORT=4000 \ No newline at end of file diff --git a/.env.local b/.env.local index c1687c6e..bc69df07 100644 --- a/.env.local +++ b/.env.local @@ -10,5 +10,9 @@ DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_ SECRET_KEY=batata HMAC_SECRET_KEY= +TWILIO_ACCOUNT_SID= +TWILIO_AUTH_TOKEN= +TWILIO_WHATSAPP_NUMBER= + HOST=::0.0.0.0 PORT=4000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 65c13732..2a66b19f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", + "twilio": "^5.1.0", "zod": "^3.23.6" }, "devDependencies": { @@ -50,6 +51,10 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" + }, + "engines": { + "node": ">=18.18", + "npm": ">=10.5.0" } }, "node_modules/@ampproject/remapping": { @@ -2178,6 +2183,19 @@ "real-require": "^0.2.0" } }, + "node_modules/@nestjs/schedule": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.0.2.tgz", + "integrity": "sha512-po9oauE7fO0CjhDKvVC2tzEgjOUwhxYoIsXIVkgfu+xaDMmzzpmXY2s1LT4oP90Z+PaTtPoAHmhslnYmo4mSZg==", + "dependencies": { + "cron": "3.1.7", + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/@nestjs/schematics": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", @@ -3286,8 +3304,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -3308,6 +3325,16 @@ "fastq": "^1.17.1" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3675,7 +3702,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "devOptional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3952,7 +3978,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4177,6 +4202,11 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4247,7 +4277,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "devOptional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4264,7 +4293,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -4441,7 +4469,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "devOptional": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -4453,7 +4480,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, "engines": { "node": ">= 0.4" } @@ -5291,6 +5317,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -5360,7 +5405,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -5458,7 +5502,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5509,7 +5552,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "devOptional": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -5623,7 +5665,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "devOptional": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -5664,7 +5705,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "devOptional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -5676,7 +5716,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -5688,7 +5727,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -5705,7 +5743,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "devOptional": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -7238,7 +7275,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, "engines": { "node": ">= 0.6" } @@ -7247,7 +7283,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7505,7 +7540,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8100,6 +8134,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8128,7 +8167,6 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "devOptional": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -8607,6 +8645,11 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -8730,7 +8773,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "devOptional": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -8830,7 +8872,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "devOptional": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -9633,6 +9674,23 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/twilio": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.1.0.tgz", + "integrity": "sha512-8nIarHCbFwBsN7/KyDbQyRszMpmheUsx9hhaWOt0UfNzX/U8rG/W9dVbq/njjK6iCaR+5JIQipcbCF15EnqqBg==", + "dependencies": { + "axios": "^1.6.8", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.2", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10050,6 +10108,14 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "engines": { + "node": ">=6.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index a7caf97b..122dcbef 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", + "twilio": "^5.1.0", "zod": "^3.23.6" }, "devDependencies": { diff --git a/src/app.module.ts b/src/app.module.ts index 5f953f1a..c8696f7c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -16,6 +16,7 @@ import { DashboardModule } from './dashboard/dashboard.module'; import { SupportersModule } from './supporters/supporters.module'; import { SuppliesHistoryModule } from './supplies-history/supplies-history.module'; import { DonationOrderModule } from './donation-order/donation-order.module'; +import { NotificationsModule } from './notifications/notifications.module'; @Module({ imports: [ @@ -32,6 +33,7 @@ import { DonationOrderModule } from './donation-order/donation-order.module'; SupportersModule, SuppliesHistoryModule, DonationOrderModule, + NotificationsModule, ], controllers: [], providers: [ diff --git a/src/dashboard/dashboard.controller.spec.ts b/src/dashboard/dashboard.controller.spec.ts index 312a8e2a..57765394 100644 --- a/src/dashboard/dashboard.controller.spec.ts +++ b/src/dashboard/dashboard.controller.spec.ts @@ -2,8 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DashboardController } from './dashboard.controller'; import { DashboardService } from './dashboard.service'; -import { PrismaService } from '../../prisma/prisma.service'; -import { PrismaModule } from '../../prisma/prisma.module'; +import { PrismaService } from '../prisma/prisma.service'; +import { PrismaModule } from '../prisma/prisma.module'; describe('DashboardController', () => { let controller: DashboardController; diff --git a/src/dashboard/dashboard.service.spec.ts b/src/dashboard/dashboard.service.spec.ts index d02863a9..674d7664 100644 --- a/src/dashboard/dashboard.service.spec.ts +++ b/src/dashboard/dashboard.service.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DashboardService } from './dashboard.service'; -import { PrismaService } from '../../prisma/prisma.service'; -import { PrismaModule } from '../../prisma/prisma.module'; +import { PrismaService } from '../prisma/prisma.service'; +import { PrismaModule } from '../prisma/prisma.module'; describe('DashboardService', () => { let service: DashboardService; diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts index 25d29883..5b69ec19 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -1,9 +1,9 @@ import * as qs from 'qs'; import { Injectable } from '@nestjs/common'; -import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaService } from '../prisma/prisma.service'; import { ShelterSearchPropsSchema } from 'src/shelter/types/search.types'; -import { SearchSchema } from 'src/types'; -import { ShelterSearch } from 'src/shelter/ShelterSearch'; +import { SearchSchema } from '../types'; +import { ShelterSearch } from '../shelter/ShelterSearch'; import { DefaultArgs } from '@prisma/client/runtime/library'; import { Prisma } from '@prisma/client'; diff --git a/src/notifications/notifications.module.ts b/src/notifications/notifications.module.ts new file mode 100644 index 00000000..22550c32 --- /dev/null +++ b/src/notifications/notifications.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { NotificationsService } from './notifications.service'; + +@Module({ + providers: [NotificationsService], + exports: [NotificationsService], +}) +export class NotificationsModule {} diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts new file mode 100644 index 00000000..f062916f --- /dev/null +++ b/src/notifications/notifications.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import * as Twilio from 'twilio'; + +@Injectable() +export class NotificationsService { + private client: Twilio.Twilio; + + constructor() { + this.client = new Twilio.Twilio( + `${process.env.TWILIO_ACCOUNT_SID}`, + `${process.env.TWILIO_AUTH_TOKEN}`, + ); + } + + async sendWhatsApp(to: string, body: string) { + try { + const result = await this.client.messages.create({ + body, + from: `whatsapp:+${process.env.TWILIO_WHATSAPP_NUMBER}`, + to: `whatsapp:+55${to}`, + }); + + return result.errorCode; + } catch (error) { + console.log(error); + } + } +} diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index e2bcecdc..05d755b0 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -1,7 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { NotificationsService } from '../notifications/notifications.service'; // Import NotificationsService describe('UsersController', () => { let controller: UsersController; @@ -9,7 +10,7 @@ describe('UsersController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UsersController], - providers: [UsersService], + providers: [UsersService, PrismaService, NotificationsService], }) .useMocker((token) => { if (token === PrismaService) { diff --git a/src/users/users.module.ts b/src/users/users.module.ts index a20a47f5..cff9386d 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -3,9 +3,10 @@ import { Module } from '@nestjs/common'; import { PrismaModule } from '../prisma/prisma.module'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; +import { NotificationsModule } from '../notifications/notifications.module'; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, NotificationsModule], providers: [UsersService], controllers: [UsersController], }) diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts index 05a1f82b..b6926fb8 100644 --- a/src/users/users.service.spec.ts +++ b/src/users/users.service.spec.ts @@ -1,13 +1,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; import { PrismaService } from 'src/prisma/prisma.service'; +import { NotificationsService } from '../notifications/notifications.service'; // Import NotificationsService describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService], + providers: [UsersService, PrismaService, NotificationsService], }) .useMocker((token) => { if (token === PrismaService) { diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 339b468a..6eed3db4 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -3,23 +3,40 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateUserSchema, UpdateUserSchema } from './types'; import { User } from '@prisma/client'; +import { NotificationsService } from '../notifications/notifications.service'; +import { generateRandomPassword } from '../utils'; @Injectable() export class UsersService { - constructor(private readonly prismaService: PrismaService) {} + constructor( + private readonly prismaService: PrismaService, + private readonly notificationService: NotificationsService, + ) {} async store(body: any) { const { name, lastName, phone } = CreateUserSchema.parse(body); + + const randomPassword = generateRandomPassword(12); + await this.prismaService.user.create({ data: { name, lastName, phone, - password: phone, + password: randomPassword, login: phone, createdAt: new Date().toISOString(), }, }); + + const sendMessage = await this.notificationService.sendWhatsApp( + phone, + `Sua nova conta foi criada na plataforma SOS-RS. Sua senha é: ${randomPassword}`, + ); + + if (sendMessage === null) { + return; + } } async update(id: string, body: any) { diff --git a/src/utils/index.ts b/src/utils/index.ts index 09934e74..3b1502e9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,6 +4,7 @@ import { getSessionData, deepMerge, capitalize, + generateRandomPassword, } from './utils'; export { @@ -12,4 +13,5 @@ export { removeNotNumbers, getSessionData, deepMerge, + generateRandomPassword, }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 378e9914..fbc16988 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,6 @@ import { Logger } from '@nestjs/common'; import { GeolocationFilter } from 'src/shelter/types/search.types'; +import * as crypto from 'crypto'; class ServerResponse { readonly message: string; @@ -117,6 +118,18 @@ function removeEmptyStrings(obj): T { ) as T; } +function generateRandomPassword(length: number): string { + const charset = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?'; + let password = ''; + const values = new Uint32Array(length); + crypto.randomFillSync(values); + for (let i = 0; i < length; i++) { + password += charset[values[i] % charset.length]; + } + return password; +} + export { ServerResponse, calculateGeolocationBounds, @@ -125,4 +138,5 @@ export { getSessionData, removeNotNumbers, removeEmptyStrings, + generateRandomPassword, };