Upload de Arquivo
Arquivos
invest-app-upload
├── 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
│ │ ├── 20230905183705_user_image
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── imgs
│ │ └── profile
│ │ ├── 9948180954ee1846a214c710ada7a2a5-avatar.png
│ │ └── avatar.png
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── profile.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── profile.html
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── config
│ ├── mail.js
│ └── multer.js
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Image.js
│ ├── Investment.js
│ └── User.js
├── routes.js
└── services
└── SendMail.js
Arquivos
invest-app-upload
├── 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
│ │ ├── 20230905183705_user_image
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.js
│ └── seeders.json
├── public
│ ├── css
│ │ └── style.css
│ ├── home.html
│ ├── imgs
│ │ └── profile
│ │ ├── 9948180954ee1846a214c710ada7a2a5-avatar.png
│ │ └── avatar.png
│ ├── js
│ │ ├── home.js
│ │ ├── lib
│ │ │ ├── auth.js
│ │ │ └── format.js
│ │ ├── profile.js
│ │ ├── services
│ │ │ └── api.js
│ │ ├── signin.js
│ │ └── signup.js
│ ├── profile.html
│ ├── signin.html
│ └── signup.html
├── requests.http
└── src
├── config
│ ├── mail.js
│ └── multer.js
├── database
│ └── database.js
├── index.js
├── middleware
│ ├── auth.js
│ └── validate.js
├── models
│ ├── Category.js
│ ├── Image.js
│ ├── Investment.js
│ └── User.js
├── routes.js
└── services
└── SendMail.js
Back-end
/codes/expressjs/invest-app-upload/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"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Investment {
id String @id @default(uuid())
name String
value Int
interest String
createdAt DateTime @default(now())
category Category @relation(fields: [categoryId], references: [id])
categoryId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
broker Broker @relation(fields: [brokerId], references: [id])
brokerId String
}
model Category {
id String @id @default(uuid())
name String @unique
color String @unique
investments Investment[]
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
investments Investment[]
image Image?
}
model Image {
id String @id @default(uuid())
path String
user User @relation(fields: [userId], references: [id])
userId String @unique
}
model Broker {
id String @id @default(uuid())
name String @unique
investments Investment[]
}
/codes/expressjs/invest-app-upload/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"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Investment {
id String @id @default(uuid())
name String
value Int
interest String
createdAt DateTime @default(now())
category Category @relation(fields: [categoryId], references: [id])
categoryId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
broker Broker @relation(fields: [brokerId], references: [id])
brokerId String
}
model Category {
id String @id @default(uuid())
name String @unique
color String @unique
investments Investment[]
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
investments Investment[]
image Image?
}
model Image {
id String @id @default(uuid())
path String
user User @relation(fields: [userId], references: [id])
userId String @unique
}
model Broker {
id String @id @default(uuid())
name String @unique
investments Investment[]
}
$ npx prisma migrate dev --name user_image
$ npx prisma migrate dev --name user_image
/codes/expressjs/invest-app-upload/src/models/Image.js
import prisma from '../database/database.js';
async function create({ userId, path }) {
const newImage = await prisma.image.create({
data: {
path,
user: {
connect: {
id: userId,
},
},
},
});
return newImage;
}
async function update({ userId, path }) {
const newImage = await prisma.image.update({
where: {
userId,
},
data: {
path,
user: {
connect: {
id: userId,
},
},
},
});
return newImage;
}
export default { create, update };
/codes/expressjs/invest-app-upload/src/models/Image.js
import prisma from '../database/database.js';
async function create({ userId, path }) {
const newImage = await prisma.image.create({
data: {
path,
user: {
connect: {
id: userId,
},
},
},
});
return newImage;
}
async function update({ userId, path }) {
const newImage = await prisma.image.update({
where: {
userId,
},
data: {
path,
user: {
connect: {
id: userId,
},
},
},
});
return newImage;
}
export default { create, update };
/codes/expressjs/invest-app-upload/src/models/User.js
import bcrypt from 'bcrypt';
import prisma from '../database/database.js';
const saltRounds = Number(process.env.BCRYPT_SALT);
async function create({ name, email, password }) {
if (name && email && password) {
const hash = await bcrypt.hash(password, saltRounds);
const createdUser = await prisma.user.create({
data: { name, email, password: hash },
});
return createdUser;
} else {
throw new Error('Unable to create user');
}
}
async function read(where) {
const users = await prisma.user.findMany({
where,
include: {
image: {
select: {
path: true,
},
},
},
});
if (users.length === 1 && where) {
return users[0];
}
return users;
}
async function readById(id) {
if (id) {
const user = await prisma.user.findUnique({
where: {
id,
},
include: {
image: {
select: {
path: true,
},
},
},
});
return user;
} else {
throw new Error('Unable to find user');
}
}
async function update({ id, name, email, password }) {
if (name && email && password && id) {
const hash = await bcrypt.hash(password, saltRounds);
const updatedUser = await prisma.user.update({
where: {
id,
},
data: { name, email, password: hash },
});
return updatedUser;
} else {
throw new Error('Unable to update user');
}
}
async function remove(id) {
if (id) {
await prisma.user.delete({
where: {
id,
},
});
return true;
} else {
throw new Error('Unable to remove user');
}
}
export default { create, read, readById, update, remove };
/codes/expressjs/invest-app-upload/src/models/User.js
import bcrypt from 'bcrypt';
import prisma from '../database/database.js';
const saltRounds = Number(process.env.BCRYPT_SALT);
async function create({ name, email, password }) {
if (name && email && password) {
const hash = await bcrypt.hash(password, saltRounds);
const createdUser = await prisma.user.create({
data: { name, email, password: hash },
});
return createdUser;
} else {
throw new Error('Unable to create user');
}
}
async function read(where) {
const users = await prisma.user.findMany({
where,
include: {
image: {
select: {
path: true,
},
},
},
});
if (users.length === 1 && where) {
return users[0];
}
return users;
}
async function readById(id) {
if (id) {
const user = await prisma.user.findUnique({
where: {
id,
},
include: {
image: {
select: {
path: true,
},
},
},
});
return user;
} else {
throw new Error('Unable to find user');
}
}
async function update({ id, name, email, password }) {
if (name && email && password && id) {
const hash = await bcrypt.hash(password, saltRounds);
const updatedUser = await prisma.user.update({
where: {
id,
},
data: { name, email, password: hash },
});
return updatedUser;
} else {
throw new Error('Unable to update user');
}
}
async function remove(id) {
if (id) {
await prisma.user.delete({
where: {
id,
},
});
return true;
} else {
throw new Error('Unable to remove user');
}
}
export default { create, read, readById, update, remove };
/codes/expressjs/invest-app-upload/.env.example
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
# EMAIL
EMAIL_HOST=smtp.ethereal.email
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=
EMAIL_PASS=
# STORAGE
STORAGE_TYPE=local
/codes/expressjs/invest-app-upload/.env.example
NODE_ENV=development
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"
# JWT
JWT_SECRET=abc
# BCRYPT
BCRYPT_SALT=10
# EMAIL
EMAIL_HOST=smtp.ethereal.email
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER=
EMAIL_PASS=
# STORAGE
STORAGE_TYPE=local
$ npm install multer
$ npm install multer
/codes/expressjs/invest-app-upload/src/config/multer.js
import path from 'node:path';
import { randomBytes } from 'node:crypto';
import multer from 'multer';
const uploadPath = path.resolve('public', 'imgs', 'profile');
const storageTypes = {
local: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadPath);
},
filename: (req, file, cb) => {
file.key = `${randomBytes(16).toString('hex')}-${file.originalname}`;
cb(null, file.key);
},
}),
};
const config = {
dest: uploadPath,
storage: storageTypes[process.env.STORAGE_TYPE],
limits: {
fileSize: 2 * 1024 * 1024,
},
fileFilter: (req, file, cb) => {
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type.'));
}
},
};
// const config = { dest: 'public/imgs/profile' };
export default config;
/codes/expressjs/invest-app-upload/src/config/multer.js
import path from 'node:path';
import { randomBytes } from 'node:crypto';
import multer from 'multer';
const uploadPath = path.resolve('public', 'imgs', 'profile');
const storageTypes = {
local: multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadPath);
},
filename: (req, file, cb) => {
file.key = `${randomBytes(16).toString('hex')}-${file.originalname}`;
cb(null, file.key);
},
}),
};
const config = {
dest: uploadPath,
storage: storageTypes[process.env.STORAGE_TYPE],
limits: {
fileSize: 2 * 1024 * 1024,
},
fileFilter: (req, file, cb) => {
const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type.'));
}
},
};
// const config = { dest: 'public/imgs/profile' };
export default config;
/codes/expressjs/invest-app-upload/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import multer from 'multer';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import uploadConfig from './config/multer.js';
import SendMail from './services/SendMail.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
import Image from './models/Image.js';
// ...
router.post(
'/users/image',
isAuthenticated,
multer(uploadConfig).single('image'),
async (req, res) => {
try {
const userId = req.userId;
if (req.file) {
const path = `/imgs/profile/${req.file.filename}`;
await Image.create({ userId, path });
res.sendStatus(201);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to create image', 400);
}
}
);
router.put(
'/users/image',
isAuthenticated,
multer(uploadConfig).single('image'),
async (req, res) => {
try {
const userId = req.userId;
if (req.file) {
const path = `/imgs/profile/${req.file.filename}`;
const image = await Image.update({ userId, path });
res.json(image);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to create image', 400);
}
}
);
// ...
/codes/expressjs/invest-app-upload/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import multer from 'multer';
import { z } from 'zod';
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
import uploadConfig from './config/multer.js';
import SendMail from './services/SendMail.js';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
import Image from './models/Image.js';
// ...
router.post(
'/users/image',
isAuthenticated,
multer(uploadConfig).single('image'),
async (req, res) => {
try {
const userId = req.userId;
if (req.file) {
const path = `/imgs/profile/${req.file.filename}`;
await Image.create({ userId, path });
res.sendStatus(201);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to create image', 400);
}
}
);
router.put(
'/users/image',
isAuthenticated,
multer(uploadConfig).single('image'),
async (req, res) => {
try {
const userId = req.userId;
if (req.file) {
const path = `/imgs/profile/${req.file.filename}`;
const image = await Image.update({ userId, path });
res.json(image);
} else {
throw new Error();
}
} catch (error) {
throw new HTTPError('Unable to create image', 400);
}
}
);
// ...
/codes/expressjs/invest-app-upload/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(express.urlencoded({ extended: true }));
server.use(express.static('public'));
server.get('/', (req, res) => {
res.redirect('/signup.html');
});
server.use('/api', router);
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
/codes/expressjs/invest-app-upload/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(express.urlencoded({ extended: true }));
server.use(express.static('public'));
server.get('/', (req, res) => {
res.redirect('/signup.html');
});
server.use('/api', router);
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
/codes/expressjs/invest-app-upload/requests.http
@host = http://localhost:3000
@token = {{signin.response.body.$.token}}
@userId = {{userMe.response.body.$.id}}
### Signin Ok
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "12345678"
}
### Read User (with token)
# @name userMe
GET {{host}}/users/me
Authorization: bearer {{token}}
### Create Image (User)
POST {{host}}/users/image
Authorization: bearer {{token}}
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Disposition: form-data; name="userId"
{{createdUserId}}
--boundary
Content-Disposition: form-data; name="image"; filename="avatar.png"
Content-Type: image/png
< ./public/imgs/profile/avatar.png
--boundary--
### Update Image (User)
PUT {{host}}/users/image
Authorization: bearer {{token}}
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Disposition: form-data; name="userId"
{{userId}}
--boundary
Content-Disposition: form-data; name="image"; filename="avatar.png"
Content-Type: image/png
< ./public/imgs/profile/avatar.png
--boundary--
/codes/expressjs/invest-app-upload/requests.http
@host = http://localhost:3000
@token = {{signin.response.body.$.token}}
@userId = {{userMe.response.body.$.id}}
### Signin Ok
# @name signin
POST {{host}}/signin
Content-Type: application/json
{
"email": "luiz@email.com",
"password": "12345678"
}
### Read User (with token)
# @name userMe
GET {{host}}/users/me
Authorization: bearer {{token}}
### Create Image (User)
POST {{host}}/users/image
Authorization: bearer {{token}}
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Disposition: form-data; name="userId"
{{createdUserId}}
--boundary
Content-Disposition: form-data; name="image"; filename="avatar.png"
Content-Type: image/png
< ./public/imgs/profile/avatar.png
--boundary--
### Update Image (User)
PUT {{host}}/users/image
Authorization: bearer {{token}}
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Disposition: form-data; name="userId"
{{userId}}
--boundary
Content-Disposition: form-data; name="image"; filename="avatar.png"
Content-Type: image/png
< ./public/imgs/profile/avatar.png
--boundary--
Front-end
/codes/expressjs/invest-app-upload/public/profile.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.1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9"
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="/home.html">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="/home.html"
>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"
>
<img
id="dropdown-avatar"
src="/imgs/profile/avatar.png"
alt="Foto de Perfil"
width="30px"
class="border border-secondary-subtle border-4 rounded-circle"
/>
</div>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<h5 id="user-name" class="dropdown-header"></h5>
</li>
<li>
<a class="dropdown-item" href="/profile.html">Profile</a>
</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">Perfil</h1>
<div class="text-center">
<div class="mb-3">
<img
src="/imgs/profile/avatar.png"
alt="Foto de Perfil"
width="200px"
id="profile-image"
/>
</div>
<div class="mb-3">
<span class="fw-bold"> Nome:</span>
<span id="profile-name"></span>
</div>
<div class="mb-3">
<span class="fw-bold"> Email:</span>
<span id="profile-email"></span>
</div>
<p>
<a href="#" data-bs-toggle="modal" data-bs-target="#exampleModal"
>Editar imagem</a
>
</p>
</div>
</div>
<form enctype="multipart/form-data">
<div
class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">
Carregar Imagem
</h1>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<input type="hidden" id="userId" name="userId" />
<input type="file" id="image" name="image" required />
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Cancelar
</button>
<button
type="submit"
class="btn btn-primary"
data-bs-dismiss="modal"
>
Carregar
</button>
</div>
</div>
</div>
</div>
</form>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
crossorigin="anonymous"
></script>
<script src="./js/profile.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-upload/public/profile.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.1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9"
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="/home.html">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="/home.html"
>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"
>
<img
id="dropdown-avatar"
src="/imgs/profile/avatar.png"
alt="Foto de Perfil"
width="30px"
class="border border-secondary-subtle border-4 rounded-circle"
/>
</div>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<h5 id="user-name" class="dropdown-header"></h5>
</li>
<li>
<a class="dropdown-item" href="/profile.html">Profile</a>
</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">Perfil</h1>
<div class="text-center">
<div class="mb-3">
<img
src="/imgs/profile/avatar.png"
alt="Foto de Perfil"
width="200px"
id="profile-image"
/>
</div>
<div class="mb-3">
<span class="fw-bold"> Nome:</span>
<span id="profile-name"></span>
</div>
<div class="mb-3">
<span class="fw-bold"> Email:</span>
<span id="profile-email"></span>
</div>
<p>
<a href="#" data-bs-toggle="modal" data-bs-target="#exampleModal"
>Editar imagem</a
>
</p>
</div>
</div>
<form enctype="multipart/form-data">
<div
class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">
Carregar Imagem
</h1>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<input type="hidden" id="userId" name="userId" />
<input type="file" id="image" name="image" required />
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Cancelar
</button>
<button
type="submit"
class="btn btn-primary"
data-bs-dismiss="modal"
>
Carregar
</button>
</div>
</div>
</div>
</div>
</form>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
crossorigin="anonymous"
></script>
<script src="./js/profile.js" type="module"></script>
</body>
</html>
/codes/expressjs/invest-app-upload/public/js/profile.js
import API from './services/api.js';
import Auth from './lib/auth.js';
const form = document.querySelector('form');
let formMethod;
async function loadProfile() {
const user = await API.read('/users/me');
let image;
if (user.image) {
image = user.image.path;
formMethod = 'put';
} else {
image = '/imgs/profile/avatar.png';
formMethod = 'post';
}
document.querySelector('#profile-name').innerText = user.name;
document.querySelector('#user-name').innerText = user.name;
document.querySelector('#profile-email').innerText = user.email;
document.querySelector('#profile-image').src = image;
document.querySelector('#dropdown-avatar').src = image;
document.querySelector('#userId').value = user.id;
}
form.onsubmit = async (event) => {
event.preventDefault();
const image = new FormData(form);
let newImage;
if (formMethod === 'post') {
newImage = await API.create('/users/image', image, true, true);
} else if (formMethod === 'put') {
newImage = await API.update('/users/image', image, true);
}
document.querySelector('#profile-image').src = newImage.path;
document.querySelector('#dropdown-avatar').src = newImage.path;
form.reset();
};
if (Auth.isAuthenticated()) {
loadProfile();
}
/codes/expressjs/invest-app-upload/public/js/profile.js
import API from './services/api.js';
import Auth from './lib/auth.js';
const form = document.querySelector('form');
let formMethod;
async function loadProfile() {
const user = await API.read('/users/me');
let image;
if (user.image) {
image = user.image.path;
formMethod = 'put';
} else {
image = '/imgs/profile/avatar.png';
formMethod = 'post';
}
document.querySelector('#profile-name').innerText = user.name;
document.querySelector('#user-name').innerText = user.name;
document.querySelector('#profile-email').innerText = user.email;
document.querySelector('#profile-image').src = image;
document.querySelector('#dropdown-avatar').src = image;
document.querySelector('#userId').value = user.id;
}
form.onsubmit = async (event) => {
event.preventDefault();
const image = new FormData(form);
let newImage;
if (formMethod === 'post') {
newImage = await API.create('/users/image', image, true, true);
} else if (formMethod === 'put') {
newImage = await API.update('/users/image', image, true);
}
document.querySelector('#profile-image').src = newImage.path;
document.querySelector('#dropdown-avatar').src = newImage.path;
form.reset();
};
if (Auth.isAuthenticated()) {
loadProfile();
}
/codes/expressjs/invest-app-upload/public/js/services/api.js
import Auth from '../lib/auth.js';
const domain = '/api';
async function create(resource, data, auth = true, formData = false) {
const url = `${domain}${resource}`;
const config = {
method: 'POST',
body: formData ? data : JSON.stringify(data),
headers: {},
};
if (!formData) {
config.headers['Content-Type'] = 'application/json; charset=UTF-8';
}
if (auth) {
config.headers.Authorization = `Bearer ${Auth.getToken()}`;
}
const res = await fetch(url, config);
if (auth && 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, formData = false) {
const url = `${domain}${resource}`;
const config = {
method: 'PUT',
body: formData ? data : JSON.stringify(data),
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
if (!formData) {
config.headers['Content-Type'] = 'application/json; charset=UTF-8';
}
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',
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-upload/public/js/services/api.js
import Auth from '../lib/auth.js';
const domain = '/api';
async function create(resource, data, auth = true, formData = false) {
const url = `${domain}${resource}`;
const config = {
method: 'POST',
body: formData ? data : JSON.stringify(data),
headers: {},
};
if (!formData) {
config.headers['Content-Type'] = 'application/json; charset=UTF-8';
}
if (auth) {
config.headers.Authorization = `Bearer ${Auth.getToken()}`;
}
const res = await fetch(url, config);
if (auth && 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, formData = false) {
const url = `${domain}${resource}`;
const config = {
method: 'PUT',
body: formData ? data : JSON.stringify(data),
headers: {
Authorization: `Bearer ${Auth.getToken()}`,
},
};
if (!formData) {
config.headers['Content-Type'] = 'application/json; charset=UTF-8';
}
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',
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 };