Validação

Database

Opções de validação:

$ sqlite3 prisma/dev.db .dump
$ sqlite3 prisma/dev.db .dump
Schema do Banco
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
 
CREATE TABLE IF NOT EXISTS "Category" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "color" TEXT NOT NULL
);
INSERT INTO Category VALUES('9be47002-2376-4975-acc9-d0c0b09fb446','Pós','#6366f1');
INSERT INTO Category VALUES('5d6a114c-6bb1-4be8-9962-8c77fa411f99','Pré','#d946ef');
INSERT INTO Category VALUES('f950a0ab-7caf-441c-9386-2082c78172e5','IPCA','#f43f5e');
INSERT INTO Category VALUES('7da57d6b-aca5-4766-b323-c3bf32c4ec28','Renda Variável','#eab308');
INSERT INTO Category VALUES('6fcdde52-1eb4-414e-8485-be2a6ce70176','Fundo de Investimento','#46efb1');
INSERT INTO Category VALUES('1d80abe9-7a31-4b90-b10b-2ebdc7fdcff0','Outros','#111111');
 
CREATE TABLE IF NOT EXISTS "User" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "email" TEXT NOT NULL,
    "password" TEXT NOT NULL
);
INSERT INTO User VALUES('b37dd024-8d74-4c99-859f-d94f19e51b6c','Luiz','luiz@email.com','$2b$10$Zn0pLtAUqnYPn3ggCvExoubRQ7XhVa7PcjF7v9AIvvPs5/yDi4EfG');
 
CREATE TABLE IF NOT EXISTS "Broker" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL
);
INSERT INTO Broker VALUES('1e6f7c70-6dea-4f51-8f19-230262b666f1','Banco Inter');
 
CREATE TABLE IF NOT EXISTS "Investment" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "value" INTEGER NOT NULL,
    "interest" TEXT NOT NULL,
    "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "categoryId" TEXT NOT NULL,
    "userId" TEXT NOT NULL,
    "brokerId" TEXT NOT NULL,
    CONSTRAINT "Investment_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT "Investment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT "Investment_brokerId_fkey" FOREIGN KEY ("brokerId") REFERENCES "Broker" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO Investment VALUES('cf2b1c62-a716-41b0-a4e7-afb5aa8a1af7','Tesouro Selic 2029',20000,'100% Selic',1693537200000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
INSERT INTO Investment VALUES('52ab29d5-0392-4b25-85a3-78faa0ef31ca','CDB Inter',15000,'100% CDI',1693710000000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
 
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
CREATE UNIQUE INDEX "Category_color_key" ON "Category"("color");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE UNIQUE INDEX "Broker_name_key" ON "Broker"("name");
COMMIT;
Schema do Banco
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
 
CREATE TABLE IF NOT EXISTS "Category" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "color" TEXT NOT NULL
);
INSERT INTO Category VALUES('9be47002-2376-4975-acc9-d0c0b09fb446','Pós','#6366f1');
INSERT INTO Category VALUES('5d6a114c-6bb1-4be8-9962-8c77fa411f99','Pré','#d946ef');
INSERT INTO Category VALUES('f950a0ab-7caf-441c-9386-2082c78172e5','IPCA','#f43f5e');
INSERT INTO Category VALUES('7da57d6b-aca5-4766-b323-c3bf32c4ec28','Renda Variável','#eab308');
INSERT INTO Category VALUES('6fcdde52-1eb4-414e-8485-be2a6ce70176','Fundo de Investimento','#46efb1');
INSERT INTO Category VALUES('1d80abe9-7a31-4b90-b10b-2ebdc7fdcff0','Outros','#111111');
 
CREATE TABLE IF NOT EXISTS "User" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "email" TEXT NOT NULL,
    "password" TEXT NOT NULL
);
INSERT INTO User VALUES('b37dd024-8d74-4c99-859f-d94f19e51b6c','Luiz','luiz@email.com','$2b$10$Zn0pLtAUqnYPn3ggCvExoubRQ7XhVa7PcjF7v9AIvvPs5/yDi4EfG');
 
CREATE TABLE IF NOT EXISTS "Broker" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL
);
INSERT INTO Broker VALUES('1e6f7c70-6dea-4f51-8f19-230262b666f1','Banco Inter');
 
