diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5415e69 --- /dev/null +++ b/.env.example @@ -0,0 +1,52 @@ +API_URI=http://localhost:4000 +APP_URI=http://localhost:4200 +APP_VERSION= + +AWS_ACCESS_KEY_ID= +AWS_BUCKET= +AWS_ENDPOINT= +AWS_REGION= +AWS_SECRET_ACCESS_KEY= + +DATABASE_MASTER_HOST= +DATABASE_SLAVE_HOST= +DATABASE_PORT= +DATABASE_TYPE= +DATABASE_NAME= +DATABASE_USER= +DATABASE_PASSWORD= + +DEBUG=false + +ENCRYPT_KEY= +JWT_SECRET= +JWT_TTL= + +MODE=production +ORIGIN=http://localhost:4200,https://ordero.vercel.app +PORT=4000 + +REDIS_DATABASE=0 +REDIS_ENABLED=false +REDIS_HOST= +REDIS_PASSWORD= +REDIS_PORT= +REDIS_QUEUE=0 + +MAIL_FROM='Ordero ' +MAIL_PASSWORD= +MAIL_USERNAME= +SMTP_HOST= +SMTP_PORT= + +MAILERSEND_API_KEY= +MAILERSEND_DOMAIN= + +SENTRY_DSN= +SOCKET_TYPE=socketio + +TWILLIO_SERVICE= +TWILLIO_SID= +TWILLIO_TOKEN= + +TZ=UTC \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 8adb43b..992a880 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -95,6 +95,8 @@ services: TWILLIO_SID: '${TWILLIO_SID}' TWILLIO_TOKEN: '${TWILLIO_TOKEN}' TWILLIO_SERVICE: '${TWILLIO_SERVICE}' + MAILERSEND_API_KEY: '${MAILERSEND_API_KEY}' + MAILERSEND_DOMAIN: '${MAILERSEND_DOMAIN}' volumes: - 'ordero:/api' ports: diff --git a/package.json b/package.json index 80927bc..2ac1e6a 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "json2csv": "^6.0.0-alpha.2", "libphonenumber-js": "^1.10.12", "lodash": "^4.17.21", + "mailersend": "^2.3.0", "mysql-import": "^5.0.21", "mysql2": "^3.10.2", "nest-router": "^1.0.9", diff --git a/src/app/owner/auth/auth.controller.ts b/src/app/owner/auth/auth.controller.ts index f54923d..24f2ea1 100644 --- a/src/app/owner/auth/auth.controller.ts +++ b/src/app/owner/auth/auth.controller.ts @@ -156,23 +156,25 @@ export class AuthController { } const owner: Owner = await Owner.findOne({ where: { email } }); - if (owner && owner.id) { - owner.reset_token = uuid(); - await owner.save(); - - this.mail - .sendMail({ - to: owner.email, - subject: 'Set up a new password', - template: 'change-password', - context: { - name: owner.name, - link: `${config.get('APP_URI')}/restaurant/auth/reset-password/${owner.reset_token}`, - }, - }) - .then(() => null) - .catch((error) => Logger.getInstance().notify(error)); - } + await this.service.forgotPassword(owner); + + // if (owner && owner.id) { + // owner.reset_token = uuid(); + // await owner.save(); + + // this.mail + // .sendMail({ + // to: owner.email, + // subject: 'Set up a new password', + // template: 'change-password', + // context: { + // name: owner.name, + // link: `${config.get('APP_URI')}/restaurant/auth/reset-password/${owner.reset_token}`, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); + // } return response.noContent(); } @@ -188,19 +190,19 @@ export class AuthController { throw new ValidationException(validation); } - const staff: Owner = await Owner.findOrFail({ where: { reset_token: body.token } }); + const owner: Owner = await Owner.findOrFail({ where: { reset_token: body.token } }); - staff.password = await hash(body.password); - staff.reset_token = null; - await staff.save(); + owner.password = await hash(body.password); + owner.reset_token = null; + await owner.save(); await this.mail .sendMail({ - to: staff.email, + to: owner.email, subject: 'Changed password', template: 'changed-password', context: { - name: staff.name, + name: owner.name, }, }) .then(() => null) diff --git a/src/app/owner/restaurant/staff/staff.controller.ts b/src/app/owner/restaurant/staff/staff.controller.ts index cfc54ea..f720108 100644 --- a/src/app/owner/restaurant/staff/staff.controller.ts +++ b/src/app/owner/restaurant/staff/staff.controller.ts @@ -2,6 +2,7 @@ import { Loc } from '@core/decorators/location.decorator'; import { Rest } from '@core/decorators/restaurant.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; import { OwnerGuard } from '@core/guards/owner.guard'; +import { MailService } from '@core/services/mail.service'; import { PermAct, PermOwner } from '@core/services/role.service'; import { Location } from '@db/entities/owner/location.entity'; import { StaffRole } from '@db/entities/staff/role.entity'; @@ -11,16 +12,14 @@ import { ValidationException } from '@lib/exceptions/validation.exception'; import { hash } from '@lib/helpers/encrypt.helper'; import { randomChar } from '@lib/helpers/utils.helper'; import { Validator } from '@lib/helpers/validator.helper'; -import Logger from '@lib/logger/logger.library'; import { Permissions } from '@lib/rbac'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; -import { MailerService } from '@nestjs-modules/mailer'; import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; @Controller() @UseGuards(OwnerAuthGuard()) export class StaffController { - constructor(private mail: MailerService) {} + constructor(private mail: MailService) {} @Get() @UseGuards(OwnerGuard) @@ -94,18 +93,28 @@ export class StaffController { staff.restaurant_id = rest.id; await staff.save(); - this.mail - .sendMail({ - to: staff.email, - subject: 'Your staff account!', - template: 'staff-register', - context: { - name: staff.name, - password: plainPass, - }, - }) - .then(() => null) - .catch((error) => Logger.getInstance().notify(error)); + await this.mail.sendStaffRegister({ + receipient: staff.email, + subject: 'Your staff account!', + data: { + team_name: 'Ordero', + name: staff.name, + password: plainPass, + }, + }); + + // this.mail + // .sendMail({ + // to: staff.email, + // subject: 'Your staff account!', + // template: 'staff-register', + // context: { + // name: staff.name, + // password: plainPass, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); await response.item(staff, StaffTransformer); } diff --git a/src/core/core.module.ts b/src/core/core.module.ts index 3ab957a..d52aeea 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -10,6 +10,7 @@ import { DataSource } from 'typeorm'; import { AuthService } from './services/auth.service'; import { AwsService } from './services/aws.service'; import { CustomerService } from './services/customer.service'; +import { MailService } from './services/mail.service'; import { PdfService } from './services/pdf.service'; import { ProductService } from './services/product.service'; import { RoleService } from './services/role.service'; @@ -26,6 +27,7 @@ const services = [ ProductService, PdfService, UtilService, + MailService, ]; @Global() diff --git a/src/core/services/auth.service.ts b/src/core/services/auth.service.ts index 9cb270b..8b283fe 100644 --- a/src/core/services/auth.service.ts +++ b/src/core/services/auth.service.ts @@ -2,17 +2,17 @@ import { jwt } from '@config/jwt.config'; import { Role } from '@db/entities/core/role.entity'; import { Owner, OwnerStatus } from '@db/entities/owner/owner.entity'; import { Restaurant, RestaurantStatus } from '@db/entities/owner/restaurant.entity'; +import { config } from '@lib/helpers/config.helper'; import { hash, hashAreEqual } from '@lib/helpers/encrypt.helper'; -import Logger from '@lib/logger/logger.library'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; import { uuid } from '@lib/uid/uuid.library'; -import { MailerService } from '@nestjs-modules/mailer'; import { Injectable, NotFoundException } from '@nestjs/common'; import * as JWT from 'jsonwebtoken'; +import { MailService } from './mail.service'; @Injectable() export class AuthService { - constructor(private mail: MailerService) {} + constructor(private readonly mailer: MailService) {} async attempt(username: string, pass: string): Promise { const user = await Owner.findOne({ where: [{ email: username }, { phone: username }] }); @@ -74,18 +74,28 @@ export class AuthService { // eslint-disable-next-line @typescript-eslint/no-unused-vars async sendVerificationEmail(user: Owner, changeEmail = false): Promise { - this.mail - .sendMail({ - to: user.email, - subject: 'Verify Your Account', - template: 'register', - context: { - name: user.name, - code: user.verification_code, - }, - }) - .then(() => null) - .catch((error) => Logger.getInstance().notify(error)); + this.mailer.sendVerificationCode({ + receipient: user.email, + subject: 'Verify Your Account', + data: { + verification_code: user.verification_code, + team_name: 'Ordero', + name: user.name, + }, + }); + + // this.mail + // .sendMail({ + // to: user.email, + // subject: 'Verify Your Account', + // template: 'register', + // context: { + // name: user.name, + // code: user.verification_code, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); } async forgotPassword(user: Owner): Promise { @@ -93,6 +103,16 @@ export class AuthService { // user.reset_token_expires = time().add(24, 'hour').toDate(); await user.save(); + this.mailer.sendResetPassword({ + receipient: user.email, + subject: 'Reset Password', + data: { + team_name: 'Ordero', + name: user.name, + reset_link: `${config.get('APP_URI')}/reset-password/${user.reset_token}`, + }, + }); + // this.mail // .sendMail({ // to: user.email, diff --git a/src/core/services/mail.service.ts b/src/core/services/mail.service.ts new file mode 100644 index 0000000..18168be --- /dev/null +++ b/src/core/services/mail.service.ts @@ -0,0 +1,95 @@ +import { config } from '@lib/helpers/config.helper'; +import { Injectable } from '@nestjs/common'; +import { EmailParams, MailerSend, Recipient, Sender } from 'mailersend'; + +interface MailersendPayload { + receipient: string; + subject: string; + data: { + name: string; + team_name: string; + verification_code?: string; + reset_link?: string; + password?: string; + }; + template_id?: string; +} + +@Injectable() +export class MailService { + protected mailerSend: MailerSend; + protected sentFrom: Sender; + protected mailDomain: string; + + constructor() { + this.mailerSend = new MailerSend({ + apiKey: config.get('MAILERSEND_API_KEY'), + }); + this.sentFrom = new Sender('user@trial-neqvygm859840p7w.mlsender.net', 'Order Team'); + } + + async sendVerificationCode(payload: MailersendPayload) { + const recipients = [new Recipient(payload.receipient, payload.data.name)]; + const personalization = [ + { + email: payload.receipient, + data: payload.data, + }, + ]; + + const emailParams = new EmailParams() + .setFrom(this.sentFrom) + .setTo(recipients) + .setSubject(payload.subject) + .setPersonalization(personalization) + .setTemplateId('z3m5jgrkqkdldpyo'); + + await this.mailerSend.email.send(emailParams); + } + + async sendResetPassword(payload: MailersendPayload) { + const recipients = [new Recipient(payload.receipient, payload.data.name)]; + const personalization = [ + { + email: payload.receipient, + data: { + name: payload.data.name, + reset_link: payload.data.reset_link, + team_name: payload.data.team_name, + }, + }, + ]; + + const emailParams = new EmailParams() + .setFrom(this.sentFrom) + .setTo(recipients) + .setSubject(payload.subject) + .setPersonalization(personalization) + .setTemplateId('pq3enl67y78g2vwr'); + + await this.mailerSend.email.send(emailParams); + } + + async sendStaffRegister(payload: MailersendPayload) { + const recipients = [new Recipient(payload.receipient, payload.data.name)]; + const personalization = [ + { + email: payload.receipient, + data: { + name: payload.data.name, + team_name: payload.data.team_name, + password: payload.data.password, + }, + }, + ]; + + const emailParams = new EmailParams() + .setFrom(this.sentFrom) + .setTo(recipients) + .setSubject(payload.subject) + .setPersonalization(personalization) + .setTemplateId('351ndgw6q6n4zqx8'); + + await this.mailerSend.email.send(emailParams); + } +} diff --git a/yarn.lock b/yarn.lock index e1b07f2..f3b6a8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3948,6 +3948,11 @@ express-useragent@^1.0.15: resolved "https://registry.yarnpkg.com/express-useragent/-/express-useragent-1.0.15.tgz#cefda5fa4904345d51d3368b117a8dd4124985d9" integrity sha512-eq5xMiYCYwFPoekffMjvEIk+NWdlQY9Y38OsTyl13IvA728vKT+q/CSERYWzcw93HGBJcIqMIsZC5CZGARPVdg== +extend@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -4408,6 +4413,16 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gaxios@^5.0.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.1.3.tgz#f7fa92da0fe197c846441e5ead2573d4979e9013" + integrity sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA== + dependencies: + extend "^3.0.2" + https-proxy-agent "^5.0.0" + is-stream "^2.0.0" + node-fetch "^2.6.9" + generate-function@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" @@ -5225,6 +5240,14 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -6226,6 +6249,15 @@ magic-string@0.26.1: dependencies: sourcemap-codec "^1.4.8" +mailersend@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/mailersend/-/mailersend-2.3.0.tgz#56e4511528441c25931bbe1edb51556085f8ac86" + integrity sha512-pe498Ry7VaAb+oqcYqmPw1V7FlECG/mcqahQ3SiK54en4ZkyRwjyxoQwA9VU4s3npB+I44LlQGUudObZQe4/jA== + dependencies: + gaxios "^5.0.1" + isomorphic-unfetch "^3.1.0" + qs "^6.11.0" + mailparser@^3.3.0: version "3.5.0" resolved "https://registry.yarnpkg.com/mailparser/-/mailparser-3.5.0.tgz#5b333b0ef2f063a7db9d24ed95f29efb464cbef3" @@ -6932,6 +6964,13 @@ node-fetch@^2.6.0, node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.9: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -7838,6 +7877,13 @@ qs@^6.10.3: dependencies: side-channel "^1.0.4" +qs@^6.11.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + qs@^6.9.4: version "6.12.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" @@ -9376,6 +9422,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"