Autenticação

Open in GitHub

Arquivos
monitor-app-prismajs-auth
├── README.md
├── back
│   ├── Dockerfile
│   ├── docker-compose.debug.yml
│   ├── docker-compose.yml
│   ├── package-lock.json
│   ├── package.json
│   ├── prisma
│   │   ├── dev.db
│   │   ├── erd.svg
│   │   ├── migrations
│   │   │   ├── 20240228124425_init
│   │   │   │   └── migration.sql
│   │   │   ├── 20240303172942_create_ping_stats_icmp_user_tags
│   │   │   │   └── migration.sql
│   │   │   ├── 20240305071549_create_user
│   │   │   │   └── migration.sql
│   │   │   └── migration_lock.toml
│   │   ├── schema.prisma
│   │   └── seed.js
│   ├── requests.http
│   ├── src
│   │   ├── database
│   │   │   └── database.js
│   │   ├── index.js
│   │   ├── lib
│   │   │   └── ping.js
│   │   ├── middleware
│   │   │   └── auth.js
│   │   ├── models
│   │   │   ├── Hosts.js
│   │   │   ├── Pings.js
│   │   │   ├── Tags.js
│   │   │   └── Users.js
│   │   ├── routes.js
│   │   └── routes.test.js
│   └── temp
│       ├── Dockerfile
│       └── docker-compose.yml
├── front
│   ├── css
│   │   └── style.css
│   ├── home.html
│   ├── js
│   │   ├── components
│   │   │   ├── HostForm.js
│   │   │   ├── HostTableRow.js
│   │   │   ├── LineChart.js
│   │   │   ├── Modal.js
│   │   │   └── Toast.js
│   │   ├── home.js
│   │   ├── lib
│   │   │   ├── dom.js
│   │   │   └── hosts.js
│   │   ├── services
│   │   │   ├── auth.js
│   │   │   └── storage.js
│   │   ├── signin.js
│   │   └── signup.js
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   └── vite.svg
│   ├── signin.html
│   ├── signup.html
│   └── vite.config.js
└── screenshots
    ├── home.png
    └── signin.png
Arquivos
monitor-app-prismajs-auth
├── README.md
├── back
│   ├── Dockerfile
│   ├── docker-compose.debug.yml
│   ├── docker-compose.yml
│   ├── package-lock.json
│   ├── package.json
│   ├── prisma
│   │   ├── dev.db
│   │   ├── erd.svg
│   │   ├── migrations
│   │   │   ├── 20240228124425_init
│   │   │   │   └── migration.sql
│   │   │   ├── 20240303172942_create_ping_stats_icmp_user_tags
│   │   │   │   └── migration.sql
│   │   │   ├── 20240305071549_create_user
│   │   │   │   └── migration.sql
│   │   │   └── migration_lock.toml
│   │   ├── schema.prisma
│   │   └── seed.js
│   ├── requests.http
│   ├── src
│   │   ├── database
│   │   │   └── database.js
│   │   ├── index.js
│   │   ├── lib
│   │   │   └── ping.js
│   │   ├── middleware
│   │   │   └── auth.js
│   │   ├── models
│   │   │   ├── Hosts.js
│   │   │   ├── Pings.js
│   │   │   ├── Tags.js
│   │   │   └── Users.js
│   │   ├── routes.js
│   │   └── routes.test.js
│   └── temp
│       ├── Dockerfile
│       └── docker-compose.yml
├── front
│   ├── css
│   │   └── style.css
│   ├── home.html
│   ├── js
│   │   ├── components
│   │   │   ├── HostForm.js
│   │   │   ├── HostTableRow.js
│   │   │   ├── LineChart.js
│   │   │   ├── Modal.js
│   │   │   └── Toast.js
│   │   ├── home.js
│   │   ├── lib
│   │   │   ├── dom.js
│   │   │   └── hosts.js
│   │   ├── services
│   │   │   ├── auth.js
│   │   │   └── storage.js
│   │   ├── signin.js
│   │   └── signup.js
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   └── vite.svg
│   ├── signin.html
│   ├── signup.html
│   └── vite.config.js
└── screenshots
    ├── home.png
    └── signin.png

Router

Fluxo de Requisição:

JSON Web Token (JWT):

$ npm install jsonwebtoken
$ npm install jsonwebtoken
/codes/expressjs/monitor-app-prismajs-auth/back/.env.example
# 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"

# BCRYPT
BCRYPT_SALT=10

# JWT
JWT_SECRET=abc
/codes/expressjs/monitor-app-prismajs-auth/back/.env.example
# 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"

# BCRYPT
BCRYPT_SALT=10

# JWT
JWT_SECRET=abc
/codes/expressjs/monitor-app-prismajs-auth/back/src/middleware/auth.js
import jwt from 'jsonwebtoken';
 
export function isAuthenticated(req, res, next) {
  try {
    const { authorization } = req.headers;
 
    const [, token] = authorization.split(' ');
 
    const { userId } = jwt.verify(token, process.env.JWT_SECRET);
 
    req.userId = userId;
 
    next();
  } catch (error) {
    res.status(401).send({ auth: false, message: 'Token invalid.' });
  }
}
 
/codes/expressjs/monitor-app-prismajs-auth/back/src/middleware/auth.js
import jwt from 'jsonwebtoken';
 
export function isAuthenticated(req, res, next) {
  try {
    const { authorization } = req.headers;
 
    const [, token] = authorization.split(' ');
 
    const { userId } = jwt.verify(token, process.env.JWT_SECRET);
 
    req.userId = userId;
 
    next();
  } catch (error) {
    res.status(401).send({ auth: false, message: 'Token invalid.' });
  }
}
 
