send all files

This commit is contained in:
Rhuan 2024-07-21 00:15:14 -03:00
parent 1e8cda0139
commit c9464c4a6f
72 changed files with 6335 additions and 0 deletions

138
README.md
View File

@ -1,2 +1,140 @@
# NAPPDesafio
## Cadastro de Produto
Precisamos criar uma aplicação, em que iremos realizar o cadastro de Produto, Estoque e
Preço.
Essa aplicação precisa ter um frontend em react em que podemos criar, atualizar, listar e
deletar produtos.
As informações enviadas para essa API devem ser armazenadas em um banco de dados
de sua preferência.
Para o cadastro ser completo precisamos receber via API as seguintes informações:
- Código
- Nome
- Estoque
- Estoque Total
- Estoque de Corte
- Preço De
- Preço Por
No banco de dados precisamos ter no mínimo seguintes informações de forma obrigatória:
- Código
- Nome
- Estoque
- Estoque Total
- Estoque de Corte
- Estoque Disponível
- Preço De
- Preço Por
OBS: fique à vontade para acrescentar mais campos caso necessário
Algumas regras que devem ser seguidas:
- O código precisa ser único;
- Ao realizar um POST e/ou PUT deve ser enviado apenas o "Estoque Total" e
"Estoque de Corte", mas a aplicação precisa calcular e salvar no "Estoque
Disponível" o resultado do "Estoque Total" - "Estoque de Corte";
- O Preço De não pode ser inferior ao Preço Por;
- Precisamos "auditar" as informações, ou seja, precisamos ver data e hora das
transações no banco de dados;
A aplicação precisa estar de forma fácil para realizarmos o setup, de preferência Docker.
A API não precisa ter autenticação, mas fique à vontade caso queira implementar.
# Projeto de Rede Social Simples com Next.js e Golang
Este é um projeto de uma aplicação de um CRUD simples desenvolvida com React.JS para o frontend e Golang (utilizando o framework Fiber) para o backend. O banco de dados utilizado é o Postgres. O propósito deste projeto é demonstrar minhas habilidades de desenvolvimento web e back-end.
## Como Rodar o Projeto
### Pré-requisitos
Antes de começar, certifique-se de ter o Docker e o Docker Compose instalados em sua máquina.
### Passo 1: Clone o Repositório
Clone este repositório para a sua máquina usando o seguinte comando:
```bash
git clone https://gitea.pradoc.com.br/Rhuan/NAPPDesafio.git
```
### Passo 2: Navegue até o Diretório do Projeto
Navegue até o diretório do projeto recém-clonado:
```bash
cd NAPPDesafio
```
### Passo 3: Configure as Variáveis de Ambiente
Crie um arquivo `.env` no diretório raiz do projeto e configure as variáveis de ambiente necessárias. Você pode usar o arquivo `.env.example` como referência.
Para api preencha:
#### .env
```env
PORT=3003
JWT_KEY=t35t4nd0
DATABASE_URL=postgresql://postgres:postgres@postgres-db-api:5432/postgres?sslmode=disable&pool_max_conns=10
```
Crie um arquivo `.env` no diretório raiz do projeto e configure as variáveis de ambiente necessárias. Você pode usar o arquivo `.env.example` como referência.
Para spa-insta preencha:
#### .env.local
```env
VITE_APP_API_URL=http://127.0.0.1:3003
```
### Passo 4: Navegue até o Diretório do Projeto
Navegue até o diretório do projeto `/etc`:
```bash
cd etc/
```
### Passo 5: Inicie o Projeto com Docker Compose
Use o Docker Compose para construir e iniciar os contêineres do frontend, backend e banco de dados:
```bash
docker compose up --build
```
Isso irá construir as imagens Docker e iniciar os serviços. Aguarde até que todos os serviços estejam em execução.
### Passo 6: Acesse a Aplicação
Após a conclusão dos passos anteriores, a aplicação deve estar em execução. Você pode acessá-la em seu navegador em:
```url
http://localhost:3000
```
## Funcionalidades Principais
- **Login**: Fazer login na aplicação.
- **CRUD Produtos**: CRUD básico de produtos.
## Tecnologias Utilizadas
- [React](https://react.dev/) - Framework React para o frontend.
- [Golang](https://golang.org) - Linguagem de programação para o backend.
- [Fiber](https://gofiber.io) - Framework web para Golang.
- [PostgreSQL](https://www.postgresql.org/) - Banco de dados PostgreSQL.
- [sqlc](https://sqlc.dev/) - Gerador de queries Sqlc.
## Contribuição
Este projeto foi criado para fins de demonstração de habilidades. Se você deseja contribuir, sinta-se à vontade para abrir um problema ou enviar uma solicitação pull.
## Licença
Este projeto é licenciado sob a licença MIT. Consulte o arquivo [LICENSE](LICENSE) para obter mais detalhes.

3
api/.exemple.env Normal file
View File

@ -0,0 +1,3 @@
PORT=
JWT_KEY=
DATABASE_URL=

21
api/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
.env

16
api/Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM golang:latest
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY .env ./
COPY . .
RUN go build -o main
EXPOSE 3003
CMD ["./main"]

1
api/README.md Normal file
View File

@ -0,0 +1 @@
# api

View File

@ -0,0 +1,24 @@
package authmodule
import (
authdto "api/application/auth/dto"
"api/models/api"
)
type Controller interface {
Authorization(data *authdto.AuthDto) (*api.Response, int, error)
}
type controller struct {
AuthService
}
func newController(service AuthService) Controller {
return &controller{
AuthService: service,
}
}
func (c *controller) Authorization(data *authdto.AuthDto) (*api.Response, int, error) {
return c.AuthService.Authorization(data)
}

View File

@ -0,0 +1,34 @@
package authdto
import (
"api/libs/logger"
"errors"
"github.com/go-playground/validator/v10"
)
type AuthDto struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
func (d *AuthDto) Validate() error {
validate := validator.New()
err := validate.Struct(d)
if err != nil {
// this check is only needed when your code could produce
// an invalid value for validation such as interface with nil
// value most including myself do not usually have code like this.
if _, ok := err.(*validator.InvalidValidationError); ok {
logger.Development.Info(err.Error())
}
for _, e := range err.(validator.ValidationErrors) {
err = errors.New(e.Field() + " " + e.Tag())
}
}
return err
}

View File

@ -0,0 +1,10 @@
package authmodule
import "github.com/gofiber/fiber/v2"
// apply routes in app fiber, with controllers and services defined
func AuthModuleDecorator(app *fiber.App) {
s := newService()
c := newController(s)
newRoutes(c, app)
}

View File

@ -0,0 +1,51 @@
package authmodule
import (
authdto "api/application/auth/dto"
"api/libs/logger"
"api/models/api"
"github.com/gofiber/fiber/v2"
)
func newRoutes(c Controller, app *fiber.App) {
app.Post("/authorization", authorization(c))
}
func authorization(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
auth := &authdto.AuthDto{}
err := c.BodyParser(auth)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
err = auth.Validate()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
response, statusCode, err := controller.Authorization(auth)
if err != nil {
logger.Production.Info(err.Error())
c.Status(statusCode)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}

View File

@ -0,0 +1,69 @@
package authmodule
import (
authdto "api/application/auth/dto"
"api/libs/jwt"
"api/libs/postgres"
"api/models/api"
"api/models/users"
"context"
"github.com/alexedwards/argon2id"
"github.com/gofiber/fiber/v2"
)
type AuthService interface {
Authorization(data *authdto.AuthDto) (*api.Response, int, error)
}
type authService struct {
userRepository *users.Queries
ctx context.Context
}
func newService() AuthService {
return &authService{
userRepository: users.New(postgres.Pool),
ctx: context.Background(),
}
}
func (a *authService) Authorization(data *authdto.AuthDto) (*api.Response, int, error) {
userL, err := a.userRepository.GetUser(a.ctx, data.Username)
if err != nil {
return &api.Response{Error: true, ErrorMessage: err.Error()}, fiber.StatusUnauthorized, err
}
match, err := argon2id.ComparePasswordAndHash(data.Password, userL.Password)
if err != nil {
return &api.Response{Error: true, ErrorMessage: err.Error()}, fiber.StatusUnauthorized, err
}
if match {
dic := map[string]interface{}{
"id": userL.User,
}
token, err := jwt.EncodeJwt(dic)
if err != nil {
return &api.Response{Error: true, ErrorMessage: err.Error()}, fiber.StatusInternalServerError, err
}
payload := struct {
User struct {
Id string `json:"id"`
Username string `json:"username"`
} `json:"user"`
Token string `json:"token"`
}{}
payload.User.Username = userL.Username
payload.User.Id = userL.User
payload.Token = token
return &api.Response{Error: false, Payload: payload}, fiber.StatusOK, err
}
return nil, fiber.StatusUnauthorized, nil
}

View File

@ -0,0 +1,45 @@
package productsmodule
import (
productdto "api/application/products/dto"
"api/models/api"
)
// Controller defines the methods to be exposed in Controller layer
type Controller interface {
CreateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error)
UpdateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error)
VerifyCode(data *productdto.ValidateCodeDto) (*api.Response, int, error)
GetProducts() (*api.Response, int, error)
DeleteProduct(data *productdto.DeleteProductDto) (*api.Response, int, error)
}
type controller struct {
ProductsService
}
func newController(service ProductsService) Controller {
return &controller{
ProductsService: service,
}
}
func (c *controller) CreateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error) {
return c.ProductsService.CreateProduct(userId, data)
}
func (c *controller) UpdateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error) {
return c.ProductsService.UpdateProduct(userId, data)
}
func (c *controller) VerifyCode(data *productdto.ValidateCodeDto) (*api.Response, int, error) {
return c.ProductsService.VerifyCode(data)
}
func (c *controller) GetProducts() (*api.Response, int, error) {
return c.ProductsService.GetProducts()
}
func (c *controller) DeleteProduct(data *productdto.DeleteProductDto) (*api.Response, int, error) {
return c.ProductsService.DeleteProduct(data)
}

View File

@ -0,0 +1,28 @@
package productdto
import (
"api/libs/logger"
"errors"
"github.com/go-playground/validator/v10"
)
type DeleteProductDto struct {
Codigo string `json:"codigo" validate:"required"`
}
func (d *DeleteProductDto) Validate() error {
validate := validator.New()
err := validate.Struct(d)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
logger.Development.Info(err.Error())
}
for _, e := range err.(validator.ValidationErrors) {
err = errors.New(e.Field() + " " + e.Tag())
}
}
return err
}

