mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 04:22:27 +10:00
use nodemailer/smtp instead of sendgrid
This commit is contained in:
10
.env.example
10
.env.example
@ -22,10 +22,12 @@ JWT_SECRET=
|
|||||||
JWT_EXPIRY_TIME=604800
|
JWT_EXPIRY_TIME=604800
|
||||||
GOOGLE_CLIENT_SECRET=
|
GOOGLE_CLIENT_SECRET=
|
||||||
GOOGLE_API_KEY=
|
GOOGLE_API_KEY=
|
||||||
SENDGRID_API_KEY=
|
MAIL_FROM_NAME=
|
||||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
MAIL_FROM_EMAIL=
|
||||||
SENDGRID_FROM_NAME=
|
MAIL_HOST=
|
||||||
SENDGRID_FROM_EMAIL=
|
MAIL_PORT=
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
STORAGE_BUCKET=
|
STORAGE_BUCKET=
|
||||||
STORAGE_REGION=
|
STORAGE_REGION=
|
||||||
STORAGE_ENDPOINT=
|
STORAGE_ENDPOINT=
|
||||||
|
|||||||
@ -6,10 +6,7 @@ services:
|
|||||||
container_name: postgres
|
container_name: postgres
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
environment:
|
env_file: .env.docker
|
||||||
- POSTGRES_DB=postgres
|
|
||||||
- POSTGRES_USER=postgres
|
|
||||||
- POSTGRES_PASSWORD=postgres
|
|
||||||
volumes:
|
volumes:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@ -38,7 +35,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: ./server/Dockerfile
|
dockerfile: ./server/Dockerfile
|
||||||
container_name: server
|
container_name: server
|
||||||
env_file: .env
|
env_file: .env.docker
|
||||||
depends_on:
|
depends_on:
|
||||||
- traefik
|
- traefik
|
||||||
- postgres
|
- postgres
|
||||||
@ -57,7 +54,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: ./client/Dockerfile
|
dockerfile: ./client/Dockerfile
|
||||||
container_name: client
|
container_name: client
|
||||||
env_file: .env
|
env_file: .env.docker
|
||||||
depends_on:
|
depends_on:
|
||||||
- traefik
|
- traefik
|
||||||
- server
|
- server
|
||||||
|
|||||||
@ -136,30 +136,16 @@ You can get your own key here: https://developers.google.com/fonts/docs/develope
|
|||||||
|
|
||||||
If you do not have a Google API Key, it was make use of the cached response JSON that's stored within the project source. Please note that this cache is not updated and may not have all the latest fonts that Google Fonts has to offer.
|
If you do not have a Google API Key, it was make use of the cached response JSON that's stored within the project source. Please note that this cache is not updated and may not have all the latest fonts that Google Fonts has to offer.
|
||||||
|
|
||||||
## SendGrid
|
## Mail
|
||||||
|
|
||||||
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
The server makes use of SMTP to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
||||||
|
|
||||||
### `SENDGRID_API_KEY`
|
### `MAIL_FROM_NAME`
|
||||||
|
|
||||||
**Required**: `no`
|
|
||||||
**Description:** SendGrid API Key
|
|
||||||
|
|
||||||
Does not require any payment or credit card information to obtain an API key.
|
|
||||||
|
|
||||||
You can get your own key here: https://docs.sendgrid.com/ui/account-and-settings/api-keys
|
|
||||||
|
|
||||||
### `SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID`
|
|
||||||
|
|
||||||
**Required**: `no`
|
|
||||||
**Description:** Dynamic Template ID for Forgot Password
|
|
||||||
|
|
||||||
### `SENDGRID_FROM_NAME`
|
|
||||||
|
|
||||||
**Required**: `no`
|
**Required**: `no`
|
||||||
**Description:** Sender's Name
|
**Description:** Sender's Name
|
||||||
|
|
||||||
### `SENDGRID_FROM_EMAIL`
|
### `MAIL_FROM_EMAIL`
|
||||||
|
|
||||||
**Required**: `no`
|
**Required**: `no`
|
||||||
**Description:** Sender's Email Address
|
**Description:** Sender's Email Address
|
||||||
|
|||||||
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
@ -221,13 +221,13 @@ importers:
|
|||||||
'@nestjs/terminus': ^9.1.1
|
'@nestjs/terminus': ^9.1.1
|
||||||
'@nestjs/typeorm': ^9.0.1
|
'@nestjs/typeorm': ^9.0.1
|
||||||
'@reactive-resume/schema': workspace:*
|
'@reactive-resume/schema': workspace:*
|
||||||
'@sendgrid/mail': ^7.7.0
|
|
||||||
'@types/bcryptjs': ^2.4.2
|
'@types/bcryptjs': ^2.4.2
|
||||||
'@types/cookie-parser': ^1.4.3
|
'@types/cookie-parser': ^1.4.3
|
||||||
'@types/express': ^4.17.13
|
'@types/express': ^4.17.13
|
||||||
'@types/lodash': ^4.14.184
|
'@types/lodash': ^4.14.184
|
||||||
'@types/multer': ^1.4.7
|
'@types/multer': ^1.4.7
|
||||||
'@types/node': ^18.7.9
|
'@types/node': ^18.7.9
|
||||||
|
'@types/nodemailer': ^6.4.5
|
||||||
'@types/passport': ^1.0.10
|
'@types/passport': ^1.0.10
|
||||||
'@types/passport-jwt': ^3.0.6
|
'@types/passport-jwt': ^3.0.6
|
||||||
'@types/passport-local': ^1.0.34
|
'@types/passport-local': ^1.0.34
|
||||||
@ -244,6 +244,7 @@ importers:
|
|||||||
multer: ^1.4.4
|
multer: ^1.4.4
|
||||||
nanoid: ^3.3.4
|
nanoid: ^3.3.4
|
||||||
node-stream-zip: ^1.15.0
|
node-stream-zip: ^1.15.0
|
||||||
|
nodemailer: ^6.7.8
|
||||||
passport: ^0.6.0
|
passport: ^0.6.0
|
||||||
passport-jwt: ^4.0.0
|
passport-jwt: ^4.0.0
|
||||||
passport-local: ^1.0.0
|
passport-local: ^1.0.0
|
||||||
@ -276,7 +277,6 @@ importers:
|
|||||||
'@nestjs/serve-static': 3.0.0_khr6mt6ojlxbw7bo55fknouh34
|
'@nestjs/serve-static': 3.0.0_khr6mt6ojlxbw7bo55fknouh34
|
||||||
'@nestjs/terminus': 9.1.1_mr622mjrz7hsw5sxbt7k6brday
|
'@nestjs/terminus': 9.1.1_mr622mjrz7hsw5sxbt7k6brday
|
||||||
'@nestjs/typeorm': 9.0.1_sp4gtzkbiyh24r2ydcb6yqstha
|
'@nestjs/typeorm': 9.0.1_sp4gtzkbiyh24r2ydcb6yqstha
|
||||||
'@sendgrid/mail': 7.7.0
|
|
||||||
'@types/passport': 1.0.10
|
'@types/passport': 1.0.10
|
||||||
bcryptjs: 2.4.3
|
bcryptjs: 2.4.3
|
||||||
cache-manager: 4.1.0
|
cache-manager: 4.1.0
|
||||||
@ -291,6 +291,7 @@ importers:
|
|||||||
multer: 1.4.4
|
multer: 1.4.4
|
||||||
nanoid: 3.3.4
|
nanoid: 3.3.4
|
||||||
node-stream-zip: 1.15.0
|
node-stream-zip: 1.15.0
|
||||||
|
nodemailer: 6.7.8
|
||||||
passport: 0.6.0
|
passport: 0.6.0
|
||||||
passport-jwt: 4.0.0
|
passport-jwt: 4.0.0
|
||||||
passport-local: 1.0.0
|
passport-local: 1.0.0
|
||||||
@ -312,6 +313,7 @@ importers:
|
|||||||
'@types/lodash': 4.14.184
|
'@types/lodash': 4.14.184
|
||||||
'@types/multer': 1.4.7
|
'@types/multer': 1.4.7
|
||||||
'@types/node': 18.7.9
|
'@types/node': 18.7.9
|
||||||
|
'@types/nodemailer': 6.4.5
|
||||||
'@types/passport-jwt': 3.0.6
|
'@types/passport-jwt': 3.0.6
|
||||||
'@types/passport-local': 1.0.34
|
'@types/passport-local': 1.0.34
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
@ -4696,33 +4698,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sendgrid/client/7.7.0:
|
|
||||||
resolution: {integrity: sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==}
|
|
||||||
engines: {node: 6.* || 8.* || >=10.*}
|
|
||||||
dependencies:
|
|
||||||
'@sendgrid/helpers': 7.7.0
|
|
||||||
axios: 0.26.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sendgrid/helpers/7.7.0:
|
|
||||||
resolution: {integrity: sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==}
|
|
||||||
engines: {node: '>= 6.0.0'}
|
|
||||||
dependencies:
|
|
||||||
deepmerge: 4.2.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sendgrid/mail/7.7.0:
|
|
||||||
resolution: {integrity: sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==}
|
|
||||||
engines: {node: 6.* || 8.* || >=10.*}
|
|
||||||
dependencies:
|
|
||||||
'@sendgrid/client': 7.7.0
|
|
||||||
'@sendgrid/helpers': 7.7.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sideway/address/4.1.4:
|
/@sideway/address/4.1.4:
|
||||||
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
|
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5101,10 +5076,17 @@ packages:
|
|||||||
|
|
||||||
/@types/node/18.6.2:
|
/@types/node/18.6.2:
|
||||||
resolution: {integrity: sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==}
|
resolution: {integrity: sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/node/18.7.9:
|
/@types/node/18.7.9:
|
||||||
resolution: {integrity: sha512-0N5Y1XAdcl865nDdjbO0m3T6FdmQ4ijE89/urOHLREyTXbpMWbSafx9y7XIsgWGtwUP2iYTinLyyW3FatAxBLQ==}
|
resolution: {integrity: sha512-0N5Y1XAdcl865nDdjbO0m3T6FdmQ4ijE89/urOHLREyTXbpMWbSafx9y7XIsgWGtwUP2iYTinLyyW3FatAxBLQ==}
|
||||||
|
|
||||||
|
/@types/nodemailer/6.4.5:
|
||||||
|
resolution: {integrity: sha512-zuP3nBRQHI6M2PkXnGGy1Ww4VB+MyYHGgnfV2T+JR9KLkeWqPJuyVUgLpKXuFnA/b7pZaIDFh2sV4759B7jK1g==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.7.9
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/normalize-package-data/2.4.1:
|
/@types/normalize-package-data/2.4.1:
|
||||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -5961,14 +5943,6 @@ packages:
|
|||||||
- debug
|
- debug
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/axios/0.26.1:
|
|
||||||
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.14.9
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/axios/0.27.2:
|
/axios/0.27.2:
|
||||||
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9804,7 +9778,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
|
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
|
||||||
engines: {node: '>= 10.13.0'}
|
engines: {node: '>= 10.13.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.6.2
|
'@types/node': 18.7.9
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
|
|
||||||
@ -11069,6 +11043,11 @@ packages:
|
|||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/nodemailer/6.7.8:
|
||||||
|
resolution: {integrity: sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/normalize-package-data/2.5.0:
|
/normalize-package-data/2.5.0:
|
||||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
"@nestjs/serve-static": "^3.0.0",
|
"@nestjs/serve-static": "^3.0.0",
|
||||||
"@nestjs/terminus": "^9.1.1",
|
"@nestjs/terminus": "^9.1.1",
|
||||||
"@nestjs/typeorm": "^9.0.1",
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
"@sendgrid/mail": "^7.7.0",
|
|
||||||
"@types/passport": "^1.0.10",
|
"@types/passport": "^1.0.10",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cache-manager": "^4.1.0",
|
"cache-manager": "^4.1.0",
|
||||||
@ -35,6 +34,7 @@
|
|||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
|
"nodemailer": "^6.7.8",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
@ -57,6 +57,7 @@
|
|||||||
"@types/lodash": "^4.14.184",
|
"@types/lodash": "^4.14.184",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^18.7.9",
|
"@types/node": "^18.7.9",
|
||||||
|
"@types/nodemailer": "^6.4.5",
|
||||||
"@types/passport-jwt": "^3.0.6",
|
"@types/passport-jwt": "^3.0.6",
|
||||||
"@types/passport-local": "^1.0.34",
|
"@types/passport-local": "^1.0.34",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import appConfig from './app.config';
|
|||||||
import authConfig from './auth.config';
|
import authConfig from './auth.config';
|
||||||
import databaseConfig from './database.config';
|
import databaseConfig from './database.config';
|
||||||
import googleConfig from './google.config';
|
import googleConfig from './google.config';
|
||||||
import sendgridConfig from './sendgrid.config';
|
import mailConfig from './mail.config';
|
||||||
import storageConfig from './storage.config';
|
import storageConfig from './storage.config';
|
||||||
|
|
||||||
const validationSchema = Joi.object({
|
const validationSchema = Joi.object({
|
||||||
@ -36,11 +36,13 @@ const validationSchema = Joi.object({
|
|||||||
GOOGLE_CLIENT_SECRET: Joi.string().allow(''),
|
GOOGLE_CLIENT_SECRET: Joi.string().allow(''),
|
||||||
PUBLIC_GOOGLE_CLIENT_ID: Joi.string().allow(''),
|
PUBLIC_GOOGLE_CLIENT_ID: Joi.string().allow(''),
|
||||||
|
|
||||||
// SendGrid
|
// Mail
|
||||||
SENDGRID_API_KEY: Joi.string().allow(''),
|
MAIL_FROM_NAME: Joi.string().allow(''),
|
||||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID: Joi.string().allow(''),
|
MAIL_FROM_EMAIL: Joi.string().allow(''),
|
||||||
SENDGRID_FROM_NAME: Joi.string().allow(''),
|
MAIL_HOST: Joi.string().allow(''),
|
||||||
SENDGRID_FROM_EMAIL: Joi.string().allow(''),
|
MAIL_PORT: Joi.string().allow(''),
|
||||||
|
MAIL_USERNAME: Joi.string().allow(''),
|
||||||
|
MAIL_PASSWORD: Joi.string().allow(''),
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
STORAGE_BUCKET: Joi.string().allow(''),
|
STORAGE_BUCKET: Joi.string().allow(''),
|
||||||
@ -54,7 +56,7 @@ const validationSchema = Joi.object({
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
NestConfigModule.forRoot({
|
NestConfigModule.forRoot({
|
||||||
load: [appConfig, authConfig, databaseConfig, googleConfig, sendgridConfig, storageConfig],
|
load: [appConfig, authConfig, databaseConfig, googleConfig, mailConfig, storageConfig],
|
||||||
validationSchema: validationSchema,
|
validationSchema: validationSchema,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
12
server/src/config/mail.config.ts
Normal file
12
server/src/config/mail.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('mail', () => ({
|
||||||
|
from: {
|
||||||
|
name: process.env.MAIL_FROM_NAME,
|
||||||
|
email: process.env.MAIL_FROM_EMAIL,
|
||||||
|
},
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: process.env.MAIL_PORT,
|
||||||
|
username: process.env.MAIL_USERNAME,
|
||||||
|
password: process.env.MAIL_PASSWORD,
|
||||||
|
}));
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { registerAs } from '@nestjs/config';
|
|
||||||
|
|
||||||
export default registerAs('sendgrid', () => ({
|
|
||||||
apiKey: process.env.SENDGRID_API_KEY,
|
|
||||||
forgotPasswordTemplateId: process.env.SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID,
|
|
||||||
fromName: process.env.SENDGRID_FROM_NAME,
|
|
||||||
fromEmail: process.env.SENDGRID_FROM_EMAIL,
|
|
||||||
}));
|
|
||||||
30
server/src/mail/dto/send-mail.dto.ts
Normal file
30
server/src/mail/dto/send-mail.dto.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class MailRecipient {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendMailDto {
|
||||||
|
@IsDefined()
|
||||||
|
@Type(() => MailRecipient)
|
||||||
|
from: MailRecipient;
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@Type(() => MailRecipient)
|
||||||
|
to: MailRecipient;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
@ -1,40 +1,56 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import SendGrid from '@sendgrid/mail';
|
import { createTransport, Transporter } from 'nodemailer';
|
||||||
|
|
||||||
import { User } from '@/users/entities/user.entity';
|
import { User } from '@/users/entities/user.entity';
|
||||||
|
|
||||||
|
import { SendMailDto } from './dto/send-mail.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MailService {
|
export class MailService {
|
||||||
constructor(private configService: ConfigService) {
|
transporter: Transporter;
|
||||||
const sendGridApiKey = this.configService.get<string>('sendgrid.apiKey');
|
|
||||||
|
|
||||||
if (sendGridApiKey) {
|
constructor(private configService: ConfigService) {
|
||||||
SendGrid.setApiKey(this.configService.get<string>('sendgrid.apiKey'));
|
this.transporter = createTransport({
|
||||||
}
|
host: this.configService.get<string>('mail.host'),
|
||||||
|
port: this.configService.get<number>('mail.port'),
|
||||||
|
pool: true,
|
||||||
|
secure: false,
|
||||||
|
tls: { ciphers: 'SSLv3' },
|
||||||
|
auth: {
|
||||||
|
user: this.configService.get<string>('mail.username'),
|
||||||
|
pass: this.configService.get<string>('mail.password'),
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEmail(mail: SendGrid.MailDataRequired) {
|
async sendEmail(sendMailDto: SendMailDto) {
|
||||||
return SendGrid.send(mail);
|
this.transporter.sendMail({
|
||||||
|
from: `${sendMailDto.from.name} <${sendMailDto.from.email}>`,
|
||||||
|
to: `${sendMailDto.to.name} <${sendMailDto.to.email}>`,
|
||||||
|
subject: sendMailDto.subject,
|
||||||
|
text: sendMailDto.message,
|
||||||
|
html: sendMailDto.message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendForgotPasswordEmail(user: User, resetToken: string): Promise<void> {
|
async sendForgotPasswordEmail(user: User, resetToken: string): Promise<void> {
|
||||||
const appUrl = this.configService.get<string>('app.url');
|
const appUrl = this.configService.get<string>('app.url');
|
||||||
const url = `${appUrl}?modal=auth.reset&resetToken=${resetToken}`;
|
const url = `${appUrl}?modal=auth.reset&resetToken=${resetToken}`;
|
||||||
|
|
||||||
const mailData: SendGrid.MailDataRequired = {
|
const sendMailDto: SendMailDto = {
|
||||||
from: {
|
from: {
|
||||||
name: this.configService.get<string>('sendgrid.fromName'),
|
name: this.configService.get<string>('mail.from.name'),
|
||||||
email: this.configService.get<string>('sendgrid.fromEmail'),
|
email: this.configService.get<string>('mail.from.email'),
|
||||||
},
|
},
|
||||||
to: user.email,
|
to: {
|
||||||
hideWarnings: true,
|
name: user.name,
|
||||||
dynamicTemplateData: { url },
|
email: user.email,
|
||||||
templateId: this.configService.get<string>('sendgrid.forgotPasswordTemplateId'),
|
},
|
||||||
|
subject: 'Reset your Reactive Resume password',
|
||||||
|
message: `<p>Hey ${user.name}!</p> <p>You can reset your password by visiting this link: <a href="${url}">${url}</a>.</p> <p>But hurry, because it will expire in 30 minutes.</p>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await SendGrid.send(mailData);
|
await this.sendEmail(sendMailDto);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|||||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { Connection, Repository } from 'typeorm';
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { MailService } from '@/mail/mail.service';
|
import { MailService } from '@/mail/mail.service';
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export class UsersService {
|
|||||||
@InjectRepository(User) private userRepository: Repository<User>,
|
@InjectRepository(User) private userRepository: Repository<User>,
|
||||||
private schedulerRegistry: SchedulerRegistry,
|
private schedulerRegistry: SchedulerRegistry,
|
||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private connection: Connection
|
private dataSource: DataSource
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findById(id: number): Promise<User> {
|
async findById(id: number): Promise<User> {
|
||||||
@ -93,7 +93,7 @@ export class UsersService {
|
|||||||
const user = await this.findByEmail(email);
|
const user = await this.findByEmail(email);
|
||||||
|
|
||||||
const resetToken = randomBytes(32).toString('hex');
|
const resetToken = randomBytes(32).toString('hex');
|
||||||
const queryRunner = this.connection.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
|
||||||
const timeout = setTimeout(async () => {
|
const timeout = setTimeout(async () => {
|
||||||
await this.userRepository.update(user.id, { resetToken: null });
|
await this.userRepository.update(user.id, { resetToken: null });
|
||||||
|
|||||||
Reference in New Issue
Block a user