/codes/expressjs/monitor-app-prismajs-auth/back/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
 
import Host from './models/Hosts.js';
import Ping from './models/Pings.js';
import Tag from './models/Tags.js';
import User from './models/Users.js';
 
import { ping } from './lib/ping.js';
 
import { isAuthenticated } from './middleware/auth.js';
 
class HttpError extends Error {
  constructor(message, code = 400) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/hosts', isAuthenticated, async (req, res) => {
  const { name, address, tags } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const createdHost = await Host.create({ name, address, tags });
 
    return res.status(201).json(createdHost);
  } catch (error) {
    throw new HttpError('Unable to create a host');
  }
});
 
router.get('/hosts', isAuthenticated, async (req, res) => {
  const { name } = req.query;
 
  try {
    if (name) {
      const filteredHosts = await Host.read({ name });
 
      return res.json(filteredHosts);
    }
 
    const hosts = await Host.read();
 
    return res.json(hosts);
  } catch (error) {
    throw new HttpError('Unable to read hosts');
  }
});
 
router.get('/hosts/:id', isAuthenticated, async (req, res) => {
  const { id } = req.params;
 
  try {
    const host = await Host.readById(id);
 
    if (host) {
      return res.json(host);
    } else {
      throw new HttpError('Host not found');
    }
  } catch (error) {
    throw new HttpError('Unable to read a host');
  }
});
 
router.put('/hosts/:id', isAuthenticated, async (req, res) => {
  const { name, address, tags } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const updatedHost = await Host.update({ id, name, address, tags });
 
    return res.json(updatedHost);
  } catch (error) {
    throw new HttpError('Unable to update a host');
  }
});
 
router.delete('/hosts/:id', isAuthenticated, async (req, res) => {
  const { id } = req.params;
 
  try {
    await Host.remove(id);
 
    return res.sendStatus(204);
  } catch (error) {
    throw new HttpError('Unable to delete a host');
  }
});
 
router.post(
  '/hosts/:hostId/pings/:count',
  isAuthenticated,
  async (req, res) => {
    const { hostId, count } = req.params;
 
    try {
      const userId = req.userId;
 
      const host = await Host.readById(hostId);
 
      const pingResult = await ping(host.address, count);
 
      const createdPing = await Ping.create({ ...pingResult, hostId, userId });
 
      return res.json(createdPing);
    } catch (error) {
      throw new HttpError('Unable to create a ping for a host');
    }
  }
);
 
router.get('/hosts/:hostId/pings', isAuthenticated, async (req, res) => {
  const { hostId: id } = req.params;
 
  try {
    const pings = await Ping.read({ host: { id } });
 
    return res.json(pings);
  } catch (error) {
    throw new HttpError('Unable to read pings by host');
  }
});
 
router.get('/tags', isAuthenticated, async (req, res) => {
  try {
    const tags = await Tag.read();
 
    return res.json(tags);
  } catch (error) {
    throw new HttpError('Unable to read tags');
  }
});
 
router.get('/tags/:tag/hosts', isAuthenticated, async (req, res) => {
  const { tag } = req.params;
 
  try {
    const host = await Host.read({ tags: tag });
 
    return res.json(host);
  } catch (error) {
    throw new HttpError('Unable to read hosts by tag');
  }
});
 
router.get('/pings', isAuthenticated, async (req, res) => {
  try {
    const pings = await Ping.read();
 
    return res.json(pings);
  } catch (error) {
    throw new HttpError('Unable to read pings');
  }
});
 
router.post('/users', async (req, res) => {
  const { name, email, password } = req.body;
 
  if (!name || !email || !password) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const createdUser = await User.create({ name, email, password });
 
    delete createdUser.password;
 
    res.status(201).json(createdUser);
  } catch (error) {
    if (
      error.message.toLowerCase().includes('unique') &&
      error.message.toLowerCase().includes('email')
    ) {
      throw new HttpError('Email already in use');
    }
 
    throw new HttpError('Unable to create a user');
  }
});
 
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', 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: 3600 } // 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((err, req, res, next) => {
  // console.error(err.message);
  console.error(err.stack);
 
  if (err instanceof HttpError) {
    return res.status(err.code).json({ message: err.message });
  }
 
  // next(err);
  return res.status(500).json({ message: 'Something broke!' });
});
 
export default router;
 
/codes/expressjs/monitor-app-prismajs-auth/back/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
 
import Host from './models/Hosts.js';
import Ping from './models/Pings.js';
import Tag from './models/Tags.js';
import User from './models/Users.js';
 
import { ping } from './lib/ping.js';
 
import { isAuthenticated } from './middleware/auth.js';
 
class HttpError extends Error {
  constructor(message, code = 400) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/hosts', isAuthenticated, async (req, res) => {
  const { name, address, tags } = req.body;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const createdHost = await Host.create({ name, address, tags });
 
    return res.status(201).json(createdHost);
  } catch (error) {
    throw new HttpError('Unable to create a host');
  }
});
 
router.get('/hosts', isAuthenticated, async (req, res) => {
  const { name } = req.query;
 
  try {
    if (name) {
      const filteredHosts = await Host.read({ name });
 
      return res.json(filteredHosts);
    }
 
    const hosts = await Host.read();
 
    return res.json(hosts);
  } catch (error) {
    throw new HttpError('Unable to read hosts');
  }
});
 
router.get('/hosts/:id', isAuthenticated, async (req, res) => {
  const { id } = req.params;
 
  try {
    const host = await Host.readById(id);
 
    if (host) {
      return res.json(host);
    } else {
      throw new HttpError('Host not found');
    }
  } catch (error) {
    throw new HttpError('Unable to read a host');
  }
});
 
