Validação
Database
Opções de validação:
- NOT NULL
"name" TEXT NOT NULL
- UNIQUE
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
- DEFAULT
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CHECK
"password" TEXT NOT NULL CHECK(LENGTH(password) >= 8)
$ sqlite3 prisma/dev.db .dump
$ sqlite3 prisma/dev.db .dump
Schema do Banco
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "Category" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL
);
INSERT INTO Category VALUES('9be47002-2376-4975-acc9-d0c0b09fb446','Pós','#6366f1');
INSERT INTO Category VALUES('5d6a114c-6bb1-4be8-9962-8c77fa411f99','Pré','#d946ef');
INSERT INTO Category VALUES('f950a0ab-7caf-441c-9386-2082c78172e5','IPCA','#f43f5e');
INSERT INTO Category VALUES('7da57d6b-aca5-4766-b323-c3bf32c4ec28','Renda Variável','#eab308');
INSERT INTO Category VALUES('6fcdde52-1eb4-414e-8485-be2a6ce70176','Fundo de Investimento','#46efb1');
INSERT INTO Category VALUES('1d80abe9-7a31-4b90-b10b-2ebdc7fdcff0','Outros','#111111');
CREATE TABLE IF NOT EXISTS "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL
);
INSERT INTO User VALUES('b37dd024-8d74-4c99-859f-d94f19e51b6c','Luiz','luiz@email.com','$2b$10$Zn0pLtAUqnYPn3ggCvExoubRQ7XhVa7PcjF7v9AIvvPs5/yDi4EfG');
CREATE TABLE IF NOT EXISTS "Broker" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL
);
INSERT INTO Broker VALUES('1e6f7c70-6dea-4f51-8f19-230262b666f1','Banco Inter');
CREATE TABLE IF NOT EXISTS "Investment" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"value" INTEGER NOT NULL,
"interest" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"categoryId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"brokerId" TEXT NOT NULL,
CONSTRAINT "Investment_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Investment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Investment_brokerId_fkey" FOREIGN KEY ("brokerId") REFERENCES "Broker" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO Investment VALUES('cf2b1c62-a716-41b0-a4e7-afb5aa8a1af7','Tesouro Selic 2029',20000,'100% Selic',1693537200000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
INSERT INTO Investment VALUES('52ab29d5-0392-4b25-85a3-78faa0ef31ca','CDB Inter',15000,'100% CDI',1693710000000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
CREATE UNIQUE INDEX "Category_color_key" ON "Category"("color");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE UNIQUE INDEX "Broker_name_key" ON "Broker"("name");
COMMIT;
Schema do Banco
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "Category" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL
);
INSERT INTO Category VALUES('9be47002-2376-4975-acc9-d0c0b09fb446','Pós','#6366f1');
INSERT INTO Category VALUES('5d6a114c-6bb1-4be8-9962-8c77fa411f99','Pré','#d946ef');
INSERT INTO Category VALUES('f950a0ab-7caf-441c-9386-2082c78172e5','IPCA','#f43f5e');
INSERT INTO Category VALUES('7da57d6b-aca5-4766-b323-c3bf32c4ec28','Renda Variável','#eab308');
INSERT INTO Category VALUES('6fcdde52-1eb4-414e-8485-be2a6ce70176','Fundo de Investimento','#46efb1');
INSERT INTO Category VALUES('1d80abe9-7a31-4b90-b10b-2ebdc7fdcff0','Outros','#111111');
CREATE TABLE IF NOT EXISTS "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL
);
INSERT INTO User VALUES('b37dd024-8d74-4c99-859f-d94f19e51b6c','Luiz','luiz@email.com','$2b$10$Zn0pLtAUqnYPn3ggCvExoubRQ7XhVa7PcjF7v9AIvvPs5/yDi4EfG');
CREATE TABLE IF NOT EXISTS "Broker" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL
);
INSERT INTO Broker VALUES('1e6f7c70-6dea-4f51-8f19-230262b666f1','Banco Inter');
CREATE TABLE IF NOT EXISTS "Investment" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"value" INTEGER NOT NULL,
"interest" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"categoryId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"brokerId" TEXT NOT NULL,
CONSTRAINT "Investment_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Investment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Investment_brokerId_fkey" FOREIGN KEY ("brokerId") REFERENCES "Broker" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO Investment VALUES('cf2b1c62-a716-41b0-a4e7-afb5aa8a1af7','Tesouro Selic 2029',20000,'100% Selic',1693537200000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
INSERT INTO Investment VALUES('52ab29d5-0392-4b25-85a3-78faa0ef31ca','CDB Inter',15000,'100% CDI',1693710000000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
CREATE UNIQUE INDEX "Category_color_key" ON "Category"("color");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE UNIQUE INDEX "Broker_name_key" ON "Broker"("name");
COMMIT;
Back-end
Arquivos
invest-app-validation
├── package-lock.json
├── package.json
├── prisma
│ ├── dev.db
│ ├── migrations
│ │ ├── 20230826214323_init
│ │ │ └── migration.sql
│ │ ├── 20230902014613_create_category
│ │ │ └── migration.sql
│ │ ├── 20230902065351_create_user
│ │ │ └── migration.sql
│ │ ├── 20230902111235_create_investment_broker_interest_created_at
│ │ │ └── migration.sql
│ │ ├── 20230902131930_create_investment_user_cascade
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
└── routes.js
Arquivos
invest-app-validation
├── package-lock.json
├── package.json
├── prisma
│ ├── dev.db
│ ├── migrations
│ │ ├── 20230826214323_init
│ │ │ └── migration.sql
│ │ ├── 20230902014613_create_category
│ │ │ └── migration.sql
│ │ ├── 20230902065351_create_user
│ │ │ └── migration.sql
│ │ ├── 20230902111235_create_investment_broker_interest_created_at
│ │ │ └── migration.sql
│ │ ├── 20230902131930_create_investment_user_cascade
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
└── routes.js
$ npm install zod
$ npm install zod
/codes/expressjs/invest-app-validation/src/middleware/validate.js
export function validate(schema) {
return function (req, res, next) {
try {
schema.parse({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (err) {
return res.status(400).send(err.errors);
}
};
}
/codes/expressjs/invest-app-validation/src/middleware/validate.js
export function validate(schema) {
return function (req, res, next) {
try {
schema.parse({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (err) {
return res.status(400).send(err.errors);
}
};
}
/codes/expressjs/invest-app-validation/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
class HTTPError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post(
'/investments',
isAuthenticated,
validate(
z.object({
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const userId = req.userId;
const createdInvestment = await Investment.create({
...investment,
userId,
});
return res.json(createdInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to create investment', 400);
}
}
);
router.get(
'/investments',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
const userId = req.userId;
let investments;
if (name) {
investments = await Investment.read({ name, userId });
} else {
investments = await Investment.read({ userId });
}
return res.json(investments);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.get(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
const userId = req.userId;
const investment = await Investment.readById(id, { userId });
return res.json(investment);
} catch (error) {
throw new HTTPError('Unable to find investment', 400);
}
}
);
router.put(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const id = req.params.id;
const userId = req.userId;
const updatedInvestment = await Investment.update({
...investment,
id,
userId,
});
return res.json(updatedInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to update investment', 400);
}
}
);
router.delete(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
if (await Investment.remove(id)) {
return res.sendStatus(204);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to remove investment', 400);
}
}
);
router.get(
'/categories',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
let categories;
if (name) {
categories = await Category.read({ name });
} else {
categories = await Category.read();
}
return res.json(categories);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.post(
'/users',
validate(
z.object({
body: z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const user = req.body;
delete user.confirmationPassword;
const newUser = await User.create(user);
delete newUser.password;
res.status(201).json(newUser);
} catch (error) {
if (
error.message.includes(
'Unique constraint failed on the fields: (`email`)'
)
) {
throw new HTTPError('Email already exists', 400);
}
throw new HTTPError('Unable to create user', 400);
}
}
);
router.get('/users/me', isAuthenticated, async (req, res) => {
try {
const userId = req.userId;
const user = await User.readById(userId);
delete user.password;
return res.json(user);
} catch (error) {
throw new HTTPError('Unable to find user', 400);
}
});
router.post(
'/signin',
validate(
z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const { email, password } = req.body;
const { id: userId, password: hash } = await User.read({ email });
const match = await bcrypt.compare(password, hash);
if (match) {
const token = jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: 3600000 } // 1h
);
return res.json({ auth: true, token });
} else {
throw new Error('User not found');
}
} catch (error) {
res.status(401).json({ error: 'User not found' });
}
}
);
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((error, req, res, next) => {
// console.error(error.stack);
if (error instanceof HTTPError) {
return res.status(error.code).json({ message: error.message });
} else {
return res.status(500).json({ message: 'Something broke!' });
}
});
export default router;
/codes/expressjs/invest-app-validation/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
class HTTPError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post(
'/investments',
isAuthenticated,
validate(
z.object({
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const userId = req.userId;
const createdInvestment = await Investment.create({
...investment,
userId,
});
return res.json(createdInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to create investment', 400);
}
}
);
router.get(
'/investments',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
const userId = req.userId;
let investments;
if (name) {
investments = await Investment.read({ name, userId });
} else {
investments = await Investment.read({ userId });
}
return res.json(investments);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.get(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
const userId = req.userId;
const investment = await Investment.readById(id, { userId });
return res.json(investment);
} catch (error) {
throw new HTTPError('Unable to find investment', 400);
}
}
);
router.put(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
body: z.object({
name: z.string(),
value: z.number(),
interest: z.string(),
createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
broker: z.string(),
categoryId: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const investment = req.body;
if (investment.createdAt) {
investment.createdAt = new Date(
investment.createdAt + 'T00:00:00-03:00'
).toISOString();
}
const id = req.params.id;
const userId = req.userId;
const updatedInvestment = await Investment.update({
...investment,
id,
userId,
});
return res.json(updatedInvestment);
} catch (error) {
console.error(error.stack);
throw new HTTPError('Unable to update investment', 400);
}
}
);
router.delete(
'/investments/:id',
isAuthenticated,
validate(
z.object({
params: z.object({
id: z.string().uuid(),
}),
})
),
async (req, res) => {
try {
const id = req.params.id;
if (await Investment.remove(id)) {
return res.sendStatus(204);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to remove investment', 400);
}
}
);
router.get(
'/categories',
isAuthenticated,
validate(
z.object({
query: z.object({
name: z.string().optional(),
}),
})
),
async (req, res) => {
try {
const { name } = req.query;
let categories;
if (name) {
categories = await Category.read({ name });
} else {
categories = await Category.read();
}
return res.json(categories);
} catch (error) {
throw new HTTPError('Unable to read investments', 400);
}
}
);
router.post(
'/users',
validate(
z.object({
body: z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const user = req.body;
delete user.confirmationPassword;
const newUser = await User.create(user);
delete newUser.password;
res.status(201).json(newUser);
} catch (error) {
if (
error.message.includes(
'Unique constraint failed on the fields: (`email`)'
)
) {
throw new HTTPError('Email already exists', 400);
}
throw new HTTPError('Unable to create user', 400);
}
}
);
router.get('/users/me', isAuthenticated, async (req, res) => {
try {
const userId = req.userId;
const user = await User.readById(userId);
delete user.password;
return res.json(user);
} catch (error) {
throw new HTTPError('Unable to find user', 400);
}
});
router.post(
'/signin',
validate(
z.object({
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
})
),
async (req, res) => {
try {
const { email, password } = req.body;
const { id: userId, password: hash } = await User.read({ email });
const match = await bcrypt.compare(password, hash);
if (match) {
const token = jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: 3600000 } // 1h
);
return res.json({ auth: true, token });
} else {
throw new Error('User not found');
}
} catch (error) {
res.status(401).json({ error: 'User not found' });
}
}
);
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((error, req, res, next) => {
// console.error(error.stack);
if (error instanceof HTTPError) {
return res.status(error.code).json({ message: error.message });
} else {
return res.status(500).json({ message: 'Something broke!' });
}
});
export default router;
Front-end
/codes/expressjs/invest-app-validation/public/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>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<a class="navbar-brand" href="#">Invest 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>
<div class="container position-relative pb-5">
<h1 class="text-center py-5 fw-bold">Cadastro de Usuário</h1>
<div class="row justify-content-center">
<form
class="col-6 needs-validation"
onsubmit="handleSubmit(event)"
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 class="position-absolute top-0 end-0 p-3" style="z-index: 11">
<div
id="liveToast"
class="toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-delay="4000"
>
<div class="toast-header">
<strong class="me-auto"></strong>
<button
type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"
></button>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
crossorigin="anonymous"
></script>
<script src="js/signup.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-validation/public/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>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<a class="navbar-brand" href="#">Invest 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>
<div class="container position-relative pb-5">
<h1 class="text-center py-5 fw-bold">Cadastro de Usuário</h1>
<div class="row justify-content-center">
<form
class="col-6 needs-validation"
onsubmit="handleSubmit(event)"
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 class="position-absolute top-0 end-0 p-3" style="z-index: 11">
<div
id="liveToast"
class="toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-delay="4000"
>
<div class="toast-header">
<strong class="me-auto"></strong>
<button
type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"
></button>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
crossorigin="anonymous"
></script>
<script src="js/signup.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-validation/public/js/signup.js
import API from './services/api.js';
const form = document.querySelector('form');
window.handleSubmit = handleSubmit;
async function handleSubmit(event) {
event.preventDefault();
if (form.checkValidity()) {
const user = Object.fromEntries(new FormData(form));
const { email, message } = await API.create('/users', user, false);
if (email) {
location.href = '/signin.html';
} else if (message === 'Email already exists') {
const error = 'Email já cadastrado';
const emailError = document.querySelector('#email + .invalid-feedback');
emailError.textContent = error;
form.email.setCustomValidity(error);
form.email.classList.add('is-invalid');
} else {
showToast('Error no cadastro');
}
} else {
form.classList.add('was-validated');
}
}
form.email.oninput = () => {
form.email.classList.remove('is-invalid');
const confirmationPasswordError = document.querySelector(
'#email + .invalid-feedback'
);
confirmationPasswordError.textContent = 'Informe o email do usuário.';
};
form.confirmationPassword.oninput = () => {
const password = form.password.value;
const confirmationPassword = form.confirmationPassword.value;
if (password !== confirmationPassword) {
const error = 'As senhas não são iguais.';
const confirmationPasswordError = document.querySelector(
'#confirmationPassword + .invalid-feedback'
);
confirmationPasswordError.textContent = error;
form.confirmationPassword.setCustomValidity(error);
} else {
form.confirmationPassword.setCustomValidity('');
}
};
function showToast(message) {
document.querySelector('.toast-header strong').innerText = message;
const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
toast.show();
}
/codes/expressjs/invest-app-validation/public/js/signup.js
import API from './services/api.js';
const form = document.querySelector('form');
window.handleSubmit = handleSubmit;
async function handleSubmit(event) {
event.preventDefault();
if (form.checkValidity()) {
const user = Object.fromEntries(new FormData(form));
const { email, message } = await API.create('/users', user, false);
if (email) {
location.href = '/signin.html';
} else if (message === 'Email already exists') {
const error = 'Email já cadastrado';
const emailError = document.querySelector('#email + .invalid-feedback');
emailError.textContent = error;
form.email.setCustomValidity(error);
form.email.classList.add('is-invalid');
} else {
showToast('Error no cadastro');
}
} else {
form.classList.add('was-validated');
}
}
form.email.oninput = () => {
form.email.classList.remove('is-invalid');
const confirmationPasswordError = document.querySelector(
'#email + .invalid-feedback'
);
confirmationPasswordError.textContent = 'Informe o email do usuário.';
};
form.confirmationPassword.oninput = () => {
const password = form.password.value;
const confirmationPassword = form.confirmationPassword.value;
if (password !== confirmationPassword) {
const error = 'As senhas não são iguais.';
const confirmationPasswordError = document.querySelector(
'#confirmationPassword + .invalid-feedback'
);
confirmationPasswordError.textContent = error;
form.confirmationPassword.setCustomValidity(error);
} else {
form.confirmationPassword.setCustomValidity('');
}
};
function showToast(message) {
document.querySelector('.toast-header strong').innerText = message;
const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
toast.show();
}
/codes/expressjs/invest-app-validation/public/signin.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>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<a class="navbar-brand" href="#">Invest 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="/signin.html"
>Entrar</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup.html">Cadastrar</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container position-relative pb-5">
<h1 class="text-center py-5 fw-bold">Entrar</h1>
<div class="row justify-content-center">
<form
class="col-6 needs-validation"
onsubmit="handleSubmit(event)"
novalidate
>
<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">
<input type="submit" class="btn btn-primary" value="Entrar" />
</div>
</form>
</div>
<div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
<div
id="liveToast"
class="toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-delay="4000"
>
<div class="toast-header">
<strong class="me-auto"></strong>
<button
type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"
></button>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
crossorigin="anonymous"
></script>
<script src="js/signin.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-validation/public/signin.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>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
crossorigin="anonymous"
/>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<a class="navbar-brand" href="#">Invest 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="/signin.html"
>Entrar</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup.html">Cadastrar</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container position-relative pb-5">
<h1 class="text-center py-5 fw-bold">Entrar</h1>
<div class="row justify-content-center">
<form
class="col-6 needs-validation"
onsubmit="handleSubmit(event)"
novalidate
>
<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">
<input type="submit" class="btn btn-primary" value="Entrar" />
</div>
</form>
</div>
<div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
<div
id="liveToast"
class="toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-delay="4000"
>
<div class="toast-header">
<strong class="me-auto"></strong>
<button
type="button"
class="btn-close"
data-bs-dismiss="toast"
aria-label="Close"
></button>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
crossorigin="anonymous"
></script>
<script src="js/signin.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-validation/public/js/signin.js
import API from './services/api.js';
import Auth from './lib/auth.js';
const form = document.querySelector('form');
window.handleSubmit = handleSubmit;
async function handleSubmit(event) {
event.preventDefault();
if (form.checkValidity()) {
const user = Object.fromEntries(new FormData(form));
const { auth, token } = await API.create('/signin', user, false);
if (auth) {
Auth.signin(token);
} else {
showToast('Error no login');
}
} else {
form.classList.add('was-validated');
}
}
function showToast(message) {
document.querySelector('.toast-header strong').innerText = message;
const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
toast.show();
}
/codes/expressjs/invest-app-validation/public/js/signin.js
import API from './services/api.js';
import Auth from './lib/auth.js';
const form = document.querySelector('form');
window.handleSubmit = handleSubmit;
async function handleSubmit(event) {
event.preventDefault();
if (form.checkValidity()) {
const user = Object.fromEntries(new FormData(form));
const { auth, token } = await API.create('/signin', user, false);
if (auth) {
Auth.signin(token);
} else {
showToast('Error no login');
}
} else {
form.classList.add('was-validated');
}
}
function showToast(message) {
document.querySelector('.toast-header strong').innerText = message;
const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
toast.show();
}