View File

@ -0,0 +1,38 @@
package productdto
import (
"api/libs/logger"
"errors"
"github.com/go-playground/validator/v10"
)
type ProductDto struct {
Nome string `json:"nome" validate:"required"`
Codigo string `json:"codigo" validate:"required"`
EstoqueTotal int64 `json:"estoqueTotal" validate:"required"`
EstoqueCorte int64 `json:"estoqueCorte" validate:"required"`
PrecoDe float32 `json:"precoDe" validate:"required"`
PrecoPor float32 `json:"precoPor" validate:"required"`
}
func (d *ProductDto) Validate() error {
validate := validator.New()
err := validate.Struct(d)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
logger.Development.Info(err.Error())
}
for _, e := range err.(validator.ValidationErrors) {
err = errors.New(e.Field() + " " + e.Tag())
}
}
if d.PrecoPor > d.PrecoDe {
err = errors.New(`o "preço de" não pode ser inferior ao "preço por"`)
}
return err
}

View File

@ -0,0 +1,28 @@
package productdto
import (
"api/libs/logger"
"errors"
"github.com/go-playground/validator/v10"
)
type ValidateCodeDto struct {
Codigo string `json:"codigo" validate:"required"`
}
func (d *ValidateCodeDto) Validate() error {
validate := validator.New()
err := validate.Struct(d)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
logger.Development.Info(err.Error())
}
for _, e := range err.(validator.ValidationErrors) {
err = errors.New(e.Field() + " " + e.Tag())
}
}
return err
}

View File

@ -0,0 +1,10 @@
package productsmodule
import "github.com/gofiber/fiber/v2"
// apply routes in app fiber, with controllers and services defined
func ProductstModuleDecorator(app *fiber.App) {
s := newService()
c := newController(s)
newRoutes(c, app)
}

View File