router.put('/hosts/:id', isAuthenticated, async (req, res) => {
  const { name, address, tags } = req.body;
 
  const { id } = req.params;
 
  if (!name || !address) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const updatedHost = await Host.update({ id, name, address, tags });
 
    return res.json(updatedHost);
  } catch (error) {
    throw new HttpError('Unable to update a host');
  }
});
 
router.delete('/hosts/:id', isAuthenticated, async (req, res) => {
  const { id } = req.params;
 
  try {
    await Host.remove(id);
 
    return res.sendStatus(204);
  } catch (error) {
    throw new HttpError('Unable to delete a host');
  }
});
 
router.post(
  '/hosts/:hostId/pings/:count',
  isAuthenticated,
  async (req, res) => {
    const { hostId, count } = req.params;
 
    try {
      const userId = req.userId;
 
      const host = await Host.readById(hostId);
 
      const pingResult = await ping(host.address, count);
 
      const createdPing = await Ping.create({ ...pingResult, hostId, userId });
 
      return res.json(createdPing);
    } catch (error) {
      throw new HttpError('Unable to create a ping for a host');
    }
  }
);
 
router.get('/hosts/:hostId/pings', isAuthenticated, async (req, res) => {
  const { hostId: id } = req.params;
 
  try {
    const pings = await Ping.read({ host: { id } });
 
    return res.json(pings);
  } catch (error) {
    throw new HttpError('Unable to read pings by host');
  }
});
 
router.get('/tags', isAuthenticated, async (req, res) => {
  try {
    const tags = await Tag.read();
 
    return res.json(tags);
  } catch (error) {
    throw new HttpError('Unable to read tags');
  }
});
 
router.get('/tags/:tag/hosts', isAuthenticated, async (req, res) => {
  const { tag } = req.params;
 
  try {
    const host = await Host.read({ tags: tag });
 
    return res.json(host);
  } catch (error) {
    throw new HttpError('Unable to read hosts by tag');
  }
});
 
router.get('/pings', isAuthenticated, async (req, res) => {
  try {
    const pings = await Ping.read();
 
    return res.json(pings);
  } catch (error) {
    throw new HttpError('Unable to read pings');
  }
});
 
router.post('/users', async (req, res) => {
  const { name, email, password } = req.body;
 
  if (!name || !email || !password) {
    throw new HttpError('Error when passing parameters');
  }
 
  try {
    const createdUser = await User.create({ name, email, password });
 
    delete createdUser.password;
 
    res.status(201).json(createdUser);
  } catch (error) {
    if (
      error.message.toLowerCase().includes('unique') &&
      error.message.toLowerCase().includes('email')
    ) {
      throw new HttpError('Email already in use');
    }
 
    throw new HttpError('Unable to create a user');
  }
});
 
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', 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: 3600 } // 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((err, req, res, next) => {
  // console.error(err.message);
  console.error(err.stack);
 
  if (err instanceof HttpError) {
    return res.status(err.code).json({ message: err.message });
  }
 
  // next(err);
  return res.status(500).json({ message: 'Something broke!' });
});
 
export default router;
 
/codes/expressjs/monitor-app-prismajs-auth/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
@createdUserId = {{createdUser.response.body.$.id}}
@token = {{signin.response.body.$.token}}
 
### Create User
# @name createdUser
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Create a user with same email
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Create a user without email
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Signin
# @name signin
POST {{server}}/signin
Content-Type: application/json
 
{
  "email": "luiz@email.com",
  "password": "123"
}
 
### Read current user
GET {{server}}/users/me
Authorization: bearer {{token}}
 
### Signin 401
POST {{server}}/signin
Content-Type: application/json
 
{
  "email": "luiz@email.com",
  "password": "321"
}
 
### Create a host without login
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host without name or address
POST {{server}}/hosts
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "DNS Server"
}
 
### Read hosts
GET {{server}}/hosts
Authorization: bearer {{token}}
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
Authorization: bearer {{token}}
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
 
### Read a host by id with invalid id
GET {{server}}/hosts/x
Authorization: bearer {{token}}
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1",
  "tags": [ "DNS", "Cloudflare"]
}
 
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host with invalid id
PUT {{server}}/hosts/x
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
 
### Delete a host with invalid id
DELETE {{server}}/hosts/x
Authorization: bearer {{token}}
 
### Create a ping
POST {{server}}/hosts/{{createdHostId}}/pings/3
Authorization: bearer {{token}}
 
### Read pings
GET {{server}}/hosts/{{createdHostId}}/pings
Authorization: bearer {{token}}
 
### Read tags
GET {{server}}/tags
Authorization: bearer {{token}}
 
### Read hosts by tag
GET {{server}}/tags/dns/hosts
Authorization: bearer {{token}}
 
### Read pings
GET {{server}}/pings
Authorization: bearer {{token}}
 
/codes/expressjs/monitor-app-prismajs-auth/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
@createdUserId = {{createdUser.response.body.$.id}}
@token = {{signin.response.body.$.token}}
 
### Create User
# @name createdUser
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Create a user with same email
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Create a user without email
POST {{server}}/users
Content-Type: application/json
 
{
  "name": "Luiz",
  "email": "luiz@email.com",
  "password": "123"
}
 
### Signin
# @name signin
POST {{server}}/signin
Content-Type: application/json
 
{
  "email": "luiz@email.com",
  "password": "123"
}
 
### Read current user
GET {{server}}/users/me
Authorization: bearer {{token}}
 
### Signin 401
POST {{server}}/signin
Content-Type: application/json
 
{
  "email": "luiz@email.com",
  "password": "321"
}
 
### Create a host without login
POST {{server}}/hosts
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host
# @name createHost
POST {{server}}/hosts
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "DNS Server",
  "address": "1.1.1.1"
}
 
