Manipulação de Email

Open in GitHub Open in Codespaces

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

nodemailer:

$ 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

Editar esta página