@ -0,0 +1,193 @@
package productsmodule
import (
productdto "api/application/products/dto"
"api/libs/jwt"
"api/libs/logger"
"api/models/api"
"github.com/gofiber/fiber/v2"
)
func newRoutes(c Controller, app *fiber.App) {
app.Post("/product", jwt.JwtProtected(), createProduct(c))
app.Put("/product", jwt.JwtProtected(), updateProduct(c))
app.Post("/product/validate", jwt.JwtProtected(), validateCode(c))
app.Get("/products", jwt.JwtProtected(), getProducts(c))
app.Delete("/product", jwt.JwtProtected(), deleteProduct(c))
}
func getProducts(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
response, statusCode, err := controller.GetProducts()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusInternalServerError)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}
func createProduct(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
userId := jwt.DecodeJwtSingleKey(c, "id").(string)
product := &productdto.ProductDto{}
err := c.BodyParser(product)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
err = product.Validate()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
response, statusCode, err := controller.CreateProduct(userId, product)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusInternalServerError)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}
func updateProduct(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
userId := jwt.DecodeJwtSingleKey(c, "id").(string)
product := &productdto.ProductDto{}
err := c.BodyParser(product)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
err = product.Validate()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
response, statusCode, err := controller.UpdateProduct(userId, product)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusInternalServerError)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}
func validateCode(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
codeValidate := &productdto.ValidateCodeDto{}
err := c.BodyParser(codeValidate)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
err = codeValidate.Validate()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
response, statusCode, err := controller.VerifyCode(codeValidate)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusInternalServerError)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}
func deleteProduct(controller Controller) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
delete := &productdto.DeleteProductDto{}
err := c.BodyParser(delete)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
err = delete.Validate()
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusBadRequest)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
response, statusCode, err := controller.DeleteProduct(delete)
if err != nil {
logger.Production.Info(err.Error())
c.Status(fiber.StatusInternalServerError)
return c.JSON(api.Response{
Error: true,
ErrorMessage: err.Error(),
})
}
return c.Status(statusCode).JSON(response)
}
}

View File

@ -0,0 +1,112 @@
package productsmodule
import (
productdto "api/application/products/dto"
"api/libs/logger"
"api/libs/postgres"
"api/models/api"
"api/models/products"
"context"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
)
type ProductsService interface {
CreateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error)
UpdateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error)
VerifyCode(data *productdto.ValidateCodeDto) (*api.Response, int, error)
GetProducts() (*api.Response, int, error)
DeleteProduct(data *productdto.DeleteProductDto) (*api.Response, int, error)
}
type productsService struct {
productsRepository *products.Queries
ctx context.Context
}
func newService() ProductsService {
return &productsService{
productsRepository: products.New(postgres.Pool),
ctx: context.Background(),
}
}
func (p *productsService) CreateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error) {
estoqueDisponivel := data.EstoqueTotal - data.EstoqueCorte
newProduct, err := p.productsRepository.CreateProduct(p.ctx, userId, products.CreateProductParams{
Nome: data.Nome,
Codigo: data.Codigo,
EstoqueTotal: data.EstoqueTotal,
EstoqueCorte: data.EstoqueCorte,
EstoqueDisponivel: estoqueDisponivel,
PrecoDe: data.PrecoDe,
PrecoPor: data.PrecoPor,
})
if err != nil {
logger.Production.Info(err.Error())
return &api.Response{Error: true, ErrorMessage: err.Error(), Payload: "error insert product"}, fiber.StatusInternalServerError, err
}
return &api.Response{Error: false, Payload: newProduct}, fiber.StatusOK, nil
}
func (p *productsService) VerifyCode(data *productdto.ValidateCodeDto) (*api.Response, int, error) {
_, err := p.productsRepository.GetProduct(p.ctx, data.Codigo)
if err != nil {
if err == pgx.ErrNoRows {
return &api.Response{Error: false, Payload: false}, fiber.StatusOK, nil
}
logger.Production.Info(err.Error())
return &api.Response{Error: true, ErrorMessage: err.Error(), Payload: "error verify code product"}, fiber.StatusInternalServerError, err
}
return &api.Response{Error: false, Payload: true}, fiber.StatusOK, nil
}
func (p *productsService) UpdateProduct(userId string, data *productdto.ProductDto) (*api.Response, int, error) {
estoqueDisponivel := data.EstoqueTotal - data.EstoqueCorte
err := p.productsRepository.UpdateProduct(p.ctx, userId, products.UpdateProductParams{
Nome: data.Nome,
Codigo: data.Codigo,
EstoqueTotal: data.EstoqueTotal,
EstoqueCorte: data.EstoqueCorte,
EstoqueDisponivel: estoqueDisponivel,
PrecoDe: data.PrecoDe,
PrecoPor: data.PrecoPor,
})
if err != nil {
logger.Production.Info(err.Error())
return &api.Response{Error: true, ErrorMessage: err.Error(), Payload: "error update product"}, fiber.StatusInternalServerError, err
}
return &api.Response{Error: false, Payload: nil}, fiber.StatusOK, nil
}
func (p *productsService) GetProducts() (*api.Response, int, error) {
products, err := p.productsRepository.ListProducts(p.ctx)
if err != nil {
logger.Production.Info(err.Error())
return &api.Response{Error: true, ErrorMessage: err.Error(), Payload: "error get products"}, fiber.StatusInternalServerError, err
}
return &api.Response{Error: false, Payload: products}, fiber.StatusOK, nil
}
func (p *productsService) DeleteProduct(data *productdto.DeleteProductDto) (*api.Response, int, error) {
err := p.productsRepository.DeleteProduct(p.ctx, data.Codigo)
if err != nil {
logger.Production.Info(err.Error())
return &api.Response{Error: true, ErrorMessage: err.Error(), Payload: "error delete product"}, fiber.StatusInternalServerError, err
}
return &api.Response{Error: false, Payload: nil}, fiber.StatusOK, nil
}

55
api/go.mod Normal file
View File

