Upload de Arquivo

Open in GitHub Open in Codespaces

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 };
 

Editar esta página