Relação entre Entidades

Open in GitHub Open in Codespaces

Arquivos
invest-app-prismajs-relation
├── package-lock.json
├── package.json
├── prisma
│   ├── dev.db
│   ├── migrations
│   │   ├── 20230826214323_init
│   │   │   └── migration.sql
│   │   ├── 20241011213516_create_category_broker
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   ├── schema.prisma
│   ├── seed.js
│   └── seeders.json
├── public
│   ├── css
│   │   └── style.css
│   ├── index.html
│   └── js
│       ├── index.js
│       ├── lib
│       │   └── format.js
│       └── services
│           └── api.js
├── requests.http
└── src
    ├── database
    │   └── database.js
    ├── index.js
    ├── models
    │   ├── Category.js
    │   └── Investment.js
    └── routes.js
Arquivos
invest-app-prismajs-relation
├── package-lock.json
├── package.json
├── prisma
│   ├── dev.db
│   ├── migrations
│   │   ├── 20230826214323_init
│   │   │   └── migration.sql
│   │   ├── 20241011213516_create_category_broker
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   ├── schema.prisma
│   ├── seed.js
│   └── seeders.json
├── public
│   ├── css
│   │   └── style.css
│   ├── index.html
│   └── js
│       ├── index.js
│       ├── lib
│       │   └── format.js
│       └── services
│           └── api.js
├── requests.http
└── src
    ├── database
    │   └── database.js
    ├── index.js
    ├── models
    │   ├── Category.js
    │   └── Investment.js
    └── routes.js

Migration

/codes/expressjs/invest-app-prismajs-relation/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
  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 Broker {
  id          String       @id @default(uuid())
  name        String       @unique
  investments Investment[]
}
 
/codes/expressjs/invest-app-prismajs-relation/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
  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 Broker {
  id          String       @id @default(uuid())
  name        String       @unique
  investments Investment[]
}
 
$ npx prisma migrate reset
$ npx prisma migrate dev --name create_category_broker
$ npx prisma migrate reset
$ npx prisma migrate dev --name create_category_broker

Tabelas:

Seed

/codes/expressjs/invest-app-prismajs-relation/prisma/seed.js
import { resolve } from 'node:path';
import { readFileSync } from 'node:fs';
import { PrismaClient } from '@prisma/client';
 
const prisma = new PrismaClient();
 
async function main() {
  const file = resolve('prisma', 'seeders.json');
 
  const seed = JSON.parse(readFileSync(file));
 
  await prisma.category.createMany({
    data: seed.categories,
  });
}
main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });
 
/codes/expressjs/invest-app-prismajs-relation/prisma/seed.js
import { resolve } from 'node:path';
import { readFileSync } from 'node:fs';
import { PrismaClient } from '@prisma/client';
 
const prisma = new PrismaClient();
 
async function main() {
  const file = resolve('prisma', 'seeders.json');
 
  const seed = JSON.parse(readFileSync(file));
 
  await prisma.category.createMany({
    data: seed.categories,
  });
}
main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });
 
/codes/expressjs/invest-app-prismajs-relation/prisma/seeders.json
{
  "categories": [
    {
      "name": "Pós",
      "color": "#6366f1"
    },
    {
      "name": "Pré",
      "color": "#d946ef"
    },
    {
      "name": "IPCA",
      "color": "#f43f5e"
    },
    {
      "name": "Renda Variável",
      "color": "#eab308"
    },
    {
      "name": "Fundo de Investimento",
      "color": "#46efb1"
    },
    {
      "name": "Outros",
      "color": "#111111"
    }
  ]
}
 
/codes/expressjs/invest-app-prismajs-relation/prisma/seeders.json
{
  "categories": [
    {
      "name": "Pós",
      "color": "#6366f1"
    },
    {
      "name": "Pré",
      "color": "#d946ef"
    },
    {
      "name": "IPCA",
      "color": "#f43f5e"
    },
    {
      "name": "Renda Variável",
      "color": "#eab308"
    },
    {
      "name": "Fundo de Investimento",
      "color": "#46efb1"
    },
    {
      "name": "Outros",
      "color": "#111111"
    }
  ]
}
 
