send all files
This commit is contained in:
parent
1e8cda0139
commit
c9464c4a6f
138
README.md
138
README.md
@ -1,2 +1,140 @@
|
|||||||
# NAPPDesafio
|
# 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
3
api/.exemple.env
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PORT=
|
||||||
|
JWT_KEY=
|
||||||
|
DATABASE_URL=
|
||||||
21
api/.gitignore
vendored
Normal file
21
api/.gitignore
vendored
Normal 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
16
api/Dockerfile
Normal 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
1
api/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# api
|
||||||
24
api/application/auth/controller.go
Normal file
24
api/application/auth/controller.go
Normal 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)
|
||||||
|
}
|
||||||
34
api/application/auth/dto/auth_dto.go
Normal file
34
api/application/auth/dto/auth_dto.go
Normal 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
|
||||||
|
}
|
||||||
10
api/application/auth/module.go
Normal file
10
api/application/auth/module.go
Normal 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)
|
||||||
|
}
|
||||||
51
api/application/auth/router.go
Normal file
51
api/application/auth/router.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
69
api/application/auth/service.go
Normal file
69
api/application/auth/service.go
Normal 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
|
||||||
|
}
|
||||||
45
api/application/products/controller.go
Normal file
45
api/application/products/controller.go
Normal 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)
|
||||||
|
}
|
||||||
28
api/application/products/dto/deleteProduct.dto.go
Normal file
28
api/application/products/dto/deleteProduct.dto.go
Normal 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
|
||||||
|
}
|
||||||
38
api/application/products/dto/product.dto.go
Normal file
38
api/application/products/dto/product.dto.go
Normal 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
|
||||||
|
}
|
||||||
28
api/application/products/dto/validateCode.dto.go
Normal file
28
api/application/products/dto/validateCode.dto.go
Normal 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
|
||||||
|
}
|
||||||
10
api/application/products/module.go
Normal file
10
api/application/products/module.go
Normal 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)
|
||||||
|
}
|
||||||
193
api/application/products/router.go
Normal file
193
api/application/products/router.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
112
api/application/products/service.go
Normal file
112
api/application/products/service.go
Normal 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
55
api/go.mod
Normal 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
175
api/go.sum
Normal 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=
|
||||||
62
api/helpers/database/database.go
Normal file
62
api/helpers/database/database.go
Normal 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")
|
||||||
|
}
|
||||||
29
api/helpers/database/queries.go
Normal file
29
api/helpers/database/queries.go
Normal 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)
|
||||||
|
)`
|
||||||
22
api/helpers/variable/variable.go
Normal file
22
api/helpers/variable/variable.go
Normal 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
74
api/libs/jwt/jwt.go
Normal 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
24
api/libs/logger/logger.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
25
api/libs/postgres/postgres.go
Normal file
25
api/libs/postgres/postgres.go
Normal 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
41
api/main.go
Normal 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"))
|
||||||
|
|
||||||
|
}
|
||||||
7
api/models/api/response.go
Normal file
7
api/models/api/response.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Error bool `json:"error"`
|
||||||
|
ErrorMessage string `json:"errorMessage"`
|
||||||
|
Payload interface{} `json:"payload"`
|
||||||
|
}
|
||||||
32
api/models/products/products.db.go
Normal file
32
api/models/products/products.db.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
15
api/models/products/products.go
Normal file
15
api/models/products/products.go
Normal 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"`
|
||||||
|
}
|
||||||
174
api/models/products/products.sql.go
Normal file
174
api/models/products/products.sql.go
Normal 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
|
||||||
|
}
|
||||||
32
api/models/products/query.sql
Normal file
32
api/models/products/query.sql
Normal 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;
|
||||||
15
api/models/products/schema.sql
Normal file
15
api/models/products/schema.sql
Normal 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
|
||||||
|
)
|
||||||
11
api/models/products/sqlc.yaml
Normal file
11
api/models/products/sqlc.yaml
Normal 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"
|
||||||
20
api/models/users/query.sql
Normal file
20
api/models/users/query.sql
Normal 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;
|
||||||
7
api/models/users/schema.sql
Normal file
7
api/models/users/schema.sql
Normal 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
|
||||||
|
)
|
||||||
10
api/models/users/sqlc.yaml
Normal file
10
api/models/users/sqlc.yaml
Normal 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"
|
||||||
32
api/models/users/users.db.go
Normal file
32
api/models/users/users.db.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
9
api/models/users/users.go
Normal file
9
api/models/users/users.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
User string
|
||||||
|
CreatedAt uint64
|
||||||
|
UpdatedAt *uint64
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
86
api/models/users/users.sql.go
Normal file
86
api/models/users/users.sql.go
Normal 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
32
etc/docker-compose.yaml
Normal 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
18
spa/.eslintrc.cjs
Normal 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
1
spa/.exemple.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_APP_API_URL=
|
||||||
24
spa/.gitignore
vendored
Normal file
24
spa/.gitignore
vendored
Normal 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
17
spa/Dockerfile
Normal 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
30
spa/README.md
Normal 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
13
spa/index.html
Normal 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
3502
spa/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
spa/package.json
Normal file
34
spa/package.json
Normal 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
1
spa/public/vite.svg
Normal 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
42
spa/src/App.css
Normal 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
25
spa/src/App.tsx
Normal 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
1
spa/src/assets/react.svg
Normal 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
25
spa/src/index.css
Normal 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
14
spa/src/main.tsx
Normal 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>
|
||||||
|
)
|
||||||
74
spa/src/pages/authorization/LoginUser.tsx
Normal file
74
spa/src/pages/authorization/LoginUser.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
spa/src/pages/products/ProductsUser.tsx
Normal file
53
spa/src/pages/products/ProductsUser.tsx
Normal 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;
|
||||||
77
spa/src/pages/products/components/TableProducts.tsx
Normal file
77
spa/src/pages/products/components/TableProducts.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
199
spa/src/pages/products/subpages/InsertUpdateProduct.tsx
Normal file
199
spa/src/pages/products/subpages/InsertUpdateProduct.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
spa/src/shared/components/default.ts
Normal file
122
spa/src/shared/components/default.ts
Normal 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
|
||||||
|
}
|
||||||
36
spa/src/shared/components/home.ts
Normal file
36
spa/src/shared/components/home.ts
Normal 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
|
||||||
|
}
|
||||||
11
spa/src/shared/components/login.ts
Normal file
11
spa/src/shared/components/login.ts
Normal 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;
|
||||||
37
spa/src/shared/components/table.ts
Normal file
37
spa/src/shared/components/table.ts
Normal 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;
|
||||||
19
spa/src/shared/store/reducers.tsx
Normal file
19
spa/src/shared/store/reducers.tsx
Normal 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
|
||||||
31
spa/src/shared/store/slices/productSlice.ts
Normal file
31
spa/src/shared/store/slices/productSlice.ts
Normal 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
|
||||||
26
spa/src/shared/store/slices/productsSlice.ts
Normal file
26
spa/src/shared/store/slices/productsSlice.ts
Normal 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
|
||||||
21
spa/src/shared/store/slices/usersSlice.ts
Normal file
21
spa/src/shared/store/slices/usersSlice.ts
Normal 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
7
spa/src/utils/routes.tsx
Normal 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
1
spa/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
28
spa/tsconfig.app.json
Normal file
28
spa/tsconfig.app.json
Normal 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
11
spa/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
spa/tsconfig.node.json
Normal file
13
spa/tsconfig.node.json
Normal 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
12
spa/vite.config.ts
Normal 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,
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user