@ -0,0 +1,55 @@
module api
go 1.21.0
require (
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
github.com/go-playground/validator/v10 v10.15.3
github.com/gofiber/fiber/v2 v2.49.1
github.com/jackc/pgx/v5 v5.6.0
go.mongodb.org/mongo-driver v1.12.1
go.uber.org/zap v1.25.0
)
require (
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx v3.6.2+incompatible // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
require (
github.com/gofiber/jwt/v3 v3.3.10
github.com/golang-jwt/jwt/v4 v4.5.0
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/joho/godotenv v1.5.1
github.com/klauspost/compress v1.16.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.49.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)

175
api/go.sum Normal file
View File

@ -0,0 +1,175 @@
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY5mJ0fVKbk31NVhlgi0yrKm51Pq/I5wcz4=
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/gofiber/fiber/v2 v2.45.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
github.com/gofiber/fiber/v2 v2.49.1 h1:0W2DRWevSirc8pJl4o8r8QejDR8TV6ZUCawHxwbIdOk=
github.com/gofiber/fiber/v2 v2.49.1/go.mod h1:nPUeEBUeeYGgwbDm59Gp7vS8MDyScL6ezr/Np9A13WU=
github.com/gofiber/jwt/v3 v3.3.10 h1:0bpWtFKaGepjwYTU4efHfy0o+matSqZwTxGMo5a+uuc=
github.com/gofiber/jwt/v3 v3.3.10/go.mod h1:GJorFVaDyfMPSK9RB8RG4NQ3s1oXKTmYaoL/ny08O1A=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE=
github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,62 @@
package database
import (
"api/libs/logger"
"api/libs/postgres"
"api/models/users"
"context"
"os"
"time"
"github.com/alexedwards/argon2id"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
func Init() {
ensureTableUsers()
ensureTableProducts()
usersRepository := users.New(postgres.Pool)
var ctx = context.Background()
hash, err := argon2id.CreateHash("admin", argon2id.DefaultParams)
if err != nil {
logger.Production.Error("error generate password hash")
}
_, err = usersRepository.GetUser(ctx, "admin")
if err != nil {
if err.Error() == pgx.ErrNoRows.Error() {
usersRepository.CreateUser(ctx, users.CreateUserParams{
User: uuid.New().String(),
CreatedAt: uint64(time.Now().Unix()),
Username: "admin",
Password: hash,
})
logger.Production.Info("Create user admin into database")
} else {
logger.Production.Error(err.Error())
os.Exit(1)
}
}
}
func ensureTableUsers() {
if _, err := postgres.Pool.Exec(context.Background(), CreateUsersTableIfNotExists); err != nil {
logger.Production.Error(err.Error())
os.Exit(1)
}
logger.Production.Info("users exists")
}
func ensureTableProducts() {
if _, err := postgres.Pool.Exec(context.Background(), CreateProductsTableIfNotExists); err != nil {
logger.Production.Error(err.Error())
os.Exit(1)
}
logger.Production.Info("products exists")
}

View File

@ -0,0 +1,29 @@
package database
var CreateUsersTableIfNotExists = `
CREATE TABLE IF NOT EXISTS users (
"user" UUID NOT NULL,
created_at BIGINT NOT NULL,
updated_at BIGINT,
username text NOT NULL,
"password" text NOT NULL,
PRIMARY KEY ("user")
)`
var CreateProductsTableIfNotExists = `
CREATE TABLE IF NOT EXISTS products (
created_by UUID NOT NULL,
created_at BIGINT NOT NULL,
updated_by UUID,
updated_at BIGINT,
nome text NOT NULL,
codigo text NOT NULL,
estoque_total BIGINT NOT NULL,
estoque_corte BIGINT NOT NULL,
estoque_disponivel BIGINT NOT NULL,
preco_de NUMERIC NOT NULL,
preco_por NUMERIC NOT NULL,
PRIMARY KEY (codigo)
)`

View File

@ -0,0 +1,22 @@
package variable
import (
"log"
"os"
"github.com/joho/godotenv"
)
// use godot package to load/read the .env file and
// return the value of the key
func GetEnvVariable(key string) string {
// load .env file
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("Error loading .env file")
}
return os.Getenv(key)
}

74
api/libs/jwt/jwt.go Normal file
View File

@ -0,0 +1,74 @@
package jwt
import (
"api/helpers/variable"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v4"
jwtMiddleware "github.com/gofiber/jwt/v3"
)
func jwtError(c *fiber.Ctx, err error) error {
// Return status 401 and failed authentication error.
if err.Error() == "Missing or malformed JWT" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"err": true,
"msg": err.Error(),
})
}
// Return status 401 and failed authentication error.
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"err": true,
"msg": err.Error(),
})
}
func JwtProtected() func(*fiber.Ctx) error {
config := jwtMiddleware.Config{
SigningKey: []byte(variable.GetEnvVariable("JWT_KEY")),
ContextKey: "authorization",
ErrorHandler: jwtError,
}
return jwtMiddleware.New(config)
}
func GetClaims(c *fiber.Ctx) jwt.MapClaims {
// Parses the JWT used to secure authorized access to private routes
token := c.Locals("authorization").(*jwt.Token)
claims := token.Claims.(jwt.MapClaims)
// Forwards the claims further to the function that is using them
return claims
}
func DecodeJwtSingleKey(c *fiber.Ctx, k string) interface{} {
claims := GetClaims(c)
return claims[k]
}
func EncodeJwt(dictionary map[string]interface{}) (string, error) {
// Create token
token := jwt.New(jwt.SigningMethodHS256)
// Set claims
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = time.Now().Add(time.Hour * 12).Unix()
// Basic claims are merged with the dictionary provided
for key, value := range dictionary {
claims[key] = value
}
// Generate encoded token and send it as response
encodedToken, err := token.SignedString([]byte(variable.GetEnvVariable("JWT_KEY")))
return encodedToken, err
}

24
api/libs/logger/logger.go Normal file
View File

@ -0,0 +1,24 @@
package logger
import "go.uber.org/zap"
var (
Development *zap.Logger
Production *zap.Logger
)
func InitializeLogger() {
var err interface{}
Development, err = zap.NewDevelopment()
if err != nil {
panic(0)
}
Production, err = zap.NewProduction()
if err != nil {
panic(0)
}
}

View File

@ -0,0 +1,25 @@
package postgres
import (
"api/helpers/variable"
"api/libs/logger"
"context"
"os"
"github.com/jackc/pgx/v5/pgxpool"
)
var Pool *pgxpool.Pool
func InitializeDatabaseConnection() {
poolConfig, err := pgxpool.ParseConfig(variable.GetEnvVariable("DATABASE_URL"))
Pool, err = pgxpool.NewWithConfig(context.Background(), poolConfig)
if err != nil {
logger.Development.Info(err.Error())
os.Exit(1)
}
logger.Development.Info("Connected to Postgres database")
}