### Create a host without name or address
POST {{server}}/hosts
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "DNS Server"
}
 
### Read hosts
GET {{server}}/hosts
Authorization: bearer {{token}}
 
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
Authorization: bearer {{token}}
 
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
 
### Read a host by id with invalid id
GET {{server}}/hosts/x
Authorization: bearer {{token}}
 
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1",
  "tags": [ "DNS", "Cloudflare"]
}
 
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS"
}
 
### Update a host with invalid id
PUT {{server}}/hosts/x
Authorization: bearer {{token}}
Content-Type: application/json
 
{
  "name": "Cloudflare DNS",
  "address": "1.1.1.1"
}
 
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
Authorization: bearer {{token}}
 
### Delete a host with invalid id
DELETE {{server}}/hosts/x
Authorization: bearer {{token}}
 
### Create a ping
POST {{server}}/hosts/{{createdHostId}}/pings/3
Authorization: bearer {{token}}
 
### Read pings
GET {{server}}/hosts/{{createdHostId}}/pings
Authorization: bearer {{token}}
 
### Read tags
GET {{server}}/tags
Authorization: bearer {{token}}
 
### Read hosts by tag
GET {{server}}/tags/dns/hosts
Authorization: bearer {{token}}
 
### Read pings
GET {{server}}/pings
Authorization: bearer {{token}}
 

Teste

Jest, Supertest:

$ npm i jest supertest -D
 
$ npm run test
$ npm i jest supertest -D
 
$ npm run test
/codes/expressjs/monitor-app-prismajs-auth/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
 
let createdHost;
 
const newHost = {
  name: 'DNS Server',
  address: '1.1.1.1',
};
 
const updatedHost = {
  name: 'Cloudflare DNS',
  address: '1.1.1.1',
  tags: ['DNS', 'Cloudflare'],
};
 
const validUser = {
  name: 'Valid',
  email: 'valid@email.com',
  password: '123',
  confirmationPassword: '123',
};
 
const invalidUser = {
  name: 'Invalid',
  email: 'invalid@email.com',
  password: '123',
  confirmationPassword: '123',
};
 
async function loadToken(user) {
  const response = await request(app).post('/signin').send(user);
 
  return response.body.token;
}
 