CREATE TABLE IF NOT EXISTS "Investment" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "value" INTEGER NOT NULL,
    "interest" TEXT NOT NULL,
    "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "categoryId" TEXT NOT NULL,
    "userId" TEXT NOT NULL,
    "brokerId" TEXT NOT NULL,
    CONSTRAINT "Investment_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
    CONSTRAINT "Investment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT "Investment_brokerId_fkey" FOREIGN KEY ("brokerId") REFERENCES "Broker" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO Investment VALUES('cf2b1c62-a716-41b0-a4e7-afb5aa8a1af7','Tesouro Selic 2029',20000,'100% Selic',1693537200000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
INSERT INTO Investment VALUES('52ab29d5-0392-4b25-85a3-78faa0ef31ca','CDB Inter',15000,'100% CDI',1693710000000,'9be47002-2376-4975-acc9-d0c0b09fb446','b37dd024-8d74-4c99-859f-d94f19e51b6c','1e6f7c70-6dea-4f51-8f19-230262b666f1');
 
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
CREATE UNIQUE INDEX "Category_color_key" ON "Category"("color");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE UNIQUE INDEX "Broker_name_key" ON "Broker"("name");
COMMIT;

Back-end

Open in GitHub Open in Codespaces

Arquivos
invest-app-validation
├── package-lock.json
├── package.json
├── prisma
│   ├── dev.db
│   ├── migrations
│   │   ├── 20230826214323_init
│   │   │   └── migration.sql
│   │   ├── 20230902014613_create_category
│   │   │   └── migration.sql
│   │   ├── 20230902065351_create_user
│   │   │   └── migration.sql
│   │   ├── 20230902111235_create_investment_broker_interest_created_at
│   │   │   └── migration.sql
│   │   ├── 20230902131930_create_investment_user_cascade
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   ├── schema.prisma
│   ├── seed.js
│   └── seeders.json
├── public
│   ├── css
│   │   └── style.css
│   ├── home.html
│   ├── js
│   │   ├── home.js
│   │   ├── lib
│   │   │   ├── auth.js
│   │   │   └── format.js
│   │   ├── services
│   │   │   └── api.js
│   │   ├── signin.js
│   │   └── signup.js
│   ├── signin.html
│   └── signup.html
├── requests.http
└── src
    ├── database
    │   └── database.js
    ├── index.js
    ├── middleware
    │   ├── auth.js
    │   └── validate.js
    ├── models
    │   ├── Category.js
    │   ├── Investment.js
    │   └── User.js
    └── routes.js
Arquivos
invest-app-validation
├── package-lock.json
├── package.json
├── prisma
│   ├── dev.db
│   ├── migrations
│   │   ├── 20230826214323_init
│   │   │   └── migration.sql
│   │   ├── 20230902014613_create_category
│   │   │   └── migration.sql
│   │   ├── 20230902065351_create_user
│   │   │   └── migration.sql
│   │   ├── 20230902111235_create_investment_broker_interest_created_at
│   │   │   └── migration.sql
│   │   ├── 20230902131930_create_investment_user_cascade
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   ├── schema.prisma
│   ├── seed.js
│   └── seeders.json
├── public
│   ├── css
│   │   └── style.css
│   ├── home.html
│   ├── js
│   │   ├── home.js
│   │   ├── lib
│   │   │   ├── auth.js
│   │   │   └── format.js
│   │   ├── services
│   │   │   └── api.js
│   │   ├── signin.js
│   │   └── signup.js
│   ├── signin.html
│   └── signup.html
├── requests.http
└── src
    ├── database
    │   └── database.js
    ├── index.js
    ├── middleware
    │   ├── auth.js
    │   └── validate.js
    ├── models
    │   ├── Category.js
    │   ├── Investment.js
    │   └── User.js
    └── routes.js
$ npm install zod
$ npm install zod
/codes/expressjs/invest-app-validation/src/middleware/validate.js
export function validate(schema) {
  return function (req, res, next) {
    try {
      schema.parse({
        body: req.body,
        query: req.query,
        params: req.params,
      });
 
      next();
    } catch (err) {
      return res.status(400).send(err.errors);
    }
  };
}
 
/codes/expressjs/invest-app-validation/src/middleware/validate.js
export function validate(schema) {
  return function (req, res, next) {
    try {
      schema.parse({
        body: req.body,
        query: req.query,
        params: req.params,
      });
 
      next();
    } catch (err) {
      return res.status(400).send(err.errors);
    }
  };
}
 
