Manipulação de Email
Arquivos
invest-app-email
├── package-lock.json
├── package.json
├── prisma
│ ├── dev.db
│ ├── migrations
│ │ ├── 20230826214323_init
│ │ │ └── migration.sql
│ │ ├── 20230902014613_create_category
│ │ │ └── migration.sql
│ │ ├── 20230902065351_create_user
│ │ │ └── migration.sql
│ │ ├── 20230902111235_create_investment_broker_interest_created_at
│ │ │ └── migration.sql
│ │ ├── 20230902131930_create_investment_user_cascade
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── config
│ └── mail.js
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
├── routes.js
└── services
└── SendMail.js
Arquivos
invest-app-email
├── package-lock.json
├── package.json
├── prisma
│ ├── dev.db
│ ├── migrations
│ │ ├── 20230826214323_init
│ │ │ └── migration.sql
│ │ ├── 20230902014613_create_category
│ │ │ └── migration.sql
│ │ ├── 20230902065351_create_user
│ │ │ └── migration.sql
│ │ ├── 20230902111235_create_investment_broker_interest_created_at
│ │ │ └── migration.sql
│ │ ├── 20230902131930_create_investment_user_cascade
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── config
│ └── mail.js
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
├── routes.js
└── services
└── SendMail.js
Back-end
$ npm install nodemailer
$ npm install nodemailer
/codes/expressjs/invest-app-email/.env.example
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
# EMAIL
EMAIL_HOST=smtp.ethereal.email
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=
EMAIL_PASS=
/codes/expressjs/invest-app-email/.env.example
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
# EMAIL
EMAIL_HOST=smtp.ethereal.email
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=
EMAIL_PASS=
Transport: Etherial, AWS SES, Mailtrap, TempMail, Mailgum, Sendgrid, Gmail
/codes/expressjs/invest-app-email/src/config/mail.js
import nodemailer from 'nodemailer';
async function mailConfig() {
const config = {
host: process.env.EMAIL_HOST,
port: Number(process.env.EMAIL_PORT),
secure: process.env.EMAIL_SECURE == 'true',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
};
if (process.env.NODE_ENV === 'development') {
const testAccount = await nodemailer.createTestAccount();
config.auth = {
user: testAccount.user,
pass: testAccount.pass,
};
}
return config;
}
export default mailConfig;
/codes/expressjs/invest-app-email/src/config/mail.js
import nodemailer from 'nodemailer';
async function mailConfig() {
const config = {
host: process.env.EMAIL_HOST,
port: Number(process.env.EMAIL_PORT),
secure: process.env.EMAIL_SECURE == 'true',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
};
if (process.env.NODE_ENV === 'development') {
const testAccount = await nodemailer.createTestAccount();
config.auth = {
user: testAccount.user,
pass: testAccount.pass,
};
}
return config;
}
export default mailConfig;
/codes/expressjs/invest-app-email/src/services/SendMail.js
import nodemailer from 'nodemailer';
import mailConfig from '../config/mail.js';
async function createNewUser(to) {
try {
const config = await mailConfig();
const transporter = nodemailer.createTransport(config);
const info = await transporter.sendMail({
from: 'noreplay@email.com',
to,
subject: 'Conta criada no Foods App',
text: `Conta criada com sucesso.\n\nAcesse o aplicativo para gerenciar o cadastro de comidas.`,
html: `<h1>Conta criada com sucesso.</h1><p>Acesse o aplicativo para gerenciar o cadastro de comidas.</p>`,
});
if (process.env.NODE_ENV === 'development') {
console.log(`Send email: ${nodemailer.getTestMessageUrl(info)}`);
}
} catch (err) {
throw new Error(err);
}
}
export default { createNewUser };
/codes/expressjs/invest-app-email/src/services/SendMail.js
import nodemailer from 'nodemailer';
import mailConfig from '../config/mail.js';
async function createNewUser(to) {
try {
const config = await mailConfig();
const transporter = nodemailer.createTransport(config);
const info = await transporter.sendMail({
from: 'noreplay@email.com',
to,
subject: 'Conta criada no Foods App',
text: `Conta criada com sucesso.\n\nAcesse o aplicativo para gerenciar o cadastro de comidas.`,
html: `<h1>Conta criada com sucesso.</h1><p>Acesse o aplicativo para gerenciar o cadastro de comidas.</p>`,
});
if (process.env.NODE_ENV === 'development') {
console.log(`Send email: ${nodemailer.getTestMessageUrl(info)}`);
}
} catch (err) {
throw new Error(err);
}
}
export default { createNewUser };
/codes/expressjs/invest-app-email/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import SendMail from './services/SendMail.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
class HTTPError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post(
'/investments',
isAuthenticated,
validate(
z.object({
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const userId = req.userId;
const createdInvestment = await Investment.create({
...investment,
userId,
});
return res.json(createdInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to create investment', 400);
}
}
);
router.get(
'/investments',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
const userId = req.userId;
let investments;
if (name) {
investments = await Investment.read({ name, userId });
} else {
investments = await Investment.read({ userId });
}
return res.json(investments);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.get(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
const userId = req.userId;
const investment = await Investment.readById(id, { userId });
return res.json(investment);
} catch (error) {
throw new HTTPError('Unable to find investment', 400);
}
}
);
router.put(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const id = req.params.id;
const userId = req.userId;
const updatedInvestment = await Investment.update({
...investment,
id,
userId,
});
return res.json(updatedInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to update investment', 400);
}
}
);
router.delete(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
if (await Investment.remove(id)) {
return res.sendStatus(204);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to remove investment', 400);
}
}
);
router.get(
'/categories',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
let categories;
if (name) {
categories = await Category.read({ name });
} else {
categories = await Category.read();
}
return res.json(categories);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.post(
'/users',
validate(
z.object({
body: z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const user = req.body;
delete user.confirmationPassword;
const newUser = await User.create(user);
delete newUser.password;
await SendMail.createNewUser(user.email);
res.status(201).json(newUser);
} catch (error) {
if (
error.message.includes(
'Unique constraint failed on the fields: (`email`)'
)
) {
throw new HTTPError('Email already exists', 400);
}
throw new HTTPError('Unable to create user', 400);
}
}
);
router.get('/users/me', isAuthenticated, async (req, res) => {
try {
const userId = req.userId;
const user = await User.readById(userId);
delete user.password;
return res.json(user);
} catch (error) {
throw new HTTPError('Unable to find user', 400);
}
});
router.post(
'/signin',
validate(
z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const { email, password } = req.body;
const { id: userId, password: hash } = await User.read({ email });
const match = await bcrypt.compare(password, hash);
if (match) {
const token = jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: 3600000 } // 1h
);
return res.json({ auth: true, token });
} else {
throw new Error('User not found');
}
} catch (error) {
res.status(401).json({ error: 'User not found' });
}
}
);
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((error, req, res, next) => {
// console.error(error.stack);
if (error instanceof HTTPError) {
return res.status(error.code).json({ message: error.message });
} else {
return res.status(500).json({ message: 'Something broke!' });
}
});
export default router;
/codes/expressjs/invest-app-email/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import SendMail from './services/SendMail.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
class HTTPError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post(
'/investments',
isAuthenticated,
validate(
z.object({
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const userId = req.userId;
const createdInvestment = await Investment.create({
...investment,
userId,
});
return res.json(createdInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to create investment', 400);
}
}
);
router.get(
'/investments',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
const userId = req.userId;
let investments;
if (name) {
investments = await Investment.read({ name, userId });
} else {
investments = await Investment.read({ userId });
}
return res.json(investments);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.get(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
const userId = req.userId;
const investment = await Investment.readById(id, { userId });
return res.json(investment);
} catch (error) {
throw new HTTPError('Unable to find investment', 400);
}
}
);
router.put(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const id = req.params.id;
const userId = req.userId;
const updatedInvestment = await Investment.update({
...investment,
id,
userId,
});
return res.json(updatedInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to update investment', 400);
}
}
);
router.delete(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
if (await Investment.remove(id)) {
return res.sendStatus(204);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to remove investment', 400);
}
}
);
router.get(
'/categories',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
let categories;
if (name) {
categories = await Category.read({ name });
} else {
categories = await Category.read();
}
return res.json(categories);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.post(
'/users',
validate(
z.object({
body: z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const user = req.body;
delete user.confirmationPassword;
const newUser = await User.create(user);
delete newUser.password;
await SendMail.createNewUser(user.email);
res.status(201).json(newUser);
} catch (error) {
if (
error.message.includes(
'Unique constraint failed on the fields: (`email`)'
)
) {
throw new HTTPError('Email already exists', 400);
}
throw new HTTPError('Unable to create user', 400);
}
}
);
router.get('/users/me', isAuthenticated, async (req, res) => {
try {
const userId = req.userId;
const user = await User.readById(userId);
delete user.password;
return res.json(user);
} catch (error) {
throw new HTTPError('Unable to find user', 400);
}
});
router.post(
'/signin',
validate(
z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const { email, password } = req.body;
const { id: userId, password: hash } = await User.read({ email });
const match = await bcrypt.compare(password, hash);
if (match) {
const token = jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: 3600000 } // 1h
);
return res.json({ auth: true, token });
} else {
throw new Error('User not found');
}
} catch (error) {
res.status(401).json({ error: 'User not found' });
}
}
);
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((error, req, res, next) => {
// console.error(error.stack);
if (error instanceof HTTPError) {
return res.status(error.code).json({ message: error.message });
} else {
return res.status(500).json({ message: 'Something broke!' });
}
});
export default router;
Preview
Email Enviado: https://ethereal.email/
Send email: https://ethereal.email/message/YFeYRLypNQPVnimlYFeYSPKgtgZN.SfxAAAAAciukP6BiWVxTD0koWBy59A
Send email: https://ethereal.email/message/YFeYRLypNQPVnimlYFeYSPKgtgZN.SfxAAAAAciukP6BiWVxTD0koWBy59A