$ npx prisma migrate reset
$ npx prisma db seed
$ npx prisma studio
$ npx prisma migrate reset
$ npx prisma db seed
$ npx prisma studio

Model

/codes/expressjs/invest-app-prismajs-relation/src/models/Category.js
import prisma from '../database/database.js';
 
async function create(category) {
  const newCategory = await prisma.category.create({
    data: category,
  });
 
  return newCategory;
}
 
async function read(where) {
  const categories = await prisma.category.findMany({
    where,
  });
 
  if (categories.length === 1 && where) {
    return categories[0];
  }
 
  return categories;
}
 
async function readById(id) {
  const category = await prisma.category.findUnique({
    where: {
      id,
    },
  });
 
  return category;
}
 
export default { create, read, readById };
 
/codes/expressjs/invest-app-prismajs-relation/src/models/Category.js
import prisma from '../database/database.js';
 
async function create(category) {
  const newCategory = await prisma.category.create({
    data: category,
  });
 
  return newCategory;
}
 
async function read(where) {
  const categories = await prisma.category.findMany({
    where,
  });
 
  if (categories.length === 1 && where) {
    return categories[0];
  }
 
  return categories;
}
 
async function readById(id) {
  const category = await prisma.category.findUnique({
    where: {
      id,
    },
  });
 
  return category;
}
 
export default { create, read, readById };
 
/codes/expressjs/invest-app-prismajs-relation/src/models/Investment.js
import prisma from '../database/database.js';
 