/codes/expressjs/invest-app-validation/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
 
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
 
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
 
class HTTPError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post(
  '/investments',
  isAuthenticated,
  validate(
    z.object({
      body: z.object({
        name: z.string(),
        value: z.number(),
        interest: z.string(),
        createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        broker: z.string(),
        categoryId: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const investment = req.body;
 
      if (investment.createdAt) {
        investment.createdAt = new Date(
          investment.createdAt + 'T00:00:00-03:00'
        ).toISOString();
      }
 
      const userId = req.userId;
 
      const createdInvestment = await Investment.create({
        ...investment,
        userId,
      });
 
      return res.json(createdInvestment);
    } catch (error) {
      console.error(error.stack);
      throw new HTTPError('Unable to create investment', 400);
    }
  }
);
 
router.get(
  '/investments',
  isAuthenticated,
  validate(
    z.object({
      query: z.object({
        name: z.string().optional(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { name } = req.query;
 
      const userId = req.userId;
 
      let investments;
 
      if (name) {
        investments = await Investment.read({ name, userId });
      } else {
        investments = await Investment.read({ userId });
      }
 
      return res.json(investments);
    } catch (error) {
      throw new HTTPError('Unable to read investments', 400);
    }
  }
);
 
router.get(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const id = req.params.id;
 
      const userId = req.userId;
 
      const investment = await Investment.readById(id, { userId });
 
      return res.json(investment);
    } catch (error) {
      throw new HTTPError('Unable to find investment', 400);
    }
  }
);
 
router.put(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
      body: z.object({
        name: z.string(),
        value: z.number(),
        interest: z.string(),
        createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        broker: z.string(),
        categoryId: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const investment = req.body;
 
      if (investment.createdAt) {
        investment.createdAt = new Date(
          investment.createdAt + 'T00:00:00-03:00'
        ).toISOString();
      }
 
      const id = req.params.id;
 
      const userId = req.userId;
 
      const updatedInvestment = await Investment.update({
        ...investment,
        id,
        userId,
      });
 
      return res.json(updatedInvestment);
    } catch (error) {
      console.error(error.stack);
      throw new HTTPError('Unable to update investment', 400);
    }
  }
);
 
router.delete(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const id = req.params.id;
 
      if (await Investment.remove(id)) {
        return res.sendStatus(204);
      } else {
        throw new Error();
      }
    } catch (error) {
      throw new HTTPError('Unable to remove investment', 400);
    }
  }
);
 
router.get(
  '/categories',
  isAuthenticated,
  validate(
    z.object({
      query: z.object({
        name: z.string().optional(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { name } = req.query;
 
      let categories;
 
      if (name) {
        categories = await Category.read({ name });
      } else {
        categories = await Category.read();
      }
 
      return res.json(categories);
    } catch (error) {
      throw new HTTPError('Unable to read investments', 400);
    }
  }
);
 
router.post(
  '/users',
  validate(
    z.object({
      body: z.object({
        name: z.string(),
        email: z.string().email(),
        password: z.string().min(8),
      }),
    })
  ),
  async (req, res) => {
    try {
      const user = req.body;
 
      delete user.confirmationPassword;
 
      const newUser = await User.create(user);
 
      delete newUser.password;
 
      res.status(201).json(newUser);
    } catch (error) {
      if (
        error.message.includes(
          'Unique constraint failed on the fields: (`email`)'
        )
      ) {
        throw new HTTPError('Email already exists', 400);
      }
 
      throw new HTTPError('Unable to create user', 400);
    }
  }
);
 
router.get('/users/me', isAuthenticated, async (req, res) => {
  try {
    const userId = req.userId;
 
    const user = await User.readById(userId);
 
    delete user.password;
 
    return res.json(user);
  } catch (error) {
    throw new HTTPError('Unable to find user', 400);
  }
});
 
router.post(
  '/signin',
  validate(
    z.object({
      body: z.object({
        email: z.string().email(),
        password: z.string().min(8),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { email, password } = req.body;
 
      const { id: userId, password: hash } = await User.read({ email });
 
      const match = await bcrypt.compare(password, hash);
 
      if (match) {
        const token = jwt.sign(
          { userId },
          process.env.JWT_SECRET,
          { expiresIn: 3600000 } // 1h
        );
 
        return res.json({ auth: true, token });
      } else {
        throw new Error('User not found');
      }
    } catch (error) {
      res.status(401).json({ error: 'User not found' });
    }
  }
);
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((error, req, res, next) => {
  // console.error(error.stack);
  if (error instanceof HTTPError) {
    return res.status(error.code).json({ message: error.message });
  } else {
    return res.status(500).json({ message: 'Something broke!' });
  }
});
 
export default router;
 
/codes/expressjs/invest-app-validation/src/routes.js
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { z } from 'zod';
 
import { isAuthenticated } from './middleware/auth.js';
import { validate } from './middleware/validate.js';
 
import Category from './models/Category.js';
import Investment from './models/Investment.js';
import User from './models/User.js';
 
class HTTPError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
}
 
const router = express.Router();
 
router.post(
  '/investments',
  isAuthenticated,
  validate(
    z.object({
      body: z.object({
        name: z.string(),
        value: z.number(),
        interest: z.string(),
        createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        broker: z.string(),
        categoryId: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const investment = req.body;
 
      if (investment.createdAt) {
        investment.createdAt = new Date(
          investment.createdAt + 'T00:00:00-03:00'
        ).toISOString();
      }
 
      const userId = req.userId;
 
      const createdInvestment = await Investment.create({
        ...investment,
        userId,
      });
 
      return res.json(createdInvestment);
    } catch (error) {
      console.error(error.stack);
      throw new HTTPError('Unable to create investment', 400);
    }
  }
);
 
router.get(
  '/investments',
  isAuthenticated,
  validate(
    z.object({
      query: z.object({
        name: z.string().optional(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { name } = req.query;
 
      const userId = req.userId;
 
      let investments;
 
      if (name) {
        investments = await Investment.read({ name, userId });
      } else {
        investments = await Investment.read({ userId });
      }
 
      return res.json(investments);
    } catch (error) {
      throw new HTTPError('Unable to read investments', 400);
    }
  }
);
 
router.get(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const id = req.params.id;
 
      const userId = req.userId;
 
      const investment = await Investment.readById(id, { userId });
 
      return res.json(investment);
    } catch (error) {
      throw new HTTPError('Unable to find investment', 400);
    }
  }
);
 
router.put(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
      body: z.object({
        name: z.string(),
        value: z.number(),
        interest: z.string(),
        createdAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        broker: z.string(),
        categoryId: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const investment = req.body;
 
      if (investment.createdAt) {
        investment.createdAt = new Date(
          investment.createdAt + 'T00:00:00-03:00'
        ).toISOString();
      }
 
      const id = req.params.id;
 
      const userId = req.userId;
 
      const updatedInvestment = await Investment.update({
        ...investment,
        id,
        userId,
      });
 
      return res.json(updatedInvestment);
    } catch (error) {
      console.error(error.stack);
      throw new HTTPError('Unable to update investment', 400);
    }
  }
);
 
router.delete(
  '/investments/:id',
  isAuthenticated,
  validate(
    z.object({
      params: z.object({
        id: z.string().uuid(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const id = req.params.id;
 
      if (await Investment.remove(id)) {
        return res.sendStatus(204);
      } else {
        throw new Error();
      }
    } catch (error) {
      throw new HTTPError('Unable to remove investment', 400);
    }
  }
);
 
router.get(
  '/categories',
  isAuthenticated,
  validate(
    z.object({
      query: z.object({
        name: z.string().optional(),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { name } = req.query;
 
      let categories;
 
      if (name) {
        categories = await Category.read({ name });
      } else {
        categories = await Category.read();
      }
 
      return res.json(categories);
    } catch (error) {
      throw new HTTPError('Unable to read investments', 400);
    }
  }
);
 
router.post(
  '/users',
  validate(
    z.object({
      body: z.object({
        name: z.string(),
        email: z.string().email(),
        password: z.string().min(8),
      }),
    })
  ),
  async (req, res) => {
    try {
      const user = req.body;
 
      delete user.confirmationPassword;
 
      const newUser = await User.create(user);
 
      delete newUser.password;
 
      res.status(201).json(newUser);
    } catch (error) {
      if (
        error.message.includes(
          'Unique constraint failed on the fields: (`email`)'
        )
      ) {
        throw new HTTPError('Email already exists', 400);
      }
 
      throw new HTTPError('Unable to create user', 400);
    }
  }
);
 
router.get('/users/me', isAuthenticated, async (req, res) => {
  try {
    const userId = req.userId;
 
    const user = await User.readById(userId);
 
    delete user.password;
 
    return res.json(user);
  } catch (error) {
    throw new HTTPError('Unable to find user', 400);
  }
});
 
router.post(
  '/signin',
  validate(
    z.object({
      body: z.object({
        email: z.string().email(),
        password: z.string().min(8),
      }),
    })
  ),
  async (req, res) => {
    try {
      const { email, password } = req.body;
 
      const { id: userId, password: hash } = await User.read({ email });
 
      const match = await bcrypt.compare(password, hash);
 
      if (match) {
        const token = jwt.sign(
          { userId },
          process.env.JWT_SECRET,
          { expiresIn: 3600000 } // 1h
        );
 
        return res.json({ auth: true, token });
      } else {
        throw new Error('User not found');
      }
    } catch (error) {
      res.status(401).json({ error: 'User not found' });
    }
  }
);
 
// 404 handler
router.use((req, res, next) => {
  return res.status(404).json({ message: 'Content not found!' });
});
 
// Error handler
router.use((error, req, res, next) => {
  // console.error(error.stack);
  if (error instanceof HTTPError) {
    return res.status(error.code).json({ message: error.message });
  } else {
    return res.status(500).json({ message: 'Something broke!' });
  }
});
 
export default router;
 

Front-end

/codes/expressjs/invest-app-validation/public/signup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Invest App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link" href="/signin.html">Entrar</a>
            </li>
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signup.html"
                >Cadastrar</a
              >
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container position-relative pb-5">
      <h1 class="text-center py-5 fw-bold">Cadastro de Usuário</h1>
 
      <div class="row justify-content-center">
        <form
          class="col-6 needs-validation"
          onsubmit="handleSubmit(event)"
          novalidate
        >
          <div class="mb-3">
            <label for="name" class="form-label">Nome</label>
            <input
              type="text"
              class="form-control"
              id="name"
              name="name"
              required
            />
            <div class="invalid-feedback">Informe o nome do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="email" class="form-label">Email</label>
            <input
              type="email"
              class="form-control"
              id="email"
              name="email"
              required
            />
            <div class="invalid-feedback">Informe o email do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Senha</label>
            <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
            <input
              type="password"
              class="form-control"
              id="password"
              name="password"
              minlength="8"
              required
            />
            <div class="invalid-feedback">
              Informe a senha com 8 caracteres.
            </div>
          </div>
          <div class="mb-3">
            <label for="confirmationPassword" class="form-label"
              >Confirmar Senha</label
            >
            <input
              type="password"
              class="form-control"
              id="confirmationPassword"
              name="confirmationPassword"
              required
            />
            <div class="invalid-feedback">Informe a confirmação de senha.</div>
          </div>
          <div class="mb-3">
            <input type="submit" class="btn btn-primary" value="Cadastrar" />
          </div>
        </form>
      </div>
 
      <div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
        <div
          id="liveToast"
          class="toast"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-bs-delay="4000"
        >
          <div class="toast-header">
            <strong class="me-auto"></strong>
            <button
              type="button"
              class="btn-close"
              data-bs-dismiss="toast"
              aria-label="Close"
            ></button>
          </div>
        </div>
      </div>
    </div>
 
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
      crossorigin="anonymous"
    ></script>
    <script src="js/signup.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-validation/public/signup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Invest App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link" href="/signin.html">Entrar</a>
            </li>
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signup.html"
                >Cadastrar</a
              >
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container position-relative pb-5">
      <h1 class="text-center py-5 fw-bold">Cadastro de Usuário</h1>
 
      <div class="row justify-content-center">
        <form
          class="col-6 needs-validation"
          onsubmit="handleSubmit(event)"
          novalidate
        >
          <div class="mb-3">
            <label for="name" class="form-label">Nome</label>
            <input
              type="text"
              class="form-control"
              id="name"
              name="name"
              required
            />
            <div class="invalid-feedback">Informe o nome do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="email" class="form-label">Email</label>
            <input
              type="email"
              class="form-control"
              id="email"
              name="email"
              required
            />
            <div class="invalid-feedback">Informe o email do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Senha</label>
            <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
            <input
              type="password"
              class="form-control"
              id="password"
              name="password"
              minlength="8"
              required
            />
            <div class="invalid-feedback">
              Informe a senha com 8 caracteres.
            </div>
          </div>
          <div class="mb-3">
            <label for="confirmationPassword" class="form-label"
              >Confirmar Senha</label
            >
            <input
              type="password"
              class="form-control"
              id="confirmationPassword"
              name="confirmationPassword"
              required
            />
            <div class="invalid-feedback">Informe a confirmação de senha.</div>
          </div>
          <div class="mb-3">
            <input type="submit" class="btn btn-primary" value="Cadastrar" />
          </div>
        </form>
      </div>
 
      <div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
        <div
          id="liveToast"
          class="toast"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-bs-delay="4000"
        >
          <div class="toast-header">
            <strong class="me-auto"></strong>
            <button
              type="button"
              class="btn-close"
              data-bs-dismiss="toast"
              aria-label="Close"
            ></button>
          </div>
        </div>
      </div>
    </div>
 
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
      crossorigin="anonymous"
    ></script>
    <script src="js/signup.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-validation/public/js/signup.js
import API from './services/api.js';
 
const form = document.querySelector('form');
 
window.handleSubmit = handleSubmit;
 
async function handleSubmit(event) {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    const { email, message } = await API.create('/users', user, false);
 
    if (email) {
      location.href = '/signin.html';
    } else if (message === 'Email already exists') {
      const error = 'Email já cadastrado';
 
      const emailError = document.querySelector('#email + .invalid-feedback');
 
      emailError.textContent = error;
 
      form.email.setCustomValidity(error);
 
      form.email.classList.add('is-invalid');
    } else {
      showToast('Error no cadastro');
    }
  } else {
    form.classList.add('was-validated');
  }
}
 
form.email.oninput = () => {
  form.email.classList.remove('is-invalid');
 
  const confirmationPasswordError = document.querySelector(
    '#email + .invalid-feedback'
  );
 
  confirmationPasswordError.textContent = 'Informe o email do usuário.';
};
 
form.confirmationPassword.oninput = () => {
  const password = form.password.value;
 
  const confirmationPassword = form.confirmationPassword.value;
 
  if (password !== confirmationPassword) {
    const error = 'As senhas não são iguais.';
 
    const confirmationPasswordError = document.querySelector(
      '#confirmationPassword + .invalid-feedback'
    );
 
    confirmationPasswordError.textContent = error;
 
    form.confirmationPassword.setCustomValidity(error);
  } else {
    form.confirmationPassword.setCustomValidity('');
  }
};
 
function showToast(message) {
  document.querySelector('.toast-header strong').innerText = message;
  const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
  toast.show();
}
 
/codes/expressjs/invest-app-validation/public/js/signup.js
import API from './services/api.js';
 
const form = document.querySelector('form');
 
window.handleSubmit = handleSubmit;
 
async function handleSubmit(event) {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    const { email, message } = await API.create('/users', user, false);
 
    if (email) {
      location.href = '/signin.html';
    } else if (message === 'Email already exists') {
      const error = 'Email já cadastrado';
 
      const emailError = document.querySelector('#email + .invalid-feedback');
 
      emailError.textContent = error;
 
      form.email.setCustomValidity(error);
 
      form.email.classList.add('is-invalid');
    } else {
      showToast('Error no cadastro');
    }
  } else {
    form.classList.add('was-validated');
  }
}
 
form.email.oninput = () => {
  form.email.classList.remove('is-invalid');
 
  const confirmationPasswordError = document.querySelector(
    '#email + .invalid-feedback'
  );
 
  confirmationPasswordError.textContent = 'Informe o email do usuário.';
};
 
form.confirmationPassword.oninput = () => {
  const password = form.password.value;
 
  const confirmationPassword = form.confirmationPassword.value;
 
  if (password !== confirmationPassword) {
    const error = 'As senhas não são iguais.';
 
    const confirmationPasswordError = document.querySelector(
      '#confirmationPassword + .invalid-feedback'
    );
 
    confirmationPasswordError.textContent = error;
 
    form.confirmationPassword.setCustomValidity(error);
  } else {
    form.confirmationPassword.setCustomValidity('');
  }
};
 
function showToast(message) {
  document.querySelector('.toast-header strong').innerText = message;
  const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
  toast.show();
}
 

/codes/expressjs/invest-app-validation/public/signin.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Invest App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signin.html"
                >Entrar</a
              >
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/signup.html">Cadastrar</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container position-relative pb-5">
      <h1 class="text-center py-5 fw-bold">Entrar</h1>
 
      <div class="row justify-content-center">
        <form
          class="col-6 needs-validation"
          onsubmit="handleSubmit(event)"
          novalidate
        >
          <div class="mb-3">
            <label for="email" class="form-label">Email</label>
            <input
              type="email"
              class="form-control"
              id="email"
              name="email"
              required
            />
            <div class="invalid-feedback">Informe o email do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Senha</label>
            <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
            <input
              type="password"
              class="form-control"
              id="password"
              name="password"
              minlength="8"
              required
            />
            <div class="invalid-feedback">
              Informe a senha com 8 caracteres.
            </div>
          </div>
          <div class="mb-3">
            <input type="submit" class="btn btn-primary" value="Entrar" />
          </div>
        </form>
      </div>
 
      <div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
        <div
          id="liveToast"
          class="toast"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-bs-delay="4000"
        >
          <div class="toast-header">
            <strong class="me-auto"></strong>
            <button
              type="button"
              class="btn-close"
              data-bs-dismiss="toast"
              aria-label="Close"
            ></button>
          </div>
        </div>
      </div>
    </div>
 
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
      crossorigin="anonymous"
    ></script>
    <script src="js/signin.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-validation/public/signin.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cadastro de Usuário</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
  </head>
 
  <body>
    <nav class="navbar navbar-expand-lg bg-light">
      <div class="container">
        <a class="navbar-brand" href="#">Invest App</a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="/signin.html"
                >Entrar</a
              >
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/signup.html">Cadastrar</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container position-relative pb-5">
      <h1 class="text-center py-5 fw-bold">Entrar</h1>
 
      <div class="row justify-content-center">
        <form
          class="col-6 needs-validation"
          onsubmit="handleSubmit(event)"
          novalidate
        >
          <div class="mb-3">
            <label for="email" class="form-label">Email</label>
            <input
              type="email"
              class="form-control"
              id="email"
              name="email"
              required
            />
            <div class="invalid-feedback">Informe o email do usuário.</div>
          </div>
          <div class="mb-3">
            <label for="password" class="form-label">Senha</label>
            <!-- pattern="(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8}" -->
            <input
              type="password"
              class="form-control"
              id="password"
              name="password"
              minlength="8"
              required
            />
            <div class="invalid-feedback">
              Informe a senha com 8 caracteres.
            </div>
          </div>
          <div class="mb-3">
            <input type="submit" class="btn btn-primary" value="Entrar" />
          </div>
        </form>
      </div>
 
      <div class="position-absolute top-0 end-0 p-3" style="z-index: 11">
        <div
          id="liveToast"
          class="toast"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-bs-delay="4000"
        >
          <div class="toast-header">
            <strong class="me-auto"></strong>
            <button
              type="button"
              class="btn-close"
              data-bs-dismiss="toast"
              aria-label="Close"
            ></button>
          </div>
        </div>
      </div>
    </div>
 
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
      crossorigin="anonymous"
    ></script>
    <script src="js/signin.js" type="module"></script>
  </body>
</html>
 
/codes/expressjs/invest-app-validation/public/js/signin.js
import API from './services/api.js';
import Auth from './lib/auth.js';
 
const form = document.querySelector('form');
 
window.handleSubmit = handleSubmit;
 
async function handleSubmit(event) {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    const { auth, token } = await API.create('/signin', user, false);
 
    if (auth) {
      Auth.signin(token);
    } else {
      showToast('Error no login');
    }
  } else {
    form.classList.add('was-validated');
  }
}
 
function showToast(message) {
  document.querySelector('.toast-header strong').innerText = message;
  const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
  toast.show();
}
 
/codes/expressjs/invest-app-validation/public/js/signin.js
import API from './services/api.js';
import Auth from './lib/auth.js';
 
const form = document.querySelector('form');
 
window.handleSubmit = handleSubmit;
 
async function handleSubmit(event) {
  event.preventDefault();
 
  if (form.checkValidity()) {
    const user = Object.fromEntries(new FormData(form));
 
    const { auth, token } = await API.create('/signin', user, false);
 
    if (auth) {
      Auth.signin(token);
    } else {
      showToast('Error no login');
    }
  } else {
    form.classList.add('was-validated');
  }
}
 
function showToast(message) {
  document.querySelector('.toast-header strong').innerText = message;
  const toast = new bootstrap.Toast(document.querySelector('#liveToast'));
  toast.show();
}
 

Editar esta página