Autenticação
Arquivos
invest-app-auth
├── 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
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
└── routes.js
Arquivos
invest-app-auth
├── 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
├── models
│ ├── Category.js
│ ├── Investment.js
│ └── User.js
└── routes.js
JSON Web Token (JWT)
Fluxo de Requisição:
$ npm install jsonwebtoken
$ npm install jsonwebtoken
Encode: jwt.sign(payload , privateKey, options)
Encode JWT
jwt.sign(
{ userId: 1 },
'secret'
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30
//=> Signature: C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728746643}
Encode JWT
jwt.sign(
{ userId: 1 },
'secret'
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30
//=> Signature: C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728746643}
Encode JWT
jwt.sign(
{ userId: 1 },
'secret',
{ expiresIn: 3600000 } // 1h
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ.V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ
//=> Signature: V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728746786,"exp":1728750386}
Encode JWT
jwt.sign(
{ userId: 1 },
'secret',
{ expiresIn: 3600000 } // 1h
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ.V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ
//=> Signature: V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728746786,"exp":1728750386}
Encode JWT
jwt.sign(
{ userId: 1 },
'abc',
{ expiresIn: '1h' }
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0ODQ5NywiZXhwIjoxNzI4NzUyMDk3fQ.0m_bFbC337WRu0bqlabUJ1hN-hNwOXVJUHKBbSdmz7s
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0ODQ5NywiZXhwIjoxNzI4NzUyMDk3fQ
//=> Signature: 0m_bFbC337WRu0bqlabUJ1hN-hNwOXVJUHKBbSdmz7s
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728748497,"exp":1728752097}
Encode JWT
jwt.sign(
{ userId: 1 },
'abc',
{ expiresIn: '1h' }
);
//=> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0ODQ5NywiZXhwIjoxNzI4NzUyMDk3fQ.0m_bFbC337WRu0bqlabUJ1hN-hNwOXVJUHKBbSdmz7s
//=> Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
//=> Payload: eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0ODQ5NywiZXhwIjoxNzI4NzUyMDk3fQ
//=> Signature: 0m_bFbC337WRu0bqlabUJ1hN-hNwOXVJUHKBbSdmz7s
// base64 decoded
//=> Header: {"alg":"HS256","typ":"JWT"}
//=> Payload: {"userId":1,"iat":1728748497,"exp":1728752097}
Observações:
iat
(Issued At
ouGerado em
) é o timestamp de quando o token foi gerado;- A
signature
é gerado pela assinatura doheader.payload
+privateKey
(HMAC-SHA256 Hash Generator); - expiresIn: expressed in seconds or a string describing a time span vercel/ms.
Decode: jwt.verify(token, privateKey)
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk',
'secret'
);
//=> { userId: 1, iat: 1728746643 }
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk',
'secret'
);
//=> { userId: 1, iat: 1728746643 }
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk',
'wrong'
);
//=> Uncaught JsonWebTokenError: invalid signature
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0NjY0M30.C1dDgnIRfWpJnqir_hyuVp_fnt7vpR6mKgHMdZoG4Rk',
'wrong'
);
//=> Uncaught JsonWebTokenError: invalid signature
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ.V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw',
'secret'
);
//=> { userId: 1, iat: 1728746786, exp: 1728750386 }
Decode JWT
jwt.verify(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTcyODc0Njc4NiwiZXhwIjoxNzI4NzUwMzg2fQ.V1eHtVYWaI5Rji8wd4onYIGqdTGlm6NAmUeIiw6G7Gw',
'secret'
);
//=> { userId: 1, iat: 1728746786, exp: 1728750386 }
Sabendo que o
privateKey
ésecret
, gere um novo token para o usuário deid
igual a2
com1h
de expiração.
Middleware
Express -> Middleware -> Router
Express -> Middleware -> Router
/codes/expressjs/invest-app-auth/src/middleware/auth.js
import jwt from 'jsonwebtoken';
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.' });
}
}
export { isAuthenticated };
/codes/expressjs/invest-app-auth/src/middleware/auth.js
import jwt from 'jsonwebtoken';
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.' });
}
}
export { isAuthenticated };
/codes/expressjs/invest-app-auth/.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"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
/codes/expressjs/invest-app-auth/.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"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
$ cp .env.example .env
$ cp .env.example .env
Router
/codes/expressjs/invest-app-auth/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { isAuthenticated } from './middleware/auth.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, 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, 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, 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, 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, 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, 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', 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) {
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', 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-auth/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { isAuthenticated } from './middleware/auth.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, 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, 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, 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, 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, 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, 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', 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) {
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', 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-auth/requests.http
@host = http://localhost:3000/api
@createdInvestmentId = {{createdInvestment.response.body.$.id}}
@token = {{signin.response.body.$.token}}
### Create User
# @name createdUser
POST {{host}}/users
Content-Type: application/json
{
"name": "Luiz",
"email": "luiz@email.com",
"password": "12345678"
}
### Signin 401
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "321"
}
### Signin Ok
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "12345678"
}
### Read User (whitout token)
GET {{host}}/users/me
### Read User (with token)
GET {{host}}/users/me
Authorization: bearer {{token}}
### Read Categories
GET {{host}}/categories
Authorization: bearer {{token}}
### Read Category Pós
# @name categoryPos
GET {{host}}/categories?name=Pós
Authorization: bearer {{token}}
### Read Category IPCA
# @name categoryIpca
GET {{host}}/categories?name=IPCA
Authorization: bearer {{token}}
### Create Investment
@categoryIpcaId = {{categoryIpca.response.body.$.id}}
# @name createdInvestment
POST {{host}}/investments
Authorization: bearer {{token}}
Content-Type: application/json
{
"name": "Tesouro Selic 2029",
"value": 10000,
"interest": "100% Selic",
"createdAt": "2023-09-01",
"broker": "Banco Inter",
"categoryId": "{{categoryIpcaId}}"
}
### Read Investments
GET {{host}}/investments
Authorization: bearer {{token}}
### Read Investments by Name
GET {{host}}/investments?name=Tesouro
Authorization: bearer {{token}}
### Read Investments by Id
GET {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
### Update Investment
@categoryPosId = {{categoryPos.response.body.$.id}}
PUT {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
Content-Type: application/json
{
"name": "Tesouro Selic 2029",
"value": 20000,
"interest": "100% Selic",
"createdAt": "2023-09-01",
"broker": "Banco Inter",
"categoryId": "{{categoryPosId}}"
}
### Delete Investment
DELETE {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
/codes/expressjs/invest-app-auth/requests.http
@host = http://localhost:3000/api
@createdInvestmentId = {{createdInvestment.response.body.$.id}}
@token = {{signin.response.body.$.token}}
### Create User
# @name createdUser
POST {{host}}/users
Content-Type: application/json
{
"name": "Luiz",
"email": "luiz@email.com",
"password": "12345678"
}
### Signin 401
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "321"
}
### Signin Ok
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "12345678"
}
### Read User (whitout token)
GET {{host}}/users/me
### Read User (with token)
GET {{host}}/users/me
Authorization: bearer {{token}}
### Read Categories
GET {{host}}/categories
Authorization: bearer {{token}}
### Read Category Pós
# @name categoryPos
GET {{host}}/categories?name=Pós
Authorization: bearer {{token}}
### Read Category IPCA
# @name categoryIpca
GET {{host}}/categories?name=IPCA
Authorization: bearer {{token}}
### Create Investment
@categoryIpcaId = {{categoryIpca.response.body.$.id}}
# @name createdInvestment
POST {{host}}/investments
Authorization: bearer {{token}}
Content-Type: application/json
{
"name": "Tesouro Selic 2029",
"value": 10000,
"interest": "100% Selic",
"createdAt": "2023-09-01",
"broker": "Banco Inter",
"categoryId": "{{categoryIpcaId}}"
}
### Read Investments
GET {{host}}/investments
Authorization: bearer {{token}}
### Read Investments by Name
GET {{host}}/investments?name=Tesouro
Authorization: bearer {{token}}
### Read Investments by Id
GET {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
### Update Investment
@categoryPosId = {{categoryPos.response.body.$.id}}
PUT {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
Content-Type: application/json
{
"name": "Tesouro Selic 2029",
"value": 20000,
"interest": "100% Selic",
"createdAt": "2023-09-01",
"broker": "Banco Inter",
"categoryId": "{{categoryPosId}}"
}
### Delete Investment
DELETE {{host}}/investments/{{createdInvestmentId}}
Authorization: bearer {{token}}
View
Login:
/codes/expressjs/invest-app-auth/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 pb-5">
<h1 class="text-center my-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>
<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-auth/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 pb-5">
<h1 class="text-center my-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>
<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-auth/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();
const user = Object.fromEntries(new FormData(form));
const { auth, token } = await API.create('/signin', user, false);
if (auth) {
Auth.signin(token);
} else {
console.log('Error no login');
}
}
/codes/expressjs/invest-app-auth/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();
const user = Object.fromEntries(new FormData(form));
const { auth, token } = await API.create('/signin', user, false);
if (auth) {
Auth.signin(token);
} else {
console.log('Error no login');
}
}
/codes/expressjs/invest-app-auth/public/js/services/api.js
import Auth from '../lib/auth.js';
const domain = '/api';
async function create(resource, data, auth = true) {
const url = `${domain}${resource}`;
const config = {
method: 'POST',
mode: 'cors',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
};
if (auth) {
config.headers.Authorization = `Bearer ${Auth.getToken()}`;
}
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function read(resource) {
const url = `${domain}${resource}`;
const config = {
method: 'get',
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function update(resource, data) {
const url = `${domain}${resource}`;
const config = {
method: 'PUT',
mode: 'cors',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function remove(resource) {
const url = `${domain}${resource}`;
const config = {
method: 'DELETE',
mode: 'cors',
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return true;
}
export default { create, read, update, remove };
/codes/expressjs/invest-app-auth/public/js/services/api.js
import Auth from '../lib/auth.js';
const domain = '/api';
async function create(resource, data, auth = true) {
const url = `${domain}${resource}`;
const config = {
method: 'POST',
mode: 'cors',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
};
if (auth) {
config.headers.Authorization = `Bearer ${Auth.getToken()}`;
}
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function read(resource) {
const url = `${domain}${resource}`;
const config = {
method: 'get',
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function update(resource, data) {
const url = `${domain}${resource}`;
const config = {
method: 'PUT',
mode: 'cors',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return await res.json();
}
async function remove(resource) {
const url = `${domain}${resource}`;
const config = {
method: 'DELETE',
mode: 'cors',
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
const res = await fetch(url, config);
if (res.status === 401) {
Auth.signout();
}
return true;
}
export default { create, read, update, remove };
/codes/expressjs/invest-app-auth/public/js/lib/auth.js
function isAuthenticated() {
if (!getToken()) {
window.location.href = '/signin.html';
} else {
return true;
}
}
function getToken() {
return localStorage.getItem('@invest-app:token');
}
function signin(token) {
localStorage.setItem('@invest-app:token', token);
window.location.href = '/home.html';
}
function signout() {
localStorage.removeItem('@invest-app:token');
window.location.href = '/signin.html';
}
export default { isAuthenticated, getToken, signin, signout };
/codes/expressjs/invest-app-auth/public/js/lib/auth.js
function isAuthenticated() {
if (!getToken()) {
window.location.href = '/signin.html';
} else {
return true;
}
}
function getToken() {
return localStorage.getItem('@invest-app:token');
}
function signin(token) {
localStorage.setItem('@invest-app:token', token);
window.location.href = '/home.html';
}
function signout() {
localStorage.removeItem('@invest-app:token');
window.location.href = '/signin.html';
}
export default { isAuthenticated, getToken, signin, signout };
Home:
/codes/expressjs/invest-app-auth/public/home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Invest App</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"
/>
<link rel="stylesheet" href="css/style.css" />
<script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
</head>
<body class="pb-5">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<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="#"
>Investimento</a
>
</li>
</ul>
<div class="dropdown">
<div
class="d-flex justify-content-center align-items-center rounded-circle bg-dark ms-2 text-white"
style="width: 1.5rem; height: 1.5rem"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="iconify" data-icon="mdi:account"></span>
</div>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<h5 id="user-name" class="dropdown-header"></h5>
</li>
<li>
<a class="dropdown-item" href="#" onclick="signout()">Sair</a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container pb-5">
<h1 class="text-center my-5">Investimentos</h1>
<div class="investments">
<p class="text-center">Nenhum investimento cadastrado.</p>
<div
id="investment-grid"
class="row row-cols-1 row-cols-md-3 g-4"
></div>
</div>
</div>
<div>
<button
class="btn btn-secondary create-investment"
style="position: fixed; bottom: 24px; right: 24px"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvasRight"
aria-controls="offcanvasRight"
>
+
</button>
</div>
<div
class="offcanvas offcanvas-end"
tabindex="-1"
id="offcanvasRight"
aria-labelledby="offcanvasRightLabel"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasRightLabel">
Cadastro de Investimento
</h5>
<button
type="button"
id="offcanvas-close"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<form>
<div class="mb-3">
<label for="name" class="form-label">Nome</label>
<input
type="name"
class="form-control"
id="name"
name="name"
placeholder="Ex: Tesouro Selic"
/>
</div>
<div class="mb-3">
<label for="value" class="form-label">Valor</label>
<input
type="number"
class="form-control"
id="value"
name="value"
step="0.01"
placeholder="100"
/>
</div>
<div class="mb-3">
<label for="interest" class="form-label">Taxa</label>
<input
type="text"
class="form-control"
id="interest"
name="interest"
placeholder="100% Selic"
/>
</div>
<div class="mb-3">
<label for="categoryId" class="form-label">Categoria</label>
<select
class="form-control"
id="categoryId"
name="categoryId"
></select>
</div>
<div class="mb-3">
<label for="broker" class="form-label">Corretora</label>
<input
type="text"
class="form-control"
id="broker"
name="broker"
placeholder="Corretora"
/>
</div>
<div class="mb-3">
<label for="createdAt" class="form-label">Data</label>
<input
type="date"
class="form-control"
id="createdAt"
name="createdAt"
/>
</div>
<div class="mb-3">
<input
type="submit"
class="form-control btn btn-secondary"
id="submit"
placeholder="100"
/>
</div>
</form>
</div>
</div>
<div class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Remover Investimento</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<p>Deseja remover o investimento?</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Fechar
</button>
<button type="button" class="btn btn-primary">Confirmar</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/home.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-auth/public/home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Invest App</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"
/>
<link rel="stylesheet" href="css/style.css" />
<script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
</head>
<body class="pb-5">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<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="#"
>Investimento</a
>
</li>
</ul>
<div class="dropdown">
<div
class="d-flex justify-content-center align-items-center rounded-circle bg-dark ms-2 text-white"
style="width: 1.5rem; height: 1.5rem"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="iconify" data-icon="mdi:account"></span>
</div>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<h5 id="user-name" class="dropdown-header"></h5>
</li>
<li>
<a class="dropdown-item" href="#" onclick="signout()">Sair</a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container pb-5">
<h1 class="text-center my-5">Investimentos</h1>
<div class="investments">
<p class="text-center">Nenhum investimento cadastrado.</p>
<div
id="investment-grid"
class="row row-cols-1 row-cols-md-3 g-4"
></div>
</div>
</div>
<div>
<button
class="btn btn-secondary create-investment"
style="position: fixed; bottom: 24px; right: 24px"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvasRight"
aria-controls="offcanvasRight"
>
+
</button>
</div>
<div
class="offcanvas offcanvas-end"
tabindex="-1"
id="offcanvasRight"
aria-labelledby="offcanvasRightLabel"
>
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasRightLabel">
Cadastro de Investimento
</h5>
<button
type="button"
id="offcanvas-close"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<form>
<div class="mb-3">
<label for="name" class="form-label">Nome</label>
<input
type="name"
class="form-control"
id="name"
name="name"
placeholder="Ex: Tesouro Selic"
/>
</div>
<div class="mb-3">
<label for="value" class="form-label">Valor</label>
<input
type="number"
class="form-control"
id="value"
name="value"
step="0.01"
placeholder="100"
/>
</div>
<div class="mb-3">
<label for="interest" class="form-label">Taxa</label>
<input
type="text"
class="form-control"
id="interest"
name="interest"
placeholder="100% Selic"
/>
</div>
<div class="mb-3">
<label for="categoryId" class="form-label">Categoria</label>
<select
class="form-control"
id="categoryId"
name="categoryId"
></select>
</div>
<div class="mb-3">
<label for="broker" class="form-label">Corretora</label>
<input
type="text"
class="form-control"
id="broker"
name="broker"
placeholder="Corretora"
/>
</div>
<div class="mb-3">
<label for="createdAt" class="form-label">Data</label>
<input
type="date"
class="form-control"
id="createdAt"
name="createdAt"
/>
</div>
<div class="mb-3">
<input
type="submit"
class="form-control btn btn-secondary"
id="submit"
placeholder="100"
/>
</div>
</form>
</div>
</div>
<div class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Remover Investimento</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<p>Deseja remover o investimento?</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Fechar
</button>
<button type="button" class="btn btn-primary">Confirmar</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/home.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-auth/public/js/home.js
import API from './services/api.js';
import Auth from './lib/auth.js';
import { formatCurrency, formatDate } from './lib/format.js';
let removedHostId;
const form = document.querySelector('form');
const bsOffcanvas = new bootstrap.Offcanvas('.offcanvas');
const confirmModal = new bootstrap.Modal('.modal');
function InvestmentCard(investment) {
return `<div class="col" id="investment-${investment.id}">
<div class="card">
<div class="card-header">
<span class="investment-name">
${investment.name}
</span>
<span class="float-end">
<span class="icon-trash" >
<span
class="iconify"
data-icon="solar:trash-bin-minimalistic-broken"
>
</span>
</span>
<span class="icon-pencil">
<span
class="iconify"
data-icon="tabler:pencil"
>
</span>
</span>
</span>
</div>
<div class="card-body">
<div>
<span class="fw-bold">Valor:</span>
<span class="investment-value">
${formatCurrency(investment.value / 100)}
</span>
</div>
<div>
<span class="fw-bold">Taxa:</span>
<span class="investment-interest">
${investment.interest}
</span>
</div>
<div>
<span class="fw-bold">Data:</span>
<span class="investment-created-at">
${formatDate(investment.createdAt)}
</span>
</div>
<div>
<span class="fw-bold">Corretora:</span>
<span class="investment-broker">
${investment.broker.name}
</span>
</div>
<div>
<span class="fw-bold">Categoria:</span>
<span
class="badge investment-category"
style="background-color: ${investment.category.color}"
>
${investment.category.name}
</span>
</div>
</div>
</div>
</div>`;
}
function createInvestmentCard(investment) {
document.querySelector('.investments p').style.display = 'none';
const investmentContainer = document.querySelector(`#investment-grid`);
investmentContainer.insertAdjacentHTML(
'beforeend',
InvestmentCard(investment)
);
loadHandleConfirmModal(investment.id);
loadHandleUpdateInvestment(investment.id);
}
async function loadInvestmentCards() {
const investments = await API.read('/investments');
for (const investment of investments) {
createInvestmentCard(investment);
}
}
function updateInvestmentCard({
id,
name,
value,
createdAt,
category,
broker,
interest,
}) {
document.querySelector(`#investment-${id} .investment-name`).innerText = name;
document.querySelector(`#investment-${id} .investment-value`).innerText =
formatCurrency(value / 100);
document.querySelector(`#investment-${id} .investment-interest`).innerText =
interest;
document.querySelector(`#investment-${id} .investment-broker`).innerText =
broker.name;
document.querySelector(`#investment-${id} .investment-created-at`).innerText =
createdAt;
document.querySelector(
`#investment-${id} .investment-category`
).style.backgroundColor = category.color;
document.querySelector(`#investment-${id} .investment-category`).innerText =
category.name;
}
function loadHandleFormSubmit(type, id) {
form.onsubmit = async (event) => {
event.preventDefault();
const investment = Object.fromEntries(new FormData(form));
investment.value = Number(investment.value) * 100;
if (type === 'create') {
const createdInvestment = await API.create('/investments', investment);
createInvestmentCard(createdInvestment);
} else if (type === 'update') {
const updatedInvestment = await API.update(
`/investments/${id}`,
investment
);
updateInvestmentCard(updatedInvestment);
}
form.reset();
document.querySelector('#offcanvas-close').click();
};
}
function loadHandleCreateInvestment() {
const button = document.querySelector('.btn.create-investment');
button.onclick = () => {
form.reset();
bsOffcanvas.show();
loadHandleFormSubmit('create');
};
}
function loadHandleUpdateInvestment(id) {
const iconPencil = document.querySelector(`#investment-${id} .icon-pencil`);
iconPencil.onclick = async () => {
const investment = await API.read(`/investments/${id}`);
const { name, value, interest, createdAt, categoryId, broker } = investment;
document.querySelector('form #name').value = name;
document.querySelector('form #value').value = value / 100;
document.querySelector('form #interest').value = interest;
document.querySelector('form #categoryId').value = categoryId;
document.querySelector('form #createdAt').value = formatDate(
createdAt,
'ymd'
);
document.querySelector('form #broker').value = broker.name;
bsOffcanvas.show();
loadHandleFormSubmit('update', id);
};
}
function loadHandleConfirmModal(id) {
const iconTrash = document.querySelector(`#investment-${id} .icon-trash`);
iconTrash.onclick = () => {
removedHostId = id;
confirmModal.show();
};
}
function loadHandleRemoveInvestment() {
const confirmBtn = document.querySelector('.modal .btn-primary');
confirmBtn.onclick = () => {
API.remove(`/investments/${removedHostId}`);
document.querySelector(`#investment-${removedHostId}`).remove();
confirmModal.hide();
};
}
async function loadCategoriesSelect() {
const select = document.querySelector('#categoryId');
const categories = await API.read('/categories');
for (const category of categories) {
const option = `<option value="${category.id}">${category.name}</option>`;
select.insertAdjacentHTML('beforeend', option);
}
}
async function loadUser() {
const user = await API.read('/users/me');
const userName = document.querySelector('#user-name');
userName.innerText = user.name;
}
window.signout = Auth.signout;
if (Auth.isAuthenticated()) {
loadInvestmentCards();
loadHandleCreateInvestment();
loadCategoriesSelect();
loadHandleRemoveInvestment();
loadUser();
}
/codes/expressjs/invest-app-auth/public/js/home.js
import API from './services/api.js';
import Auth from './lib/auth.js';
import { formatCurrency, formatDate } from './lib/format.js';
let removedHostId;
const form = document.querySelector('form');
const bsOffcanvas = new bootstrap.Offcanvas('.offcanvas');
const confirmModal = new bootstrap.Modal('.modal');
function InvestmentCard(investment) {
return `<div class="col" id="investment-${investment.id}">
<div class="card">
<div class="card-header">
<span class="investment-name">
${investment.name}
</span>
<span class="float-end">
<span class="icon-trash" >
<span
class="iconify"
data-icon="solar:trash-bin-minimalistic-broken"
>
</span>
</span>
<span class="icon-pencil">
<span
class="iconify"
data-icon="tabler:pencil"
>
</span>
</span>
</span>
</div>
<div class="card-body">
<div>
<span class="fw-bold">Valor:</span>
<span class="investment-value">
${formatCurrency(investment.value / 100)}
</span>
</div>
<div>
<span class="fw-bold">Taxa:</span>
<span class="investment-interest">
${investment.interest}
</span>
</div>
<div>
<span class="fw-bold">Data:</span>
<span class="investment-created-at">
${formatDate(investment.createdAt)}
</span>
</div>
<div>
<span class="fw-bold">Corretora:</span>
<span class="investment-broker">
${investment.broker.name}
</span>
</div>
<div>
<span class="fw-bold">Categoria:</span>
<span
class="badge investment-category"
style="background-color: ${investment.category.color}"
>
${investment.category.name}
</span>
</div>
</div>
</div>
</div>`;
}
function createInvestmentCard(investment) {
document.querySelector('.investments p').style.display = 'none';
const investmentContainer = document.querySelector(`#investment-grid`);
investmentContainer.insertAdjacentHTML(
'beforeend',
InvestmentCard(investment)
);
loadHandleConfirmModal(investment.id);
loadHandleUpdateInvestment(investment.id);
}
async function loadInvestmentCards() {
const investments = await API.read('/investments');
for (const investment of investments) {
createInvestmentCard(investment);
}
}
function updateInvestmentCard({
id,
name,
value,
createdAt,
category,
broker,
interest,
}) {
document.querySelector(`#investment-${id} .investment-name`).innerText = name;
document.querySelector(`#investment-${id} .investment-value`).innerText =
formatCurrency(value / 100);
document.querySelector(`#investment-${id} .investment-interest`).innerText =
interest;
document.querySelector(`#investment-${id} .investment-broker`).innerText =
broker.name;
document.querySelector(`#investment-${id} .investment-created-at`).innerText =
createdAt;
document.querySelector(
`#investment-${id} .investment-category`
).style.backgroundColor = category.color;
document.querySelector(`#investment-${id} .investment-category`).innerText =
category.name;
}
function loadHandleFormSubmit(type, id) {
form.onsubmit = async (event) => {
event.preventDefault();
const investment = Object.fromEntries(new FormData(form));
investment.value = Number(investment.value) * 100;
if (type === 'create') {
const createdInvestment = await API.create('/investments', investment);
createInvestmentCard(createdInvestment);
} else if (type === 'update') {
const updatedInvestment = await API.update(
`/investments/${id}`,
investment
);
updateInvestmentCard(updatedInvestment);
}
form.reset();
document.querySelector('#offcanvas-close').click();
};
}
function loadHandleCreateInvestment() {
const button = document.querySelector('.btn.create-investment');
button.onclick = () => {
form.reset();
bsOffcanvas.show();
loadHandleFormSubmit('create');
};
}
function loadHandleUpdateInvestment(id) {
const iconPencil = document.querySelector(`#investment-${id} .icon-pencil`);
iconPencil.onclick = async () => {
const investment = await API.read(`/investments/${id}`);
const { name, value, interest, createdAt, categoryId, broker } = investment;
document.querySelector('form #name').value = name;
document.querySelector('form #value').value = value / 100;
document.querySelector('form #interest').value = interest;
document.querySelector('form #categoryId').value = categoryId;
document.querySelector('form #createdAt').value = formatDate(
createdAt,
'ymd'
);
document.querySelector('form #broker').value = broker.name;
bsOffcanvas.show();
loadHandleFormSubmit('update', id);
};
}
function loadHandleConfirmModal(id) {
const iconTrash = document.querySelector(`#investment-${id} .icon-trash`);
iconTrash.onclick = () => {
removedHostId = id;
confirmModal.show();
};
}
function loadHandleRemoveInvestment() {
const confirmBtn = document.querySelector('.modal .btn-primary');
confirmBtn.onclick = () => {
API.remove(`/investments/${removedHostId}`);
document.querySelector(`#investment-${removedHostId}`).remove();
confirmModal.hide();
};
}
async function loadCategoriesSelect() {
const select = document.querySelector('#categoryId');
const categories = await API.read('/categories');
for (const category of categories) {
const option = `<option value="${category.id}">${category.name}</option>`;
select.insertAdjacentHTML('beforeend', option);
}
}
async function loadUser() {
const user = await API.read('/users/me');
const userName = document.querySelector('#user-name');
userName.innerText = user.name;
}
window.signout = Auth.signout;
if (Auth.isAuthenticated()) {
loadInvestmentCards();
loadHandleCreateInvestment();
loadCategoriesSelect();
loadHandleRemoveInvestment();
loadUser();
}
/codes/expressjs/invest-app-auth/public/js/lib/format.js
export function formatCurrency(value) {
return Number(value).toLocaleString('pt-br', {
style: 'currency',
currency: 'BRL',
});
}
export function formatDate(value, pattern) {
const date = new Date(value).toLocaleDateString('pt-br');
if (pattern === 'ymd') {
return date.split('/').reverse().join('-');
} else {
return date;
}
}
/codes/expressjs/invest-app-auth/public/js/lib/format.js
export function formatCurrency(value) {
return Number(value).toLocaleString('pt-br', {
style: 'currency',
currency: 'BRL',
});
}
export function formatDate(value, pattern) {
const date = new Date(value).toLocaleDateString('pt-br');
if (pattern === 'ymd') {
return date.split('/').reverse().join('-');
} else {
return date;
}
}