async function create({
  name,
  value,
  interest,
  createdAt,
  broker,
  categoryId,
}) {
  if (name && value && interest && createdAt && broker && categoryId) {
    const createdInvestment = await prisma.investment.create({
      data: {
        name,
        value,
        interest,
        createdAt,
        category: {
          connect: {
            id: categoryId,
          },
        },
        broker: {
          connectOrCreate: {
            where: {
              name: broker,
            },
            create: {
              name: broker,
            },
          },
        },
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return createdInvestment;
  } else {
    throw new Error('Unable to create investment');
  }
}
 
async function read(where) {
  if (where?.name) {
    where.name = {
      contains: where.name,
    };
  }
 
  const investments = await prisma.investment.findMany({
    where,
    include: {
      category: true,
      broker: true,
    },
  });
 
  if (investments.length === 1 && where) {
    return investments[0];
  }
 
  return investments;
}
 
async function readById(id) {
  if (id) {
    const investment = await prisma.investment.findUnique({
      where: {
        id,
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return investment;
  } else {
    throw new Error('Unable to find investment');
  }
}
 
async function update({
  id,
  name,
  value,
  interest,
  createdAt,
  broker,
  categoryId,
}) {
  if (name && value && id && categoryId && broker && interest && createdAt) {
    const updatedInvestment = await prisma.investment.update({
      where: {
        id,
      },
      data: {
        name,
        value,
        interest,
        createdAt,
        category: {
          connect: {
            id: categoryId,
          },
        },
        broker: {
          connectOrCreate: {
            where: {
              name: broker,
            },
            create: {
              name: broker,
            },
          },
        },
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return updatedInvestment;
  } else {
    throw new Error('Unable to update investment');
  }
}
 
async function remove(id) {
  if (id) {
    await prisma.investment.delete({
      where: {
        id,
      },
    });
 
    return true;
  } else {
    throw new Error('Unable to remove investment');
  }
}
 
export default { create, read, readById, update, remove };
 
/codes/expressjs/invest-app-prismajs-relation/src/models/Investment.js
import prisma from '../database/database.js';
 
async function create({
  name,
  value,
  interest,
  createdAt,
  broker,
  categoryId,
}) {
  if (name && value && interest && createdAt && broker && categoryId) {
    const createdInvestment = await prisma.investment.create({
      data: {
        name,
        value,
        interest,
        createdAt,
        category: {
          connect: {
            id: categoryId,
          },
        },
        broker: {
          connectOrCreate: {
            where: {
              name: broker,
            },
            create: {
              name: broker,
            },
          },
        },
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return createdInvestment;
  } else {
    throw new Error('Unable to create investment');
  }
}
 
async function read(where) {
  if (where?.name) {
    where.name = {
      contains: where.name,
    };
  }
 
  const investments = await prisma.investment.findMany({
    where,
    include: {
      category: true,
      broker: true,
    },
  });
 
  if (investments.length === 1 && where) {
    return investments[0];
  }
 
  return investments;
}
 
async function readById(id) {
  if (id) {
    const investment = await prisma.investment.findUnique({
      where: {
        id,
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return investment;
  } else {
    throw new Error('Unable to find investment');
  }
}
 
async function update({
  id,
  name,
  value,
  interest,
  createdAt,
  broker,
  categoryId,
}) {
  if (name && value && id && categoryId && broker && interest && createdAt) {
    const updatedInvestment = await prisma.investment.update({
      where: {
        id,
      },
      data: {
        name,
        value,
        interest,
        createdAt,
        category: {
          connect: {
            id: categoryId,
          },
        },
        broker: {
          connectOrCreate: {
            where: {
              name: broker,
            },
            create: {
              name: broker,
            },
          },
        },
      },
      include: {
        category: true,
        broker: true,
      },
    });
 
    return updatedInvestment;
  } else {
    throw new Error('Unable to update investment');
  }
}
 
async function remove(id) {
  if (id) {
    await prisma.investment.delete({
      where: {
        id,
      },
    });
 
    return true;
  } else {
    throw new Error('Unable to remove investment');
  }
}
 
export default { create, read, readById, update, remove };
 

Router

/codes/expressjs/invest-app-prismajs-relation/src/routes.js
import express from 'express';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
 
class HTTPError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/investments', async (req, res) => {
  try {
    const investment = req.body;
 
    if (investment.createdAt) {
      investment.createdAt = new Date(
        investment.createdAt + 'T00:00:00-03:00'
      ).toISOString();
    }
 
    const createdInvestment = await Investment.create(investment);
 
    return res.json(createdInvestment);
  } catch (error) {
    throw new HTTPError('Unable to create investment', 400);
  }
});
 
router.get('/investments', async (req, res) => {
  try {
    const { name } = req.query;
 
    let investments;
 
    if (name) {
      investments = await Investment.read({ name });
    } else {
      investments = await Investment.read();
    }
 
    return res.json(investments);
  } catch (error) {
    throw new HTTPError('Unable to read investments', 400);
  }
});
 
router.get('/investments/:id', async (req, res) => {
  try {
    const id = req.params.id;
 
    const investment = await Investment.readById(id);
 
    return res.json(investment);
  } catch (error) {
    throw new HTTPError('Unable to find investment', 400);
  }
});
 
router.put('/investments/:id', async (req, res) => {
  try {
    const id = req.params.id;
 
    const investment = req.body;
 
    if (investment.createdAt) {
      investment.createdAt = new Date(
        investment.createdAt + 'T00:00:00-03:00'
      ).toISOString();
    }
 
    const updatedInvestment = await Investment.update({ ...investment, id });
 
    return res.json(updatedInvestment);
  } catch (error) {
    throw new HTTPError('Unable to update investment', 400);
  }
});
 
router.delete('/investments/:id', 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', 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);
  }
});
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((err, req, res, next) => {
  // console.error(err.stack);
  if (err instanceof HTTPError) {
    return res.status(err.code).json({ message: err.message });
  } else {
    return res.status(500).json({ message: 'Something broke!' });
  }
});
 
export default router;
 
/codes/expressjs/invest-app-prismajs-relation/src/routes.js
import express from 'express';
import Category from './models/Category.js';
import Investment from './models/Investment.js';
 
class HTTPError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post('/investments', async (req, res) => {
  try {
    const investment = req.body;
 
    if (investment.createdAt) {
      investment.createdAt = new Date(
        investment.createdAt + 'T00:00:00-03:00'
      ).toISOString();
    }
 
    const createdInvestment = await Investment.create(investment);
 
    return res.json(createdInvestment);
  } catch (error) {
    throw new HTTPError('Unable to create investment', 400);
  }
});
 
router.get('/investments', async (req, res) => {
  try {
    const { name } = req.query;
 
    let investments;
 
    if (name) {
      investments = await Investment.read({ name });
    } else {
      investments = await Investment.read();
    }
 
    return res.json(investments);
  } catch (error) {
    throw new HTTPError('Unable to read investments', 400);
  }
});
 
router.get('/investments/:id', async (req, res) => {
  try {
    const id = req.params.id;
 
    const investment = await Investment.readById(id);
 
    return res.json(investment);
  } catch (error) {
    throw new HTTPError('Unable to find investment', 400);
  }
});
 
router.put('/investments/:id', async (req, res) => {
  try {
    const id = req.params.id;
 
    const investment = req.body;
 
    if (investment.createdAt) {
      investment.createdAt = new Date(
        investment.createdAt + 'T00:00:00-03:00'
      ).toISOString();
    }
 
    const updatedInvestment = await Investment.update({ ...investment, id });
 
    return res.json(updatedInvestment);
  } catch (error) {
    throw new HTTPError('Unable to update investment', 400);
  }
});
 
router.delete('/investments/:id', 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', 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);
  }
});
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((err, req, res, next) => {
  // console.error(err.stack);
  if (err instanceof HTTPError) {
    return res.status(err.code).json({ message: err.message });
  } else {
    return res.status(500).json({ message: 'Something broke!' });
  }
});
 
export default router;
 
/codes/expressjs/invest-app-prismajs-relation/requests.http
@host = http://localhost:3000/api
@createdInvestmentId = {{createdInvestment.response.body.$.id}}
 
### Read Categories
 
GET {{host}}/categories
 
### Read Categories by Name
 
# @name categoryPos
GET {{host}}/categories?name=Pós
 
### Read Categories by Name
 
# @name categoryIpca
GET {{host}}/categories?name=IPCA
 
### Create Investment
 
@categoryIpcaId = {{categoryIpca.response.body.$.id}}
 
# @name createdInvestment
POST {{host}}/investments
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
 
### Read Investments by Name
 
GET {{host}}/investments?name=Tesouro
 
### Update Investment
 
@categoryPosId = {{categoryPos.response.body.$.id}}
 
PUT {{host}}/investments/{{createdInvestmentId}}
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}}
 
 
/codes/expressjs/invest-app-prismajs-relation/requests.http
@host = http://localhost:3000/api
@createdInvestmentId = {{createdInvestment.response.body.$.id}}
 
### Read Categories
 
GET {{host}}/categories
 
### Read Categories by Name
 
# @name categoryPos
GET {{host}}/categories?name=Pós
 
### Read Categories by Name
 
# @name categoryIpca
GET {{host}}/categories?name=IPCA
 
### Create Investment
 
@categoryIpcaId = {{categoryIpca.response.body.$.id}}
 
# @name createdInvestment
POST {{host}}/investments
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
 
### Read Investments by Name
 
GET {{host}}/investments?name=Tesouro
 
### Update Investment
 
@categoryPosId = {{categoryPos.response.body.$.id}}
 
PUT {{host}}/investments/{{createdInvestmentId}}
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}}
 
 

View

/codes/expressjs/invest-app-prismajs-relation/public/index.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="#">Home</a>
            </li>
          </ul>
        </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/index.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-prismajs-relation/public/index.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="#">Home</a>
            </li>
          </ul>
        </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/index.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-prismajs-relation/public/js/index.js
import API from './services/api.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);
  }
}
 
loadInvestmentCards();
 
loadHandleCreateInvestment();
 
loadCategoriesSelect();
 
loadHandleRemoveInvestment();
 
/codes/expressjs/invest-app-prismajs-relation/public/js/index.js
import API from './services/api.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);
  }
}
 
loadInvestmentCards();
 
loadHandleCreateInvestment();
 
loadCategoriesSelect();
 
loadHandleRemoveInvestment();
 

Editar esta página