41
api/main.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
authmodule "api/application/auth"
productsmodule "api/application/products"
"api/helpers/database"
"api/helpers/variable"
"api/libs/logger"
"api/libs/postgres"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/helmet"
loggerFiber "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
logger.InitializeLogger()
postgres.InitializeDatabaseConnection()
database.Init()
app := fiber.New()
app.Use(recover.New())
app.Use(helmet.New())
app.Use(cors.New(cors.ConfigDefault))
app.Use(loggerFiber.New())
authmodule.AuthModuleDecorator(app)
productsmodule.ProductstModuleDecorator(app)
app.Listen(":" + variable.GetEnvVariable("PORT"))
}

View File

@ -0,0 +1,7 @@
package api
type Response struct {
Error bool `json:"error"`
ErrorMessage string `json:"errorMessage"`
Payload interface{} `json:"payload"`
}

View File

@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
package products
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@ -0,0 +1,15 @@
package products
type Product struct {
CreatedBy string `json:"createdBy"`
CreatedAt uint64 `json:"createdAt"`
UpdatedBy *string `json:"updatedBy"`
UpdatedAt *uint64 `json:"updatedAt"`
Nome string `json:"nome"`
Codigo string `json:"codigo"`
EstoqueTotal int64 `json:"estoqueTotal"`
EstoqueCorte int64 `json:"estoqueCorte"`
EstoqueDisponivel int64 `json:"estoqueDisponivel"`
PrecoDe float32 `json:"precoDe"`
PrecoPor float32 `json:"precoPor"`
}

View File

