Cadastro de Usuário
Arquivos
monitor-app-prismajs-user
├── back
│ ├── 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
│ ├── models
│ │ ├── Hosts.js
│ │ ├── Pings.js
│ │ ├── Tags.js
│ │ └── Users.js
│ ├── routes.js
│ └── routes.test.js
└── 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
│ │ └── storage.js
│ ├── signin.js
│ └── signup.js
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── signin.html
├── signup.html
└── vite.config.js
Arquivos
monitor-app-prismajs-user
├── back
│ ├── 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
│ ├── models
│ │ ├── Hosts.js
│ │ ├── Pings.js
│ │ ├── Tags.js
│ │ └── Users.js
│ ├── routes.js
│ └── routes.test.js
└── 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
│ │ └── storage.js
│ ├── signin.js
│ └── signup.js
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── signin.html
├── signup.html
└── vite.config.js
Banco de Dados
Modelo Lógico
Modelo Físico
Migration
/codes/expressjs/monitor-app-prismajs-user/back/prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
// generator erd {
// provider = "prisma-erd-generator"
// }
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Tag {
id String @id @default(uuid())
name String @unique
hosts TagsOnHosts[]
}
model Host {
id String @id @default(uuid())
name String
address String
pings Ping[]
tags TagsOnHosts[]
}
model TagsOnHosts {
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tagId String
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade, onUpdate: Cascade)
hostId String
@@id([tagId, hostId])
}
model Ping {
id String @id @default(uuid())
createAt DateTime @default(now())
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade, onUpdate: Cascade)
hostId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
icmps Icmp[]
stats Stats?
}
model Stats {
id String @id @default(uuid())
transmitted Int
received Int
time Float
ping Ping @relation(fields: [pingId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pingId String @unique
}
model Icmp {
id String @id @default(uuid())
seq Int
ttl Int
time Float
ping Ping @relation(fields: [pingId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pingId String
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
pings Ping[]
}
/codes/expressjs/monitor-app-prismajs-user/back/prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
// generator erd {
// provider = "prisma-erd-generator"
// }
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Tag {
id String @id @default(uuid())
name String @unique
hosts TagsOnHosts[]
}
model Host {
id String @id @default(uuid())
name String
address String
pings Ping[]
tags TagsOnHosts[]
}
model TagsOnHosts {
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade, onUpdate: Cascade)
tagId String
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade, onUpdate: Cascade)
hostId String
@@id([tagId, hostId])
}
model Ping {
id String @id @default(uuid())
createAt DateTime @default(now())
host Host @relation(fields: [hostId], references: [id], onDelete: Cascade, onUpdate: Cascade)
hostId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
icmps Icmp[]
stats Stats?
}
model Stats {
id String @id @default(uuid())
transmitted Int
received Int
time Float
ping Ping @relation(fields: [pingId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pingId String @unique
}
model Icmp {
id String @id @default(uuid())
seq Int
ttl Int
time Float
ping Ping @relation(fields: [pingId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pingId String
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
pings Ping[]
}
$ npx prisma migrate reset
$ npx prisma migrate dev --name create_user
$ npx prisma migrate reset
$ npx prisma migrate dev --name create_user
Model
$ npm install bcrypt
$ npm install bcrypt
12345678 + 10
=> $2a$10$13Aak6RFaLSM2BWra67RA.KElfrt41YQsTjy9nul0bxBhXI2vjiPe
12345678 + 10
=> $2a$10$13Aak6RFaLSM2BWra67RA.KElfrt41YQsTjy9nul0bxBhXI2vjiPe
12345678 + 10
=> $2a$10$CZHQV57ViPynbUbeffLU7.cTV8Rr9PjlJXkoe/Xu1FIUXA/I0we5C
12345678 + 10
=> $2a$10$CZHQV57ViPynbUbeffLU7.cTV8Rr9PjlJXkoe/Xu1FIUXA/I0we5C
12345678 + $2a$10$13Aak6RFaLSM2BWra67RA.KElfrt41YQsTjy9nul0bxBhXI2vjiPe
=> true
12345678 + $2a$10$13Aak6RFaLSM2BWra67RA.KElfrt41YQsTjy9nul0bxBhXI2vjiPe
=> true
/codes/expressjs/monitor-app-prismajs-user/back/src/models/Users.js
import bcrypt from 'bcrypt';
import prisma from '../database/database.js';
const saltRounds = Number(process.env.BCRYPT_SALT);
async function create({ name, email, password }) {
const hash = await bcrypt.hash(password, saltRounds);
const data = { name, email, password: hash };
const createdUser = await prisma.user.create({
data,
});
return createdUser;
}
async function read(where) {
const users = await prisma.user.findMany({
where,
});
if (users.length === 1 && where) {
return users[0];
}
return users;
}
async function readById(id) {
const user = await prisma.user.findUnique({
where: {
id,
},
});
return user;
}
async function update({ id, name, email, password }) {
const hash = await bcrypt.hash(password, saltRounds);
const data = { name, email, password: hash };
const updatedUser = await prisma.user.update({
where: {
id,
},
data,
});
return updatedUser;
}
async function remove(id) {
await prisma.user.delete({
where: {
id,
},
});
}
export default { create, read, readById, update, remove };
/codes/expressjs/monitor-app-prismajs-user/back/src/models/Users.js
import bcrypt from 'bcrypt';
import prisma from '../database/database.js';
const saltRounds = Number(process.env.BCRYPT_SALT);
async function create({ name, email, password }) {
const hash = await bcrypt.hash(password, saltRounds);
const data = { name, email, password: hash };
const createdUser = await prisma.user.create({
data,
});
return createdUser;
}
async function read(where) {
const users = await prisma.user.findMany({
where,
});
if (users.length === 1 && where) {
return users[0];
}
return users;
}
async function readById(id) {
const user = await prisma.user.findUnique({
where: {
id,
},
});
return user;
}
async function update({ id, name, email, password }) {
const hash = await bcrypt.hash(password, saltRounds);
const data = { name, email, password: hash };
const updatedUser = await prisma.user.update({
where: {
id,
},
data,
});
return updatedUser;
}
async function remove(id) {
await prisma.user.delete({
where: {
id,
},
});
}
export default { create, read, readById, update, remove };
/codes/expressjs/monitor-app-prismajs-user/back/src/models/Pings.js
import prisma from '../database/database.js';
async function create({ icmps, stats, host }) {
if (!icmps || !stats || !host) {
throw new Error('Error when passing parameters');
}
const createdPing = await prisma.ping.create({
data: {
icmps: {
create: icmps,
},
stats: {
create: stats,
},
host: {
connect: {
id: host.id,
},
},
},
include: {
icmps: true,
stats: true,
host: true,
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
return createdPing;
}
async function read(where = {}) {
const pings = await prisma.ping.findMany({
where,
include: {
icmps: true,
stats: true,
host: true,
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
return pings;
}
export default { create, read };
/codes/expressjs/monitor-app-prismajs-user/back/src/models/Pings.js
import prisma from '../database/database.js';
async function create({ icmps, stats, host }) {
if (!icmps || !stats || !host) {
throw new Error('Error when passing parameters');
}
const createdPing = await prisma.ping.create({
data: {
icmps: {
create: icmps,
},
stats: {
create: stats,
},
host: {
connect: {
id: host.id,
},
},
},
include: {
icmps: true,
stats: true,
host: true,
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
return createdPing;
}
async function read(where = {}) {
const pings = await prisma.ping.findMany({
where,
include: {
icmps: true,
stats: true,
host: true,
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
return pings;
}
export default { create, read };
Router
$ npm install dotenv
$ npm install dotenv
/codes/expressjs/monitor-app-prismajs-user/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
/codes/expressjs/monitor-app-prismajs-user/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
/codes/expressjs/monitor-app-prismajs-user/back/src/index.js
import 'express-async-errors';
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import router from './routes.js';
const server = express();
server.use(morgan('tiny'));
server.use(
cors({
origin: '*',
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
preflightContinue: false,
})
);
server.use(express.json());
server.use(router);
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
export default server;
/codes/expressjs/monitor-app-prismajs-user/back/src/index.js
import 'express-async-errors';
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import router from './routes.js';
const server = express();
server.use(morgan('tiny'));
server.use(
cors({
origin: '*',
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
preflightContinue: false,
})
);
server.use(express.json());
server.use(router);
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
export default server;
/codes/expressjs/monitor-app-prismajs-user/back/src/routes.js
import express from 'express';
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';
class HttpError extends Error {
constructor(message, code = 400) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post('/hosts', 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', 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', 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', 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', 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', async (req, res) => {
const { hostId, count } = req.params;
try {
const host = await Host.readById(hostId);
const pingResult = await ping(host.address, count);
const createdPing = await Ping.create({ ...pingResult, host });
return res.json(createdPing);
} catch (error) {
throw new HttpError('Unable to create a ping for a host');
}
});
router.get('/hosts/:hostId/pings', 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', 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', 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', 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');
}
});
// 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-user/back/src/routes.js
import express from 'express';
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';
class HttpError extends Error {
constructor(message, code = 400) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post('/hosts', 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', 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', 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', 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', 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', async (req, res) => {
const { hostId, count } = req.params;
try {
const host = await Host.readById(hostId);
const pingResult = await ping(host.address, count);
const createdPing = await Ping.create({ ...pingResult, host });
return res.json(createdPing);
} catch (error) {
throw new HttpError('Unable to create a ping for a host');
}
});
router.get('/hosts/:hostId/pings', 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', 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', 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', 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');
}
});
// 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-user/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
@createdUserId = {{createdUser.response.body.$.id}}
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server",
"address": "1.1.1.1"
}
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server"
}
### Read hosts
GET {{server}}/hosts
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
### Read a host by id with invalid id
GET {{server}}/hosts/x
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
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}}
Content-Type: application/json
{
"name": "Cloudflare DNS"
}
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
### Delete a host with invalid id
DELETE {{server}}/hosts/x
### Create a ping
POST {{server}}/hosts/{{createdHostId}}/pings/3
### Read pings
GET {{server}}/hosts/{{createdHostId}}/pings
### Read tags
GET {{server}}/tags
### Read hosts by tag
GET {{server}}/tags/dns/hosts
### Read pings
GET {{server}}/pings
### 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"
}
/codes/expressjs/monitor-app-prismajs-user/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
@createdUserId = {{createdUser.response.body.$.id}}
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server",
"address": "1.1.1.1"
}
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server"
}
### Read hosts
GET {{server}}/hosts
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
### Read a host by id with invalid id
GET {{server}}/hosts/x
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
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}}
Content-Type: application/json
{
"name": "Cloudflare DNS"
}
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
### Delete a host with invalid id
DELETE {{server}}/hosts/x
### Create a ping
POST {{server}}/hosts/{{createdHostId}}/pings/3
### Read pings
GET {{server}}/hosts/{{createdHostId}}/pings
### Read tags
GET {{server}}/tags
### Read hosts by tag
GET {{server}}/tags/dns/hosts
### Read pings
GET {{server}}/pings
### 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"
}
Teste
$ npm i jest supertest -D
$ npm run test
$ npm i jest supertest -D
$ npm run test
/codes/expressjs/monitor-app-prismajs-user/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',
};
describe('Moniotr App', () => {
describe('Hosts Endpoints', () => {
describe('POST /hosts', () => {
it('should create a new host', async () => {
const response = await request(app).post('/hosts').send(newHost);
createdHost = response.body;
expect(response.statusCode).toBe(201);
});
it('should create a new host with tags', async () => {
const tags = ['DNS'];
const response = await request(app)
.post('/hosts')
.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 response = await request(app).post('/hosts').send({
name: 'DNS Server',
});
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts', () => {
it('should show all hosts', async () => {
const response = await request(app).get('/hosts');
expect(response.statusCode).toBe(200);
});
it('should list the valid host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === createdHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should show all hosts by name', async () => {
const response = await request(app).get('/hosts?name=DNS');
expect(response.statusCode).toBe(200);
});
});
describe('GET /hosts/:hostId', () => {
it('should show a host by id', async () => {
const response = await request(app).get(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(200);
expect(response.body.name).toBe(createdHost.name);
});
it('should not show a host with invalid id', async () => {
const response = await request(app).get(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to read a host');
});
});
describe('PUT /hosts/:hostId', () => {
it('should update a host', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send(updatedHost);
expect(response.statusCode).toBe(200);
});
it('should list an updated host', async () => {
const response = await request(app).get('/hosts');
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 response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send({
name: 'Cloudflare DNS',
});
expect(response.statusCode).toBe(400);
});
it('should not update a host with invalid id', async () => {
const response = await request(app).put(`/hosts/x`).send(updatedHost);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to update a host');
});
});
describe('DELETE /hosts/:hostId', () => {
it('should remove a host', async () => {
const response = await request(app).delete(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(204);
});
it('should not delete a host with invalid id', async () => {
const response = await request(app).delete(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to delete a host');
});
});
});
describe.skip('Host Ping Endpoints', () => {
describe('POST /hosts/:hostId/pings/:count', () => {
it('should create a ping with valid host', async () => {
let response = await request(app).post('/hosts').send(newHost);
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(200);
expect(response.body.icmps.length).toBe(3);
});
it('should not create a ping with unknown ip', async () => {
let response = await request(app)
.post('/hosts')
.send({ name: 'unknown host', address: '172.16.0.1' });
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(400);
});
it('should not create a ping with unknown domain', async () => {
let response = await request(app)
.post('/hosts')
.send({ name: 'unknown host', address: 'www.unknownhost.com' });
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts/:hostId/pings', () => {
it('should show a ping by hostId', async () => {
const response = await request(app).get(
`/hosts/${createdHost.id}/pings`
);
expect(response.statusCode).toBe(200);
});
});
});
describe('Tag Endpoints', () => {
describe('GET /tags', () => {
it('should show tags', async () => {
const response = await request(app).get(`/tags`);
expect(response.statusCode).toBe(200);
});
});
describe('GET /tags/:tagName/hosts', () => {
it('should show hosts by tagName', async () => {
const response = await request(app).get(
`/tags/${createdHost.id}/hosts`
);
expect(response.statusCode).toBe(200);
});
});
});
describe('Ping Endpoints', () => {
describe('GET /pings', () => {
it('should show pings', async () => {
const response = await request(app).get(`/pings`);
expect(response.statusCode).toBe(200);
});
});
});
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('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-user/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',
};
describe('Moniotr App', () => {
describe('Hosts Endpoints', () => {
describe('POST /hosts', () => {
it('should create a new host', async () => {
const response = await request(app).post('/hosts').send(newHost);
createdHost = response.body;
expect(response.statusCode).toBe(201);
});
it('should create a new host with tags', async () => {
const tags = ['DNS'];
const response = await request(app)
.post('/hosts')
.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 response = await request(app).post('/hosts').send({
name: 'DNS Server',
});
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts', () => {
it('should show all hosts', async () => {
const response = await request(app).get('/hosts');
expect(response.statusCode).toBe(200);
});
it('should list the valid host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === createdHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should show all hosts by name', async () => {
const response = await request(app).get('/hosts?name=DNS');
expect(response.statusCode).toBe(200);
});
});
describe('GET /hosts/:hostId', () => {
it('should show a host by id', async () => {
const response = await request(app).get(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(200);
expect(response.body.name).toBe(createdHost.name);
});
it('should not show a host with invalid id', async () => {
const response = await request(app).get(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to read a host');
});
});
describe('PUT /hosts/:hostId', () => {
it('should update a host', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send(updatedHost);
expect(response.statusCode).toBe(200);
});
it('should list an updated host', async () => {
const response = await request(app).get('/hosts');
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 response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send({
name: 'Cloudflare DNS',
});
expect(response.statusCode).toBe(400);
});
it('should not update a host with invalid id', async () => {
const response = await request(app).put(`/hosts/x`).send(updatedHost);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to update a host');
});
});
describe('DELETE /hosts/:hostId', () => {
it('should remove a host', async () => {
const response = await request(app).delete(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(204);
});
it('should not delete a host with invalid id', async () => {
const response = await request(app).delete(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to delete a host');
});
});
});
describe.skip('Host Ping Endpoints', () => {
describe('POST /hosts/:hostId/pings/:count', () => {
it('should create a ping with valid host', async () => {
let response = await request(app).post('/hosts').send(newHost);
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(200);
expect(response.body.icmps.length).toBe(3);
});
it('should not create a ping with unknown ip', async () => {
let response = await request(app)
.post('/hosts')
.send({ name: 'unknown host', address: '172.16.0.1' });
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(400);
});
it('should not create a ping with unknown domain', async () => {
let response = await request(app)
.post('/hosts')
.send({ name: 'unknown host', address: 'www.unknownhost.com' });
response = await request(app).post(
`/hosts/${response.body.id}/pings/3`
);
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts/:hostId/pings', () => {
it('should show a ping by hostId', async () => {
const response = await request(app).get(
`/hosts/${createdHost.id}/pings`
);
expect(response.statusCode).toBe(200);
});
});
});
describe('Tag Endpoints', () => {
describe('GET /tags', () => {
it('should show tags', async () => {
const response = await request(app).get(`/tags`);
expect(response.statusCode).toBe(200);
});
});
describe('GET /tags/:tagName/hosts', () => {
it('should show hosts by tagName', async () => {
const response = await request(app).get(
`/tags/${createdHost.id}/hosts`
);
expect(response.statusCode).toBe(200);
});
});
});
describe('Ping Endpoints', () => {
describe('GET /pings', () => {
it('should show pings', async () => {
const response = await request(app).get(`/pings`);
expect(response.statusCode).toBe(200);
});
});
});
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('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-user/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",
"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-user/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",
"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-user/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-user/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-user/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-user/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);
}