describe('Moniotr App', () => {
  beforeAll(() => console.log('2 - beforeAll'));
 
  describe('User Endpoints', () => {
    describe('POST /users', () => {
      it('should create a new user', async () => {
        const response = await request(app).post('/users').send(validUser);
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should not create a new user with same email', async () => {
        const response = await request(app).post('/users').send(validUser);
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not create a new user without email', async () => {
        const { name, password } = validUser;
 
        const response = await request(app)
          .post('/users')
          .send({ name, password });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /users/me', () => {
      it('should not show the current user without login', async () => {
        const response = await request(app).get('/users/me');
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show the current user', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/users/me')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Sign in Endpoints', () => {
    describe('POST /signin', () => {
      it('should login a valid user', async () => {
        const response = await request(app).post('/signin').send(validUser);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should not login an invalid user', async () => {
        const response = await request(app).post('/signin').send(invalidUser);
 
        expect(response.statusCode).toBe(401);
      });
    });
  });
 
  describe('Hosts Endpoints', () => {
    describe('POST /hosts', () => {
      it('should not create a new host without login', async () => {
        const response = await request(app).post('/hosts').send(newHost);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should create a new host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send(newHost);
 
        createdHost = response.body;
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should create a new host with tags', async () => {
        const token = await loadToken(validUser);
 
        const tags = ['DNS'];
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'Google DNS', address: '8.8.4.4', tags });
 
        expect(response.statusCode).toBe(201);
 
        const hasValidTag = response.body.tags
          .map((_) => _.tag)
          .some((tag) => tag.name === tags[0]);
 
        expect(hasValidTag).toBeTruthy();
      });
 
      it('should not create a new host without name or address', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({
            name: 'DNS Server',
          });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts', () => {
      it('should not show all hosts without login', async () => {
        const response = await request(app).get('/hosts');
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show all hosts', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list the valid host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        const hasValidHost = response.body.some(
          (host) => host.address === createdHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should show all hosts by name', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts?name=DNS')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /hosts/:hostId', () => {
      it('should not show a host by id without login', async () => {
        const response = await request(app).get(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show a host by id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.name).toBe(createdHost.name);
      });
 
      it('should not show a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/x`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to read a host');
      });
    });
 
    describe('PUT /hosts/:hostId', () => {
      it('should not update a host without login', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should update a host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list an updated host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        const hasValidHost = response.body.some(
          (host) => host.address === updatedHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should not update a host without name or address', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token)
          .send({
            name: 'Cloudflare DNS',
          });
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not update a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/x`)
          .set('Authorization', 'bearer ' + token)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to update a host');
      });
    });
 
    describe('DELETE /hosts/:hostId', () => {
      it('should not remove a host without login', async () => {
        const response = await request(app).delete(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should remove a host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .delete(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(204);
      });
 
      it('should not delete a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .delete(`/hosts/x`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to delete a host');
      });
    });
  });
 
  describe('Host Ping Endpoints', () => {
    describe('POST /hosts/:hostId/pings/:count', () => {
      it('should not create a ping with valid host without login', async () => {
        const response = await request(app).post(`/hosts/1/pings/3`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should create a ping with valid host', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send(newHost);
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.icmps.length).toBe(3);
      });
 
      it('should not create a ping with unknown ip', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'unknown host', address: '172.16.0.1' });
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not create a ping with unknown domain', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'unknown host', address: 'www.unknownhost.com' });
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts/:hostId/pings', () => {
      it('should not show a ping by hostId without login', async () => {
        const response = await request(app).get(
          `/hosts/${createdHost.id}/pings`
        );
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show a ping by hostId', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/${createdHost.id}/pings`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Tag Endpoints', () => {
    describe('GET /tags', () => {
      it('should not show tags without login', async () => {
        const response = await request(app).get(`/tags`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show tags', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/tags`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /tags/:tagName/hosts', () => {
      it('should not show hosts by tagName without login', async () => {
        const response = await request(app).get(
          `/tags/${createdHost.id}/hosts`
        );
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show hosts by tagName', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/tags/${createdHost.id}/hosts`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Ping Endpoints', () => {
    describe('GET /pings', () => {
      it('should not show pings without login', async () => {
        const response = await request(app).get(`/pings`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show pings', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/pings`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('404 Endpoints', () => {
    describe('GET /unknown-endpoint', () => {
      it('should show pings', async () => {
        const response = await request(app).get(`/unknown-endpoint`);
 
        expect(response.statusCode).toBe(404);
      });
    });
  });
});
 
/codes/expressjs/monitor-app-prismajs-auth/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
 
let createdHost;
 
const newHost = {
  name: 'DNS Server',
  address: '1.1.1.1',
};
 
const updatedHost = {
  name: 'Cloudflare DNS',
  address: '1.1.1.1',
  tags: ['DNS', 'Cloudflare'],
};
 
const validUser = {
  name: 'Valid',
  email: 'valid@email.com',
  password: '123',
  confirmationPassword: '123',
};
 
const invalidUser = {
  name: 'Invalid',
  email: 'invalid@email.com',
  password: '123',
  confirmationPassword: '123',
};
 
async function loadToken(user) {
  const response = await request(app).post('/signin').send(user);
 
  return response.body.token;
}
 
describe('Moniotr App', () => {
  beforeAll(() => console.log('2 - beforeAll'));
 
  describe('User Endpoints', () => {
    describe('POST /users', () => {
      it('should create a new user', async () => {
        const response = await request(app).post('/users').send(validUser);
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should not create a new user with same email', async () => {
        const response = await request(app).post('/users').send(validUser);
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not create a new user without email', async () => {
        const { name, password } = validUser;
 
        const response = await request(app)
          .post('/users')
          .send({ name, password });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /users/me', () => {
      it('should not show the current user without login', async () => {
        const response = await request(app).get('/users/me');
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show the current user', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/users/me')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Sign in Endpoints', () => {
    describe('POST /signin', () => {
      it('should login a valid user', async () => {
        const response = await request(app).post('/signin').send(validUser);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should not login an invalid user', async () => {
        const response = await request(app).post('/signin').send(invalidUser);
 
        expect(response.statusCode).toBe(401);
      });
    });
  });
 
  describe('Hosts Endpoints', () => {
    describe('POST /hosts', () => {
      it('should not create a new host without login', async () => {
        const response = await request(app).post('/hosts').send(newHost);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should create a new host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send(newHost);
 
        createdHost = response.body;
 
        expect(response.statusCode).toBe(201);
      });
 
      it('should create a new host with tags', async () => {
        const token = await loadToken(validUser);
 
        const tags = ['DNS'];
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'Google DNS', address: '8.8.4.4', tags });
 
        expect(response.statusCode).toBe(201);
 
        const hasValidTag = response.body.tags
          .map((_) => _.tag)
          .some((tag) => tag.name === tags[0]);
 
        expect(hasValidTag).toBeTruthy();
      });
 
      it('should not create a new host without name or address', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({
            name: 'DNS Server',
          });
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts', () => {
      it('should not show all hosts without login', async () => {
        const response = await request(app).get('/hosts');
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show all hosts', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list the valid host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        const hasValidHost = response.body.some(
          (host) => host.address === createdHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should show all hosts by name', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts?name=DNS')
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /hosts/:hostId', () => {
      it('should not show a host by id without login', async () => {
        const response = await request(app).get(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show a host by id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.name).toBe(createdHost.name);
      });
 
      it('should not show a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/x`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to read a host');
      });
    });
 
    describe('PUT /hosts/:hostId', () => {
      it('should not update a host without login', async () => {
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should update a host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(200);
      });
 
      it('should list an updated host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get('/hosts')
          .set('Authorization', 'bearer ' + token);
 
        const hasValidHost = response.body.some(
          (host) => host.address === updatedHost.address
        );
 
        expect(hasValidHost).toBeTruthy();
      });
 
      it('should not update a host without name or address', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token)
          .send({
            name: 'Cloudflare DNS',
          });
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not update a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .put(`/hosts/x`)
          .set('Authorization', 'bearer ' + token)
          .send(updatedHost);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to update a host');
      });
    });
 
    describe('DELETE /hosts/:hostId', () => {
      it('should not remove a host without login', async () => {
        const response = await request(app).delete(`/hosts/${createdHost.id}`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should remove a host', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .delete(`/hosts/${createdHost.id}`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(204);
      });
 
      it('should not delete a host with invalid id', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .delete(`/hosts/x`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
 
        expect(response.body.message).toBe('Unable to delete a host');
      });
    });
  });
 
  describe('Host Ping Endpoints', () => {
    describe('POST /hosts/:hostId/pings/:count', () => {
      it('should not create a ping with valid host without login', async () => {
        const response = await request(app).post(`/hosts/1/pings/3`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should create a ping with valid host', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send(newHost);
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
 
        expect(response.body.icmps.length).toBe(3);
      });
 
      it('should not create a ping with unknown ip', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'unknown host', address: '172.16.0.1' });
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
      });
 
      it('should not create a ping with unknown domain', async () => {
        const token = await loadToken(validUser);
 
        let response = await request(app)
          .post('/hosts')
          .set('Authorization', 'bearer ' + token)
          .send({ name: 'unknown host', address: 'www.unknownhost.com' });
 
        response = await request(app)
          .post(`/hosts/${response.body.id}/pings/3`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(400);
      });
    });
 
    describe('GET /hosts/:hostId/pings', () => {
      it('should not show a ping by hostId without login', async () => {
        const response = await request(app).get(
          `/hosts/${createdHost.id}/pings`
        );
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show a ping by hostId', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/hosts/${createdHost.id}/pings`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Tag Endpoints', () => {
    describe('GET /tags', () => {
      it('should not show tags without login', async () => {
        const response = await request(app).get(`/tags`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show tags', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/tags`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
 
    describe('GET /tags/:tagName/hosts', () => {
      it('should not show hosts by tagName without login', async () => {
        const response = await request(app).get(
          `/tags/${createdHost.id}/hosts`
        );
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show hosts by tagName', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/tags/${createdHost.id}/hosts`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('Ping Endpoints', () => {
    describe('GET /pings', () => {
      it('should not show pings without login', async () => {
        const response = await request(app).get(`/pings`);
 
        expect(response.statusCode).toBe(401);
      });
 
      it('should show pings', async () => {
        const token = await loadToken(validUser);
 
        const response = await request(app)
          .get(`/pings`)
          .set('Authorization', 'bearer ' + token);
 
        expect(response.statusCode).toBe(200);
      });
    });
  });
 
  describe('404 Endpoints', () => {
    describe('GET /unknown-endpoint', () => {
      it('should show pings', async () => {
        const response = await request(app).get(`/unknown-endpoint`);
 
        expect(response.statusCode).toBe(404);
      });
    });
  });
});
 

.skip()

describe.skip()
 
it.skip()
describe.skip()
 
it.skip()

Cobertura de Testes

/codes/expressjs/monitor-app-prismajs-auth/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "db:reset": "prisma migrate reset --force",
    "test": "npm run db:reset && node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "npm run db:reset && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "prisma": {
    "seed": "node prisma/seed.js"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "@prisma/client": "^5.10.2",
    "bcrypt": "^5.1.1",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "jsonwebtoken": "^9.0.2",
    "morgan": "^1.10.0",
    "prisma": "^5.10.2",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "@mermaid-js/mermaid-cli": "^10.8.0",
    "jest": "^29.7.0",
    "prisma-erd-generator": "^1.11.2",
    "supertest": "^6.3.4"
  }
}
 
/codes/expressjs/monitor-app-prismajs-auth/back/package.json
{
  "name": "invest-app",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "db:reset": "prisma migrate reset --force",
    "test": "npm run db:reset && node --experimental-vm-modules ./node_modules/.bin/jest src",
    "test:coverage": "npm run db:reset && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
  },
  "prisma": {
    "seed": "node prisma/seed.js"
  },
  "jest": {
    "collectCoverage": true,
    "testTimeout": 20000,
    "coverageReporters": [
      "json",
      "html"
    ]
  },
  "dependencies": {
    "@prisma/client": "^5.10.2",
    "bcrypt": "^5.1.1",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.18.2",
    "express-async-errors": "^3.1.1",
    "jsonwebtoken": "^9.0.2",
    "morgan": "^1.10.0",
    "prisma": "^5.10.2",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "@mermaid-js/mermaid-cli": "^10.8.0",
    "jest": "^29.7.0",
    "prisma-erd-generator": "^1.11.2",
    "supertest": "^6.3.4"
  }
}
 
$ npm run test:coverage
$ npm run test:coverage

View

/codes/expressjs/monitor-app-prismajs-auth/front/signup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link" href="/signin.html">Entrar</a>
            </li>
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signup.html"
                >Cadastrar</a
              >
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <main>
      <div class="container py-5">
        <h1 class="text-center mb-5 fw-bold">Cadastro de Usuário</h1>
 
        <div class="row justify-content-center">
          <form class="col-6 needs-validation" novalidate>
            <div class="mb-3">
              <label for="name" class="form-label">Nome</label>
              <input
                type="text"
                class="form-control"
                id="name"
                name="name"
                required
              />
              <div class="invalid-feedback">Informe o nome do usuário.</div>
            </div>
            <div class="mb-3">
              <label for="email" class="form-label">Email</label>
              <input
                type="email"
                class="form-control"
                id="email"
                name="email"
                required
              />
              <div class="invalid-feedback">Informe o email do usuário.</div>
            </div>
            <div class="mb-3">
              <label for="password" class="form-label">Senha</label>
              <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
              <input
                type="password"
                class="form-control"
                id="password"
                name="password"
                minlength="8"
                required
              />
              <div class="invalid-feedback">
                Informe a senha com 8 caracteres.
              </div>
            </div>
            <div class="mb-3">
              <label for="confirmationPassword" class="form-label"
                >Confirmar Senha</label
              >
              <input
                type="password"
                class="form-control"
                id="confirmationPassword"
                name="confirmationPassword"
                required
              />
              <div class="invalid-feedback">
                Informe a confirmação de senha.
              </div>
            </div>
            <div class="mb-3">
              <input type="submit" class="btn btn-primary" value="Cadastrar" />
            </div>
          </form>
        </div>
      </div>
    </main>
 
    <script src="js/signup.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-prismajs-auth/front/signup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link" href="/signin.html">Entrar</a>
            </li>
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signup.html"
                >Cadastrar</a
              >
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <main>
      <div class="container py-5">
        <h1 class="text-center mb-5 fw-bold">Cadastro de Usuário</h1>
 
        <div class="row justify-content-center">
          <form class="col-6 needs-validation" novalidate>
            <div class="mb-3">
              <label for="name" class="form-label">Nome</label>
              <input
                type="text"
                class="form-control"
                id="name"
                name="name"
                required
              />
              <div class="invalid-feedback">Informe o nome do usuário.</div>
            </div>
            <div class="mb-3">
              <label for="email" class="form-label">Email</label>
              <input
                type="email"
                class="form-control"
                id="email"
                name="email"
                required
              />
              <div class="invalid-feedback">Informe o email do usuário.</div>
            </div>
            <div class="mb-3">
              <label for="password" class="form-label">Senha</label>
              <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
              <input
                type="password"
                class="form-control"
                id="password"
                name="password"
                minlength="8"
                required
              />
              <div class="invalid-feedback">
                Informe a senha com 8 caracteres.
              </div>
            </div>
            <div class="mb-3">
              <label for="confirmationPassword" class="form-label"
                >Confirmar Senha</label
              >
              <input
                type="password"
                class="form-control"
                id="confirmationPassword"
                name="confirmationPassword"
                required
              />
              <div class="invalid-feedback">
                Informe a confirmação de senha.
              </div>
            </div>
            <div class="mb-3">
              <input type="submit" class="btn btn-primary" value="Cadastrar" />
            </div>
          </form>
        </div>
      </div>
    </main>
 
    <script src="js/signup.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/signup.js
import 'bootstrap';
 
import { $ } from './lib/dom.js';
import API from './services/storage.js';
import Toast from './components/Toast';
 
import 'bootstrap/dist/css/bootstrap.min.css';
 
Toast.create('body > main');
 
const form = $('form');
 
form.onsubmit = async (event) => {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    try {
      const { email, message } = await API.create('users', user, false);
 
      if (email) {
        location.href = '/signin.html';
      } else if (message === 'Email already in use') {
        setInputValidity('email', 'Email já cadastrado.');
 
        form.classList.add('was-validated');
      } else {
        Toast.show('Erro no cadastro');
      }
    } catch (error) {
      Toast.show('Erro no cadastro');
    }
  } else {
    form.classList.add('was-validated');
  }
};
 
[form.name, form.email, form.password].map((input) => {
  input.addEventListener('input', () => {
    input.setCustomValidity('');
 
    form.classList.remove('was-validated');
  });
});
 
form.confirmationPassword.addEventListener('input', () => {
  const password = form.password.value;
 
  const confirmationPassword = form.confirmationPassword.value;
 
  if (password !== confirmationPassword) {
    setInputValidity('confirmationPassword', 'As senhas não são iguais.');
  } else {
    form.confirmationPassword.setCustomValidity('');
 
    form.classList.remove('was-validated');
  }
});
 
function setInputValidity(input, message) {
  $(`#${input} + .invalid-feedback`).textContent = message;
 
  form[input].setCustomValidity(message);
}
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/signup.js
import 'bootstrap';
 
import { $ } from './lib/dom.js';
import API from './services/storage.js';
import Toast from './components/Toast';
 
import 'bootstrap/dist/css/bootstrap.min.css';
 
Toast.create('body > main');
 
const form = $('form');
 
form.onsubmit = async (event) => {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    try {
      const { email, message } = await API.create('users', user, false);
 
      if (email) {
        location.href = '/signin.html';
      } else if (message === 'Email already in use') {
        setInputValidity('email', 'Email já cadastrado.');
 
        form.classList.add('was-validated');
      } else {
        Toast.show('Erro no cadastro');
      }
    } catch (error) {
      Toast.show('Erro no cadastro');
    }
  } else {
    form.classList.add('was-validated');
  }
};
 
[form.name, form.email, form.password].map((input) => {
  input.addEventListener('input', () => {
    input.setCustomValidity('');
 
    form.classList.remove('was-validated');
  });
});
 
form.confirmationPassword.addEventListener('input', () => {
  const password = form.password.value;
 
  const confirmationPassword = form.confirmationPassword.value;
 
  if (password !== confirmationPassword) {
    setInputValidity('confirmationPassword', 'As senhas não são iguais.');
  } else {
    form.confirmationPassword.setCustomValidity('');
 
    form.classList.remove('was-validated');
  }
});
 
function setInputValidity(input, message) {
  $(`#${input} + .invalid-feedback`).textContent = message;
 
  form[input].setCustomValidity(message);
}
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/services/storage.js
import Auth from './auth.js';
 
const API_URL = '/api';
 
async function create(resource, data, auth = true) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'post',
    body: JSON.stringify(data),
  };
 
  if (auth) {
    config.headers.Authorization = `Bearer ${Auth.getToken()}`;
  }
 
  const res = await fetch(resource, config);
 
  const createdData = await res.json();
 
  return createdData;
}
 
async function read(resource) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'get',
    headers: {
      Authorization: `Bearer ${Auth.getToken()}`,
    },
  };
 
  const res = await fetch(resource, config);
 
  const data = await res.json();
 
  return data;
}
 
async function update(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${Auth.getToken()}`,
    },
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, config);
 
  const updatedData = await res.json();
 
  return updatedData;
}
 
async function remove(resource) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${Auth.getToken()}`,
    },
  };
 
  const res = await fetch(resource, config);
 
  return res.ok;
}
 
export default { create, read, update, remove };
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/services/storage.js
import Auth from './auth.js';
 
const API_URL = '/api';
 
async function create(resource, data, auth = true) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    headers: {
      'Content-Type': 'application/json',
    },
    method: 'post',
    body: JSON.stringify(data),
  };
 
  if (auth) {
    config.headers.Authorization = `Bearer ${Auth.getToken()}`;
  }
 
  const res = await fetch(resource, config);
 
  const createdData = await res.json();
 
  return createdData;
}
 
async function read(resource) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'get',
    headers: {
      Authorization: `Bearer ${Auth.getToken()}`,
    },
  };
 
  const res = await fetch(resource, config);
 
  const data = await res.json();
 
  return data;
}
 
async function update(resource, data) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${Auth.getToken()}`,
    },
    body: JSON.stringify(data),
  };
 
  const res = await fetch(resource, config);
 
  const updatedData = await res.json();
 
  return updatedData;
}
 
async function remove(resource) {
  resource = `${API_URL}/${resource}`;
 
  const config = {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${Auth.getToken()}`,
    },
  };
 
  const res = await fetch(resource, config);
 
  return res.ok;
}
 
export default { create, read, update, remove };
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/services/auth.js
function isAuthenticated() {
  if (!getToken()) {
    window.location.href = '/signin.html';
  } else {
    return true;
  }
}
 
function getToken() {
  return localStorage.getItem('@hostMonitor:token');
}
 
function signin(token) {
  localStorage.setItem('@hostMonitor:token', token);
 
  window.location.href = '/home.html';
}
 
function signout() {
  localStorage.removeItem('@hostMonitor:token');
 
  window.location.href = '/signin.html';
}
 
export default { isAuthenticated, getToken, signin, signout };
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/services/auth.js
function isAuthenticated() {
  if (!getToken()) {
    window.location.href = '/signin.html';
  } else {
    return true;
  }
}
 
function getToken() {
  return localStorage.getItem('@hostMonitor:token');
}
 
function signin(token) {
  localStorage.setItem('@hostMonitor:token', token);
 
  window.location.href = '/home.html';
}
 
function signout() {
  localStorage.removeItem('@hostMonitor:token');
 
  window.location.href = '/signin.html';
}
 
export default { isAuthenticated, getToken, signin, signout };
 

/codes/expressjs/monitor-app-prismajs-auth/front/home.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Monitor App</title>
    <script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#" onclick="signout()">Sair</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
 
    <div class="container">
      <h1 class="text-center m-0 p-5">Monitor App</h1>
 
      <div class="row row-cols-1 row-cols-md-2 g-4">
        <div class="col">
          <div class="card table-hosts">
            <div class="card-header">
              <h5 class="text-center">
                Hosts
                <div
                  class="float-end create-host-event lh-base"
                  data-bs-toggle="offcanvas"
                  data-bs-target="#offcanvasRight"
                  aria-controls="offcanvasRight"
                >
                  <iconify-icon icon="bx:plus"></iconify-icon>
                </div>
              </h5>
            </div>
            <div class="card-body">
              <table class="table table-hosts">
                <thead>
                  <tr>
                    <th>Nome</th>
                    <th>Endereço</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
          </div>
        </div>
        <div class="col">
          <div class="card">
            <div class="card-header">
              <h5 class="text-center">Times</h5>
            </div>
            <div class="card-body">
              <canvas id="chart-line"></canvas>
            </div>
          </div>
        </div>
      </div>
    </div>
 
    <script type="module" src="/js/home.js"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-prismajs-auth/front/home.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Monitor App</title>
    <script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
      <div class="container">
        <a class="navbar-brand" href="#">Monitor App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#" onclick="signout()">Sair</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
 
    <div class="container">
      <h1 class="text-center m-0 p-5">Monitor App</h1>
 
      <div class="row row-cols-1 row-cols-md-2 g-4">
        <div class="col">
          <div class="card table-hosts">
            <div class="card-header">
              <h5 class="text-center">
                Hosts
                <div
                  class="float-end create-host-event lh-base"
                  data-bs-toggle="offcanvas"
                  data-bs-target="#offcanvasRight"
                  aria-controls="offcanvasRight"
                >
                  <iconify-icon icon="bx:plus"></iconify-icon>
                </div>
              </h5>
            </div>
            <div class="card-body">
              <table class="table table-hosts">
                <thead>
                  <tr>
                    <th>Nome</th>
                    <th>Endereço</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
          </div>
        </div>
        <div class="col">
          <div class="card">
            <div class="card-header">
              <h5 class="text-center">Times</h5>
            </div>
            <div class="card-body">
              <canvas id="chart-line"></canvas>
            </div>
          </div>
        </div>
      </div>
    </div>
 
    <script type="module" src="/js/home.js"></script>
  </body>
</html>
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/home.js
import 'bootstrap';
import 'iconify-icon';
 
import HostForm from './components/HostForm';
import Modal from './components/Modal';
import * as LineChart from './components/LineChart';
import Hosts from './lib/hosts';
import Auth from './services/auth.js';
 
import 'bootstrap/dist/css/bootstrap.css';
 
import '../css/style.css';
 
window.signout = Auth.signout;
 
if (Auth.isAuthenticated()) {
  Hosts.load();
 
  HostForm.create();
 
  Modal.create();
 
  LineChart.create('chart-line');
}
 
/codes/expressjs/monitor-app-prismajs-auth/front/js/home.js
import 'bootstrap';
import 'iconify-icon';
 
import HostForm from './components/HostForm';
import Modal from './components/Modal';
import * as LineChart from './components/LineChart';
import Hosts from './lib/hosts';
import Auth from './services/auth.js';
 
import 'bootstrap/dist/css/bootstrap.css';
 
import '../css/style.css';
 
window.signout = Auth.signout;
 
if (Auth.isAuthenticated()) {
  Hosts.load();
 
  HostForm.create();
 
  Modal.create();
 
  LineChart.create('chart-line');
}
 

Editar esta página