@ -0,0 +1,174 @@
package products
import (
"context"
"time"
)
const createProduct = `-- name: CreateProduct :one
INSERT INTO products (
created_at, created_by, updated_at, updated_by, nome, codigo, estoque_total, estoque_corte, estoque_disponivel, preco_de, preco_por
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
)
RETURNING created_by, created_at, updated_by, updated_at, nome, codigo, estoque_total, estoque_corte, estoque_disponivel, preco_de, preco_por
`
type CreateProductParams struct {
CreatedAt uint64
CreatedBy string
UpdatedAt uint64
UpdatedBy string
Nome string
Codigo string
EstoqueTotal int64
EstoqueCorte int64
EstoqueDisponivel int64
PrecoDe float32
PrecoPor float32
}
func (q *Queries) CreateProduct(ctx context.Context, userId string, arg CreateProductParams) (Product, error) {
t := time.Now().Unix()
row := q.db.QueryRow(ctx, createProduct,
t,
userId,
nil,
nil,
arg.Nome,
arg.Codigo,
arg.EstoqueTotal,
arg.EstoqueCorte,
arg.EstoqueDisponivel,
arg.PrecoDe,
arg.PrecoPor,
)
var i Product
err := row.Scan(
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedBy,
&i.UpdatedAt,
&i.Nome,
&i.Codigo,
&i.EstoqueTotal,
&i.EstoqueCorte,
&i.EstoqueDisponivel,
&i.PrecoDe,
&i.PrecoPor,
)
return i, err
}
const getProduct = `-- name: GetProduct :one
SELECT created_by, created_at, updated_by, updated_at, nome, codigo, estoque_total, estoque_corte, estoque_disponivel, preco_de, preco_por FROM products
WHERE codigo = $1 LIMIT 1
`
func (q *Queries) GetProduct(ctx context.Context, codigo string) (Product, error) {
row := q.db.QueryRow(ctx, getProduct, codigo)
var i Product
err := row.Scan(
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedBy,
&i.UpdatedAt,
&i.Nome,
&i.Codigo,
&i.EstoqueTotal,
&i.EstoqueCorte,
&i.EstoqueDisponivel,
&i.PrecoDe,
&i.PrecoPor,
)
return i, err
}
const listProducts = `-- name: ListProducts :many
SELECT created_by, created_at, updated_by, updated_at, nome, codigo, estoque_total, estoque_corte, estoque_disponivel, preco_de, preco_por FROM products
ORDER BY created_at
`
func (q *Queries) ListProducts(ctx context.Context) ([]Product, error) {
rows, err := q.db.Query(ctx, listProducts)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Product
for rows.Next() {
var i Product
if err := rows.Scan(
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedBy,
&i.UpdatedAt,
&i.Nome,
&i.Codigo,
&i.EstoqueTotal,
&i.EstoqueCorte,
&i.EstoqueDisponivel,
&i.PrecoDe,
&i.PrecoPor,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateProduct = `-- name: UpdateProduct :exec
UPDATE products
SET
updated_at = $2,
updated_by = $3,
nome = $4,
estoque_total = $5,
estoque_corte = $6,
estoque_disponivel = $7,
preco_de = $8,
preco_por = $9
WHERE codigo = $1
`
type UpdateProductParams struct {
UpdatedAt uint64
UpdatedBy string
Nome string
Codigo string
EstoqueTotal int64
EstoqueCorte int64
EstoqueDisponivel int64
PrecoDe float32
PrecoPor float32
}
func (q *Queries) UpdateProduct(ctx context.Context, userId string, arg UpdateProductParams) error {
t := time.Now().Unix()
_, err := q.db.Exec(ctx, updateProduct,
arg.Codigo,
t,
userId,
arg.Nome,
arg.EstoqueTotal,
arg.EstoqueCorte,
arg.EstoqueDisponivel,
arg.PrecoDe,
arg.PrecoPor,
)
return err
}
const deleteProduct = `-- name: DeleteProduct :exec
DELETE FROM products
WHERE codigo = $1
`
func (q *Queries) DeleteProduct(ctx context.Context, codigo string) error {
_, err := q.db.Exec(ctx, deleteProduct, codigo)
return err
}

View File

@ -0,0 +1,32 @@
-- name: GetProduct :one
SELECT * FROM products
WHERE codigo = $1 LIMIT 1;
-- name: ListProducts :many
SELECT * FROM products
ORDER BY created_at;
-- name: CreateProduct :one
INSERT INTO products (
created_at, created_by, updated_at, updated_by, nome, codigo, estoque_total, estoque_corte, estoque_disponivel, preco_de, preco_por
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
)
RETURNING *;
-- name: UpdateProduct :exec
UPDATE products
SET
updated_at = $2,
updated_by = $3,
nome = $4,
estoque_total = $5,
estoque_corte = $6,
estoque_disponivel = $7,
preco_de = $8,
preco_por = $9
WHERE codigo = $1;
-- name: DeleteProduct :exec
DELETE FROM products
WHERE codigo = $1;

View File

@ -0,0 +1,15 @@
CREATE TABLE products (
product UUID NOT NULL,
created_by UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_by UUID,
updated_at TIMESTAMP,
nome text NOT NULL,
codigo text NOT NULL,
estoque_total BIGINT NOT NULL,
estoque_corte BIGINT NOT NULL,
estoque_disponivel BIGINT NOT NULL,
preco_de NUMERIC NOT NULL,
preco_por NUMERIC NOT NULL
)

View File

@ -0,0 +1,11 @@
version: "2
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "products"
out: "./"
sql_package: "pgx/v5"

View File

@ -0,0 +1,20 @@
-- name: GetUser :one
SELECT * FROM users
WHERE username = $1 LIMIT 1;
-- name: CreateUser :one
INSERT INTO users (
"user", created_at, updated_at, username, "password"
) VALUES (
$1, $2, $3, $4, $5
)
RETURNING *;
-- name: UpdateUser :exec
UPDATE users
SET
updated_at = $2,
username = $3,
"password" = $4
WHERE "user" = $1;

View File

@ -0,0 +1,7 @@
CREATE TABLE users (
"user" UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP,
username text NOT NULL,
"password" text NOT NULL
)

View File

@ -0,0 +1,10 @@
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "users"
out: "./"
sql_package: "pgx/v5"

View File

@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
package users
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@ -0,0 +1,9 @@
package users
type User struct {
User string
CreatedAt uint64
UpdatedAt *uint64
Username string
Password string
}

View File

@ -0,0 +1,86 @@
package users
import (
"context"
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (
"user", created_at, updated_at, username, "password"
) VALUES (
$1, $2, $3, $4, $5
)
RETURNING "user", created_at, updated_at, username, password
`
type CreateUserParams struct {
User string
CreatedAt uint64
UpdatedAt *uint64
Username string
Password string
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRow(ctx, createUser,
arg.User,
arg.CreatedAt,
arg.UpdatedAt,
arg.Username,
arg.Password,
)
var i User
err := row.Scan(
&i.User,
&i.CreatedAt,
i.UpdatedAt,
&i.Username,
&i.Password,
)
return i, err
}
const getUser = `-- name: GetUser :one
SELECT "user", created_at, updated_at, username, password FROM users
WHERE username = $1 LIMIT 1
`
func (q *Queries) GetUser(ctx context.Context, username string) (User, error) {
row := q.db.QueryRow(ctx, getUser, username)
var i User
err := row.Scan(
&i.User,
&i.CreatedAt,
&i.UpdatedAt,
&i.Username,
&i.Password,
)
return i, err
}
const updateUser = `-- name: UpdateUser :exec
UPDATE users
SET
updated_at = $2,
username = $3,
"password" = $4
WHERE "user" = $1
`
type UpdateUserParams struct {
User string
UpdatedAt uint64
Username string
Password string
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
_, err := q.db.Exec(ctx, updateUser,
arg.User,
arg.UpdatedAt,
arg.Username,
arg.Password,
)
return err
}

32
etc/docker-compose.yaml Normal file
View File

@ -0,0 +1,32 @@
version: '3.8'
services:
go-api-product:
container_name: go-api-product
build:
context: ../api
restart: always
ports:
- "3003:3003"
links:
- postgres-db-api
depends_on:
- postgres-db-api
postgres-db-api:
container_name: postgres-db-api
image: postgres
environment:
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
frontend-product:
container_name: frontend-product
build:
context: ../spa
ports:
- "3000:3000"
depends_on:
- go-api-product

18
spa/.eslintrc.cjs Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

1
spa/.exemple.env Normal file
View File

@ -0,0 +1 @@
VITE_APP_API_URL=

24
spa/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
.env
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

17
spa/Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY .env ./
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

30
spa/README.md Normal file
View File

@ -0,0 +1,30 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

13
spa/index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

3502
spa/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
spa/package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "spa",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.2.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-router": "^6.25.1",
"react-router-dom": "^6.25.1",
"styled-components": "^6.1.12"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"typescript": "^5.2.2",
"vite": "^5.3.4"
}
}

1
spa/public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

42
spa/src/App.css Normal file
View File

@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

25
spa/src/App.tsx Normal file
View File

@ -0,0 +1,25 @@
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import { paths } from "./utils/routes"
import "./index.css";
import LoginUser from "./pages/authorization/LoginUser";
import Products from "./pages/products/ProductsUser";
import InsertUpdateProduct from "./pages/products/subpages/InsertUpdateProduct";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to={paths.user.login} replace />} />
<Route path={paths.user.login} element={<LoginUser />} />
<Route path={paths.user.products} element={<Products />} />
<Route path={paths.user.editProduct} element={<InsertUpdateProduct />} />
</Routes>
</BrowserRouter>
);
}
export default App;

1
spa/src/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

25
spa/src/index.css Normal file
View File

@ -0,0 +1,25 @@
*{
box-sizing: border-box;
padding: 0;
margin: 0;
}
:root {
font-family: sans-serif;
font-size: 62.5%;
}
body {
display: flex;
justify-content: center;
min-height: 100vh;
}
.error{
margin: 10px;
color: red;
}

14
spa/src/main.tsx Normal file
View File

@ -0,0 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import store from './shared/store/reducers.tsx';
import { Provider } from 'react-redux';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)

View File

@ -0,0 +1,74 @@
import { useState } from 'react';
import { Button, Form, FormGroup, Input, Label } from '../../shared/components/default';
import { setUsersSlice } from '../../shared/store/slices/usersSlice';
import LoginContainer from '../../shared/components/login';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router';
export default function LoginUser(): JSX.Element {
const dispatch = useDispatch()
const navigate = useNavigate()
const [username, setUsername] = useState<string>("")
const [password, setPassword] = useState<string>("")
const [errorP, setErrorP] = useState<boolean>(false)
const login = async () => {
const data = {
username,
password,
}
try {
const response = await fetch(import.meta.env.VITE_APP_API_URL + '/authorization', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
setErrorP(false)
const data = await response.json()
localStorage.setItem('token', data?.payload?.token)
dispatch(setUsersSlice({ ...data.payload.user }))
navigate('/products')
} else {
const data = await response.json()
if (response.status === 401) {
setErrorP(true)
}
console.error('Erro ao autenticar usuário.', data);
}
} catch (error) {
console.error('Erro na solicitação:', error);
}
}
return (
<LoginContainer>
<Form>
<h2>Login</h2>
<FormGroup>
<Label >Usuário</Label>
<Input type="text" value={username} onChange={(e) => setUsername(e.target.value.toString())} />
</FormGroup>
<FormGroup>
<Label >Senha</Label>
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value.toString())} />
{errorP && <p className="error">Senha ou Usuário inválido</p>}
</FormGroup>
<Button onClick={() => login()} >Entrar</Button>
</Form>
</LoginContainer>
);
}

View File

@ -0,0 +1,53 @@
import { useNavigate } from "react-router";
import { Button, Title } from "../../shared/components/default";
import { FlexContainer } from "../../shared/components/home";
import { ProductProps, setProductSlice } from "../../shared/store/slices/productSlice";
import { useDispatch } from "react-redux";
import { useEffect } from "react";
import TableProducts from "./components/TableProducts";
import { setProductsSlice } from "../../shared/store/slices/productsSlice";
const Products = () => {
const navigate = useNavigate()
const dispatch = useDispatch()
const getProducts = async () => {
try {
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/products`, {
headers: {
"Authorization": `Bearer ${localStorage.getItem('token')}`
},
})
if (!response.ok) throw new Error(`HTTP error status ${response.status}`)
const data = await response.json()
dispatch(setProductsSlice(data?.payload as ProductProps[]))
} catch(e) {
console.error(e)
}
}
useEffect(()=>{
getProducts()
dispatch(setProductSlice(null))
// eslint-disable-next-line react-hooks/exhaustive-deps
},[])
return (
<>
<Title>Produtos</Title>
<FlexContainer>
<Button onClick={()=> navigate("/product/edit")}>Inserir produto</Button>
</FlexContainer>
<FlexContainer>
<TableProducts/>
</FlexContainer>
</>
);
};
export default Products;

View File

@ -0,0 +1,77 @@
import { useNavigate } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import Table from "../../../shared/components/table";
import { setProductSlice } from "../../../shared/store/slices/productSlice";
import { Button } from "../../../shared/components/default";
import { RootState } from "../../../shared/store/reducers";
import { setProductsSlice } from "../../../shared/store/slices/productsSlice";
export default function TableProducts () {
const navigate = useNavigate()
const dispatch = useDispatch()
const products = useSelector((state:RootState) => state.products)
const deleteProduct = async (code: string) =>{
try {
const response = await fetch(
`${import.meta.env.VITE_APP_API_URL}/product`,
{
method:"DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({codigo:code}),
}
);
if (!response.ok) throw new Error(`Erro ao excluir producto`);
dispatch(setProductsSlice(products.filter((p)=> p.codigo !== code)))
} catch (error) {
alert(error)
}
}
return (
<>
<Table>
<thead>
<tr>
<th>Código</th>
<th>Nome</th>
<th>Estoque Total</th>
<th>Estoque de Corte</th>
<th>Estoque Disponível</th>
<th>Preço De</th>
<th>Preço Por</th>
<th>Açoes</th>
</tr>
</thead>
<tbody>
{ !!products && products.map((p,index)=>{
return(
<tr key={index}>
<td>{p.codigo}</td>
<td>{p.nome}</td>
<td>{p.estoqueTotal}</td>
<td>{p.estoqueCorte}</td>
<td>{p.estoqueDisponivel}</td>
<td>{p.precoDe}</td>
<td>{p.precoPor}</td>
<td><Button onClick={()=>{
dispatch(setProductSlice(p))
navigate("/product/edit")
}}>Alterar</Button> <Button onClick={()=> deleteProduct(p.codigo)}>Excluir</Button></td>
</tr>
)
})
}
</tbody>
</Table>
</>
);
}

View File

@ -0,0 +1,199 @@
import { useEffect, useState } from "react";
import {
Button,
Form,
FormGroup,
Input,
Label,
Title,
} from "../../../shared/components/default";
import { FlexContainer } from "../../../shared/components/home";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../../shared/store/reducers";
import { setProductSlice } from "../../../shared/store/slices/productSlice";
import { useNavigate } from "react-router";
export default function InsertUpdateProduct() {
const dispatch = useDispatch();
const navigate = useNavigate();
const product = useSelector((state: RootState) => state.product.product);
const [edit, setEdit] = useState(false);
const insertUpdateProduct = async () => {
if (product === null) return
try {
if (product.precoPor > product.precoDe) throw new Error(`o "preço de" não pode ser inferior ao "preço por"`);
if (edit === false) {
let response = await fetch(
`${import.meta.env.VITE_APP_API_URL}/product/validate`,
{
method:"POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify(product),
}
);
if (!response.ok) throw new Error(`HTTP error status ${response.status}`);
const validateData = await response.json()
if (validateData?.payload === true) throw new Error(`Código já existente`);
response = await fetch(
`${import.meta.env.VITE_APP_API_URL}/product`,
{
method:"POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify(product),
}
);
if (!response.ok) throw new Error(`HTTP error status ${response.status}`);
navigate("/products")
} else {
const response = await fetch(
`${import.meta.env.VITE_APP_API_URL}/product`,
{
method:"PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify(product),
}
);
if (!response.ok) throw new Error(`HTTP error status ${response.status}`);
navigate("/products")
}
} catch (e) {
window.alert(e)
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const alterProduct = (field: string, value: any) => {
if (!product) return
dispatch(
setProductSlice({
...product,
[field]:value
})
)
}
useEffect(() => {
if (product !== null) {
setEdit(true);
} else {
dispatch(
setProductSlice({
codigo: "",
estoqueCorte: 0,
estoqueTotal: 0,
nome: "",
precoDe: 0,
precoPor: 0,
estoqueDisponivel: 0,
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{edit === true ? (
<Title>Atualizar Produto</Title>
) : (
<Title>Inserir Produto</Title>
)}
{!!product && (
<FlexContainer>
<Form>
<FormGroup>
<Label>Código</Label>
<Input type="text" value={product.codigo} disabled={edit} onChange={(e) => alterProduct("codigo", e.target.value as string)} />
</FormGroup>
<FormGroup>
<Label>Nome</Label>
<Input type="text" value={product.nome} onChange={(e) => alterProduct("nome", e.target.value as string)} />
</FormGroup>
<FormGroup>
<Label>Estoque Total</Label>
<Input
type="number"
value={product.estoqueTotal}
onChange={(e) => {
let value = e.target.valueAsNumber
if (isNaN(value) === true) value = 0
alterProduct("estoqueTotal", value)
}}
/>
</FormGroup>
<FormGroup>
<Label>Estoque de Corte</Label>
<Input
type="number"
value={product.estoqueCorte}
onChange={(e) => {
let value = e.target.valueAsNumber
if (isNaN(value) === true) value = 0
alterProduct("estoqueCorte", value)
}}
/>
</FormGroup>
<FormGroup>
<Label>Preço De</Label>
<Input
type="number"
value={product.precoDe}
onChange={(e) => {
let value = e.target.valueAsNumber
if (isNaN(value) === true) value = 0
alterProduct("precoDe", value)
}}
/>
</FormGroup>
<FormGroup>
<Label>Preço Por</Label>
<Input
type="number"
value={product.precoPor}
onChange={(e) => {
let value = e.target.valueAsNumber
if (isNaN(value) === true) value = 0
alterProduct("precoPor", value)
}}
/>
</FormGroup>
<FormGroup>
<Button onClick={() => insertUpdateProduct()}>{edit === true ?"Atualizar":"Cadastrar"}</Button>
</FormGroup>
<FormGroup>
<Button onClick={() => navigate("/products")}>Cancelar</Button>
</FormGroup>
</Form>
</FlexContainer>
)}
</>
);
}

View File

@ -0,0 +1,122 @@
import styled from 'styled-components';
const Form = styled.div`
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
width: 300px;
`;
const FormGroup = styled.div`
margin-bottom: 20px;
`;
const Label = styled.label`
display: block;
margin-bottom: 5px;
font-weight: bold;
`;
const Input = styled.input`
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
`;
const Button = styled.button`
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
&:hover {
background-color: #0056b3;
}
`;
const LinkStyled = styled.a`
display:block;
color: #007bff;
text-decoration: none;
font-weight: bold;
margin-right: 20px;
&:hover {
text-decoration: underline;
}
`
const LinksContainer = styled.div`
text-align: center; /* Alinha os links no centro horizontalmente */
margin: 10px;
`;
const HeaderContainer = styled.header`
background-color: #007bff;
color: white;
padding: 10px 20px;
width: 100vh;
border-radius: 10px;
`;
const Title = styled.h1`
font-size: 32px;
margin: 5px;
`;
const Nav = styled.nav`
ul {
list-style: none;
padding: 0px 5px;
display: flex;
gap: 20px;
}
li {
margin: 0;
}
a {
color: white;
text-decoration: none;
font-size: 15px;
&:hover {
text-decoration: underline;
}
}
`;
const LinkButton = styled.button`
background: none;
border: none;
color: #007bff;
text-decoration: underline;
cursor: pointer;
font-family: inherit;
font-size: inherit;
padding: 0;
margin: 0;
outline: none;
transition: color 0.2s ease-in-out;
&:hover {
color: #0056b3;
}
`;
export {
Form,
FormGroup,
Label,
Input,
Button,
LinkStyled,
LinksContainer,
HeaderContainer,
Title,
Nav,
LinkButton
}

View File

@ -0,0 +1,36 @@
import styled from 'styled-components';
const HomeContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
`;
const LinksContainer = styled.div`
text-align: center;
`;
const FlexContainer = styled.div`
display: flex;
justify-content: center;
align-items: start;
`;
const StartLink = styled.a`
display:block;
color: #007bff;
text-decoration: none;
font-weight: bold;
margin-right: 20px;
&:hover {
text-decoration: underline;
}
`
export default HomeContainer;
export {
StartLink,
LinksContainer,
FlexContainer
}

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
const LoginContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
`;
export default LoginContainer;

View File

@ -0,0 +1,37 @@
import styled from 'styled-components';
const Table = styled.table`
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 1em;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-width: 400px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #007bff;
color: #ffffff;
text-align: center;
}
tr {
&:nth-of-type(even) {
background-color: #f3f3f3;
}
&:hover {
background-color: #f1f1f1;
}
}
`;
export default Table;

View File

@ -0,0 +1,19 @@
import { combineReducers } from "redux"
import usersSlice from "./slices/usersSlice"
import productSlice from "./slices/productSlice"
import productsSlice from "./slices/productsSlice"
import { configureStore } from "@reduxjs/toolkit"
const rootReducer = combineReducers({
users: usersSlice,
product: productSlice,
products: productsSlice,
})
const reduxStore = configureStore({ reducer: rootReducer })
export default reduxStore
export type RootState = ReturnType<typeof reduxStore.getState>
export type AppDispatch = typeof reduxStore.dispatch

View File

@ -0,0 +1,31 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
export interface ProductProps {
nome: string
codigo: string
estoqueTotal: number
estoqueCorte: number
estoqueDisponivel: number
precoDe: number
precoPor: number
}
export interface ProductState {
product: ProductProps | null;
}
const initialState : ProductState = {
product: null,
};
const productSlice = createSlice({
name: "product",
initialState,
reducers: {
setProductSlice: (state, action: PayloadAction<ProductProps|null>) => {
state.product = action.payload;
},
}
})
export default productSlice.reducer
export const { setProductSlice } = productSlice.actions

View File

@ -0,0 +1,26 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
export interface ProductProps {
nome: string
codigo: string
estoqueTotal: number
estoqueCorte: number
estoqueDisponivel: number
precoDe: number
precoPor: number
}
const initialState : ProductProps[] = []
const productsSlice = createSlice({
name: "products",
initialState,
reducers: {
setProductsSlice: (_, action: PayloadAction<ProductProps[]>) => {
return action.payload
}
}
})
export default productsSlice.reducer
export const { setProductsSlice } = productsSlice.actions

View File

@ -0,0 +1,21 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
export interface UserProps {
user: string
username: string
}
const initialState : UserProps[] = []
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
setUsersSlice: (_, action: PayloadAction<UserProps[]>) => {
return action.payload
}
}
})
export default usersSlice.reducer
export const { setUsersSlice } = usersSlice.actions

7
spa/src/utils/routes.tsx Normal file
View File

@ -0,0 +1,7 @@
export const paths = {
user:{
login:"/login",
products: "/products",
editProduct: "/product/edit"
}
}

1
spa/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

28
spa/tsconfig.app.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

11
spa/tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}

13
spa/tsconfig.node.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmit": true
},
"include": ["vite.config.ts"]
}

12
spa/vite.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
host: '0.0.0.0',
open: true,
}
})