12 | cd pet-dex-backend
13 | ```
14 |
15 | 3. **Pegue uma Issue:**
16 |
17 | Navegue pelas "Issues" e de preferência por uma Issue marcada como "Good First Issue". As "Issues" dessa forma são especialmente indicadas para novos colaboradores e são pontos de partida acessíveis no projeto. Mas, caso não haja nenhuma assim ou você se considere apto a pegar outra sem essa marcação, você possui total liberdade para pegá-la.
18 |
19 | Depois de escolher uma task, clique nela para obter mais detalhes.
20 |
21 | Lembre-se:
22 | Tarefas com fotos ao lado direito já foram selecionadas por outros colaboradores.
23 | Geralmente, você terá uma semana para concluir a tarefa, mas isso pode variar dependendo das políticas do projeto.
24 |
25 | 4. **Crie uma Branch:** Crie uma branch para trabalhar nas suas alterações.
26 |
27 | 5. **Faça Alterações:** Faça as alterações desejadas no código, documentação, ou outros recursos.
28 | 6. **Testes:** Certifique-se de que todas as mudanças são testadas e não introduzem erros.
29 | 7. **Commits Significativos:** Faça commits significativos e com mensagens claras. Utilizando comando abaixo e seguindo as instruções o commit ficara no padrão utilizado no projeto.
30 |
31 | ```bash
32 | git commit
33 | ```
34 |
35 | 1. **Atualize a Documentação:** Se necessário, atualize a documentação relevante para refletir suas mudanças.
36 | 2. **Envie as Alterações:** Envie suas alterações para o seu fork:
37 |
38 | ```bash
39 | git push origin nome-da-sua-branch
40 |
41 | ```
42 |
43 | 3. **Criação de Pull Request (PR):** Abra um Pull Request pelo o seu fork para o repositorio da PetDex, descrevendo suas alterações e fornecendo contexto sobre o que foi feito.
44 | 4. **Revisão de Código:** A equipe de mantenedores do projeto irá revisar o seu PR. Esteja disposto a fazer ajustes se necessário.
45 | 5. **Merge e Fechamento:** Após a revisão bem-sucedida, suas alterações serão mescladas à branch principal. Seu PR será fechado.
46 |
47 | ## Diretrizes de Contribuição
48 |
49 | - **Documentação:** Sempre atualize a documentação para refletir mudanças significativas.
50 | - **Testes:** Certifique-se de que suas alterações não quebram testes existentes. Se necessário, adicione novos testes.
51 | - **Tamanho das Pull Requests:** PRs menores são mais fáceis de revisar e mesclar. Tente manter o escopo de suas contribuições relativamente pequeno.
52 | - **Mantenha a Cortesia:** Seja cortês e respeitoso ao discutir e revisar o trabalho de outros contribuidores.
53 |
54 | ## Reconhecimento
55 |
56 | Agradecemos por ajudar a melhorar a PetDex! Sua dedicação à qualidade e inovação é fundamental para o sucesso contínuo deste projeto.
57 |
58 | Se você tiver alguma dúvida ou precisar de ajuda em qualquer etapa do processo de contribuição, sinta-se à vontade para criar um problema (issue) ou entrar em contato com a equipe de mantenedores.[Discord](discord.gg/3gsMAEumEd)
59 |
60 | ## Banco de dados
61 |
62 | Usamos MariaDB como Banco de Dados Relacional da aplicação.
63 |
64 | #### Migration
65 |
66 | Uma migration é um script que é usado para alterar o esquema de um banco de dados, como a adição de novas tabelas, colunas ou índices.
67 | Para criar uma migration, você deve criar um novo arquivo com a extensão .sql. O conteúdo do arquivo deve conter as alterações que você deseja fazer no esquema do banco de dados.
68 | Os arquivos .sql deverão ser criados com o comando abaixo:
69 |
70 | ```bash
71 | make create-migrations title=titulo_da_migration
72 | ```
73 |
74 | Ao executar o comando, serão criados dois arquivos na pasta /migrations com sufixos `.up` e `.down`. Os arquivos `.up` representam as alterações desejadas que serão aplicados no banco de dados, enquantos os arquivos `.down`, representa a ação de rollback referente ao que foi executado no arquivos `.up` de mesma versão.
75 |
76 | Obs.: crie os script SQL de forma idempotente e caso o seu script tenha vários comando ou consultas, considere colocar isso em uma transação.
77 |
78 | # 2. Diretrizes de Documentação da API PetDex-Backend com Swag
79 |
80 | Swag, é o pacote responsável pela geração automática da documentação API RESTful. O mesmo documenta códigos em Go com Swagger 2.0.
81 |
82 | ## Sumário
83 |
84 | 1. [Dependências](#dependências)
85 |
86 | 2. [Nova documentação](#executando-o-Swago)
87 |
88 | 3. [Acesso](#acessando-documentação)
89 |
90 | ### Dependências
91 |
92 | Para habilitar a geração automática de documentação via Swag, é necessário a sua instalação. Execute o comando a seguir para disponibilizar os recursos dessa feramenta pré configurados via Makefile.
93 |
94 | ```bash
95 | go install github.com/swaggo/swag/cmd/swag@latest
96 | ```
97 |
98 | Em caso de dúvidas na instalação e na sintaxe da documentação, consulte o repositório oficial da documentação através do link a seguir
99 | * [swag](https://github.com/swaggo/swag)
100 |
101 | Também é necessário ter o GNU Make instalado para executar os comandos definidos em Makefile:
102 |
103 | * [Make](https://www.gnu.org/software/make/)
104 |
105 | ### Executando o Swag
106 |
107 | Para gerar a documentação da API, depois documentado o endpoint, execute o comando a seguir:
108 |
109 | ```bash
110 | make swag
111 | ```
112 | Esse comando irá atualizar os arquivos swagger disponíveis no diretório `swagger/`
113 |
114 | ### Acessando Documentação
115 |
116 | A documentação estará disponívei para acesso através da rota `api/swagger/`
117 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.21.4 as build
2 | WORKDIR /usr/src/app/go/api
3 | COPY go.mod go.sum ./
4 | RUN go mod download
5 | COPY . ./
6 | RUN CGO_ENABLED=0 GOOS=linux go build -o pet-dex-api ./api/
7 |
8 | FROM alpine:3.15.11 as api
9 | WORKDIR /usr/src/app/go/api
10 | COPY --from=build /usr/src/app/go/api .
11 | EXPOSE 3000
12 | CMD ["./pet-dex-api"]
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Devhat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | title := "add_needed_care"
2 | include .env
3 |
4 | dev:
5 | docker compose --profile development --env-file .env up --build
6 |
7 | prod:
8 | docker compose --profile integration-tests --env-file .env up --build
9 |
10 | run:
11 | go run ./api/main.go
12 |
13 | test:
14 | go test ./...
15 |
16 | migration:
17 | go run cmd/main.go
18 |
19 | migration-up:
20 | go run cmd/main.go -up
21 |
22 | lint:
23 | docker run --rm -v ./:/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run -v
24 |
25 | swag:
26 | swag init -g api/main.go -o swagger/ --md swagger/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | PetDex - Seu Catálogo de Pets Virtual
8 |
9 |
10 |
11 | [](https://go.dev/)
12 | [](https://swagger.io/)
13 | [](https://mariadb.org/)
14 |
15 | Bem-vindo ao PetDex, o aplicativo que transforma a experiência de ser tutor de pets em algo único e interativo. Com o PetDex, os tutores podem catalogar e compartilhar informações sobre seus pets, semelhante à famosa Pokedex, mas para animais de estimação.
16 |
17 | ## Funcionalidades Principais
18 |
19 | ### 1. **Catálogo de Pets Personalizado**
20 |
21 | - Adicione informações sobre seus pets, incluindo nome, raça, idade e peculiaridades.
22 | - Faça o upload de fotos adoráveis dos seus companheiros peludos.
23 |
24 | ### 2. **Exploração de Raças**
25 |
26 | - Descubra novas raças de animais que você ainda não tem.
27 | - Explore informações detalhadas sobre cada raça, como características físicas, temperamento e cuidados específicos.
28 |
29 | ## Como Contribuir
30 |
31 | Se você é um entusiasta de pets, desenvolvedor em ascensão ou simplesmente quer fazer parte da comunidade PetDex, aqui estão algumas maneiras de contribuir:
32 |
33 | 1. **Desenvolvimento:**
34 | - Faça um fork do repositório e trabalhe em novas funcionalidades.
35 | - Resolva problemas existentes ou proponha melhorias.
36 | 2. **Documentação:**
37 | - Aprimore a documentação existente ou crie tutoriais para ajudar outros desenvolvedores.
38 | 3. **Testes:**
39 | - Ajude a garantir a estabilidade do aplicativo testando as novas funcionalidades e relatar problemas.
40 |
41 | ## Executando o projeto
42 |
43 | ### Manualmente
44 |
45 | Requisitos:
46 |
47 | - Go `1.21.4`
48 |
49 | Todos os comandos devem ser executados na raiz do projeto. Não esqueça de adaptar as variáveis de ambiente e configurar as conexões com os serviços que a aplicação depende.
50 |
51 | ```bash
52 | make run
53 |
54 | # ou
55 |
56 | go run ./api/main.go
57 | ```
58 |
59 | ### Com docker compose
60 |
61 | Requisitos:
62 |
63 | - Go `1.21.4`
64 | - Docker
65 |
66 | O projeto possui um arquivo `docker-compose.yml` que irá subir containers com todas as dependências do projeto e executará o programa com live reload ativado. Com um simples comando você vai ter um ambiente de desenvolvimento completamente configurado onde só vai se preocupar em codificar e salvar o código.
67 |
68 | Executar o projeto com Docker Compose proporciona muitas vantagens, facilitando a colaboratividade e também executando o programa em um ambiente o mais parecido possível com o ambiente de produção.
69 |
70 | Antes de iniciar, certifique-se de ter o [Docker](https://docs.docker.com/get-docker/) instalado e configurado corretamente em sua máquina.
71 |
72 | Na raiz do projeto, copie o `.env.example` e nomeie o novo arquivo com `.env`:
73 |
74 | ```bash
75 | cp .env.example .env
76 | ```
77 |
78 | Por fim, execute o projeto com:
79 |
80 | ```bash
81 | make dev
82 |
83 | # ou
84 |
85 | docker compose --profile development --env-file .env up # use -d para executar os containers em background
86 | ```
87 |
88 | _Subir todos os containers pode demorar um tempo dependendo do seu setup ou internet._
89 |
90 | ## Contato
91 |
92 | Se precisar de ajuda, tiver sugestões ou quiser se envolver mais profundamente com a comunidade PetDex, entre em contato conosco:
93 |
94 | - Discord: [https://discord.gg/9f5BZ7yD](https://discord.gg/9f5BZ7yD)
95 | - Twitter: [Devhat (@DevHatt) / X (twitter.com)](https://twitter.com/DevHatt)
96 |
97 | Junte-se a nós nesta jornada emocionante de tornar o PetDex a melhor experiência para tutores de pets em todo o mundo!
98 |
--------------------------------------------------------------------------------
/a.out:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devhatt/pet-dex-backend/42b37eb12762c7c9305974a699ea4e9349a5cf68/a.out
--------------------------------------------------------------------------------
/api/controllers/breed.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "pet-dex-backend/v2/infra/config"
7 | "pet-dex-backend/v2/pkg/uniqueEntityId"
8 | "pet-dex-backend/v2/usecase"
9 |
10 | "github.com/go-chi/chi/v5"
11 | )
12 |
13 | var logger = config.GetLogger("breed-controller")
14 |
15 | type BreedController struct {
16 | Usecase *usecase.BreedUseCase
17 | }
18 |
19 | func NewBreedController(usecase *usecase.BreedUseCase) *BreedController {
20 | return &BreedController{
21 | Usecase: usecase,
22 | }
23 | }
24 |
25 | // List retrieves breeds information for all pets.
26 | // @Summary View list of all Breeds
27 | // @Description Retrieves list of all pet breeds
28 | // @Tags Pet
29 | // @Produce json
30 | // @Success 200 {object} dto.BreedList
31 | // @Failure 400
32 | // @Failure 500
33 | // @Router /pets/breeds/ [get]
34 | func (cntrl *BreedController) List(responseWriter http.ResponseWriter, request *http.Request) {
35 | breeds, err := cntrl.Usecase.List()
36 | if err != nil {
37 | logger.Error("error listing breeds", err)
38 | responseWriter.WriteHeader(http.StatusInternalServerError)
39 | return
40 | }
41 |
42 | responseWriter.WriteHeader(http.StatusOK)
43 | err = json.NewEncoder(responseWriter).Encode(breeds)
44 | if err != nil {
45 | logger.Error("error encoding json", err)
46 | responseWriter.WriteHeader(http.StatusInternalServerError)
47 | }
48 | }
49 |
50 | func (cntrl *BreedController) FindBreed(w http.ResponseWriter, r *http.Request) {
51 | IDStr := chi.URLParam(r, "id")
52 |
53 | ID, err := uniqueEntityId.ParseID(IDStr)
54 | if err != nil {
55 | http.Error(w, "Bad Request: Invalid ID", http.StatusBadRequest)
56 | return
57 | }
58 |
59 | breed, err := cntrl.Usecase.FindByID(ID)
60 | if err != nil {
61 | http.Error(w, err.Error(), http.StatusInternalServerError)
62 | return
63 | }
64 |
65 | if err := json.NewEncoder(w).Encode(&breed); err != nil {
66 | http.Error(w, "Failed to encode breed", http.StatusInternalServerError)
67 | return
68 | }
69 |
70 | w.WriteHeader(http.StatusOK)
71 | }
72 |
--------------------------------------------------------------------------------
/api/controllers/ong.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "pet-dex-backend/v2/entity/dto"
7 | "pet-dex-backend/v2/infra/config"
8 | "pet-dex-backend/v2/pkg/uniqueEntityId"
9 | "pet-dex-backend/v2/usecase"
10 | "strconv"
11 | "strings"
12 |
13 | "github.com/go-chi/chi/v5"
14 | )
15 |
16 | type OngController struct {
17 | usecase *usecase.OngUsecase
18 | logger config.Logger
19 | }
20 |
21 | func NewOngcontroller(usecase *usecase.OngUsecase) *OngController {
22 | return &OngController{
23 | usecase: usecase,
24 | logger: *config.GetLogger("ong-controller"),
25 | }
26 | }
27 |
28 | // Add Ong to the database.
29 | // @Summary Create Ong
30 | // @Description Sends the Ong registration data via the request body for persistence in the database.
31 | // @Tags Ong
32 | // @Accept json
33 | // @Param ongDto body dto.OngInsertDto true "Ong object information for registration"
34 | // @Success 201
35 | // @Failure 400
36 | // @Failure 500
37 | // @Router /ongs/ [post]
38 | func (oc *OngController) Insert(w http.ResponseWriter, r *http.Request) {
39 | var ongDto dto.OngInsertDto
40 | err := json.NewDecoder(r.Body).Decode(&ongDto)
41 |
42 | if err != nil {
43 | oc.logger.Error("error on ong controller: ", err)
44 | w.WriteHeader(http.StatusBadRequest)
45 | return
46 | }
47 |
48 | err = oc.usecase.Save(&ongDto)
49 |
50 | if err != nil {
51 | oc.logger.Error("error on ong controller: ", err)
52 | w.WriteHeader(http.StatusInternalServerError)
53 | return
54 | }
55 |
56 | w.WriteHeader(http.StatusCreated)
57 | }
58 |
59 | // List List of Ong information retrieval from query parameters.
60 | // @Summary View list of Ong.
61 | // @Description This endpoint allows you to retrieve a list Ong organized according to query parameters..
62 | // @Description.markdown details
63 | // @Tags Ong
64 | // @Produce json
65 | // @Param limit query string false "Query limits the return of 10 data." example(10)
66 | // @Param sortBy query string false "Property used to sort and organize displayed data" example(name)
67 | // @Param order query string false "Data can be returned in ascending (asc) or descending (desc) order" example des" example(desc)
68 | // @Param offset query string false "Initial position of the offset that marks the beginning of the display of the next elements" default(0)
69 | // @Success 200 {object} dto.OngListMapper
70 | // @Failure 400
71 | // @Failure 500
72 | // @Router /ongs/ [get]
73 | func (oc *OngController) List(w http.ResponseWriter, r *http.Request) {
74 | // pageStr := r.URL.Query().Get("page")
75 | limitStr := r.URL.Query().Get("limit")
76 | sortBy := r.URL.Query().Get("sortBy")
77 | order := r.URL.Query().Get("order")
78 | offsetStr := r.URL.Query().Get("offset")
79 |
80 | validSortBy := map[string]bool{"name": true, "address": true}
81 | if !validSortBy[sortBy] {
82 | sortBy = "name"
83 | }
84 |
85 | if strings.ToLower(order) != "asc" && strings.ToLower(order) != "desc" {
86 | order = "asc"
87 | }
88 |
89 | // page, err := strconv.Atoi(pageStr)
90 | // if err != nil || page < 1 {
91 | // page = 1
92 | // }
93 |
94 | limit, err := strconv.Atoi(limitStr)
95 | if err != nil || limit < 1 {
96 | limit = 10
97 | }
98 |
99 | offset, _ := strconv.Atoi(offsetStr)
100 |
101 | ongs, err := oc.usecase.List(limit, offset, sortBy, order)
102 |
103 | if err != nil {
104 | logger.Error("error listing ongs", err)
105 | w.WriteHeader(http.StatusInternalServerError)
106 | return
107 | }
108 |
109 | w.WriteHeader(http.StatusOK)
110 | err = json.NewEncoder(w).Encode(ongs)
111 | if err != nil {
112 | logger.Error("error encoding json", err)
113 | w.WriteHeader(http.StatusInternalServerError)
114 | }
115 | }
116 |
117 | // FindByID Retrieves ONG information from its provided ID.
118 | // @Summary Find ONG by ID
119 | // @Description Retrieves ONG details based on the ONG ID provided as a parameter.
120 | // @Tags Ong
121 | // @Accept json
122 | // @Produce json
123 | // @Param ongID path string true "ID of the ONG to be retrieved"
124 | // @Success 200 {object} dto.OngListMapper
125 | // @Failure 400
126 | // @Failure 500
127 | // @Router /ongs/{ongID} [get]
128 | func (oc *OngController) FindByID(w http.ResponseWriter, r *http.Request) {
129 | IDStr := chi.URLParam(r, "ongID")
130 |
131 | ID, err := uniqueEntityId.ParseID(IDStr)
132 | if err != nil {
133 | oc.logger.Error("error on ong controller: ", err)
134 | http.Error(w, "Bad Request: Invalid ID", http.StatusBadRequest)
135 | return
136 | }
137 |
138 | ong, err := oc.usecase.FindByID(ID)
139 | if err != nil {
140 | oc.logger.Error("error on ong controller: ", err)
141 | w.WriteHeader(http.StatusInternalServerError)
142 | }
143 |
144 | if err = json.NewEncoder(w).Encode(&ong); err != nil {
145 | oc.logger.Error("error on ong controller: ", err)
146 | http.Error(w, "Failed to encode ong", http.StatusInternalServerError)
147 | return
148 | }
149 | }
150 |
151 | // Update Ong to database.
152 | // @Summary Update Ong By ID
153 | // @Description Updates the details of an existing Ong based on the provided Ong ID.
154 | // @Tags Ong
155 | // @Accept json
156 | // @Param ongID path string true "Ong id to be updated"
157 | // @Param ongDto body dto.OngUpdateDto true "Data to update of the Ong"
158 | // @Success 201
159 | // @Failure 400
160 | // @Failure 500
161 | // @Router /ongs/{ongID} [patch]
162 | func (oc *OngController) Update(w http.ResponseWriter, r *http.Request) {
163 | IDStr := chi.URLParam(r, "ongID")
164 | ID, err := uniqueEntityId.ParseID(IDStr)
165 |
166 | if err != nil {
167 | oc.logger.Error("error on ong controller:", err)
168 | w.WriteHeader(http.StatusBadRequest)
169 | return
170 | }
171 |
172 | var ongDto dto.OngUpdateDto
173 | err = json.NewDecoder(r.Body).Decode(&ongDto)
174 |
175 | if err != nil {
176 | oc.logger.Error("error on ong controller: ", err)
177 | w.WriteHeader(http.StatusBadRequest)
178 | return
179 | }
180 |
181 | err = oc.usecase.Update(ID, &ongDto)
182 |
183 | if err != nil {
184 | oc.logger.Error("error on ong controller: ", err)
185 | w.WriteHeader(http.StatusInternalServerError)
186 | return
187 | }
188 |
189 | w.WriteHeader(http.StatusCreated)
190 |
191 | }
192 |
193 | // Deletes the ONG entity by ID.
194 | // @Summary Delete ONG by ID.
195 | // @Description Deletes the ONG corresponding to the provided ID in the request parameter.
196 | // @Tags Ong
197 | // @Accept json
198 | // @Produce json
199 | // @Param ongID path string true "ID of the ONG to be deleted"
200 | // @Success 200
201 | // @Failure 400
202 | // @Failure 500
203 | // @Router /ongs/{ongID} [delete]
204 | func (oc *OngController) Delete(w http.ResponseWriter, r *http.Request) {
205 | IDStr := chi.URLParam(r, "ongID")
206 | ID, err := uniqueEntityId.ParseID(IDStr)
207 | if err != nil {
208 | oc.logger.Error("error on ong controller: ", err)
209 | w.WriteHeader(http.StatusBadRequest)
210 | return
211 | }
212 |
213 | err = oc.usecase.Delete(ID)
214 | if err != nil {
215 | oc.logger.Error("error on ong controller: ", err)
216 | w.WriteHeader(http.StatusInternalServerError)
217 | return
218 | }
219 |
220 | w.WriteHeader(http.StatusOK)
221 | }
222 |
--------------------------------------------------------------------------------
/api/controllers/pet.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "pet-dex-backend/v2/api/errors"
8 | "pet-dex-backend/v2/entity"
9 | "pet-dex-backend/v2/entity/dto"
10 | "pet-dex-backend/v2/infra/config"
11 | "pet-dex-backend/v2/usecase"
12 | "strconv"
13 | "strings"
14 | "time"
15 |
16 | "pet-dex-backend/v2/pkg/encoder"
17 | "pet-dex-backend/v2/pkg/uniqueEntityId"
18 |
19 | "github.com/go-chi/chi/v5"
20 | )
21 |
22 | type PetController struct {
23 | Usecase *usecase.PetUseCase
24 | }
25 |
26 | func NewPetController(usecase *usecase.PetUseCase) *PetController {
27 | return &PetController{
28 | Usecase: usecase,
29 | }
30 | }
31 |
32 | // Update Pet to the database.
33 | // @Summary Update an Pet existing.
34 | // @Description Update the Pet's registration data via the request body for persistence in the database.
35 | // @Tags User
36 | // @Accept json
37 | // @Produce json
38 | // @Param userID path string true "User ID"
39 | // @Param petID path string true "Pet ID"
40 | // @Param petDto body dto.PetUpdateDto true "Pet object information for update of data"
41 | // @Success 200
42 | // @Failure 400
43 | // @Failure 500
44 | // @Router /user/{userID}/pets/{petID} [patch]
45 | func (pc *PetController) Update(w http.ResponseWriter, r *http.Request) {
46 | userID := chi.URLParam(r, "userID")
47 | petID := chi.URLParam(r, "petID")
48 |
49 | var petUpdateDto dto.PetUpdateDto
50 | err := json.NewDecoder(r.Body).Decode(&petUpdateDto)
51 | defer r.Body.Close()
52 |
53 | if err != nil {
54 | fmt.Printf("Invalid request: could not decode pet data from request body %s", err.Error())
55 | err := errors.ErrInvalidBody{
56 | Description: "The body is invalid",
57 | }
58 |
59 | w.WriteHeader(http.StatusBadRequest)
60 | json_err := json.NewEncoder(w).Encode(err)
61 | if json_err != nil {
62 | logger.Error("error encoding json", err)
63 | w.WriteHeader(http.StatusInternalServerError)
64 | }
65 | }
66 |
67 | err = pc.Usecase.Update(petID, userID, petUpdateDto)
68 |
69 | if err != nil {
70 | fmt.Printf("Error in usecase: %s", err.Error())
71 |
72 | err := errors.ErrInvalidID{
73 | Description: err.Error(),
74 | }
75 |
76 | w.WriteHeader(http.StatusBadRequest)
77 | json_err := json.NewEncoder(w).Encode(err)
78 | if json_err != nil {
79 | logger.Error("error encoding json", err)
80 | w.WriteHeader(http.StatusInternalServerError)
81 | }
82 | return
83 | }
84 | }
85 |
86 | // FindPet Retrieves Pet information from its provided ID.
87 | // @Summary Find Pet by ID
88 | // @Description Retrieves Pet details based on the pet ID provided as a parameter.
89 | // @Tags Pet
90 | // @Accept json
91 | // @Produce json
92 | // @Param petID path string true "ID of the Pet to be retrieved"
93 | // @Success 200 {object} entity.Pet
94 | // @Failure 400
95 | // @Failure 500
96 | // @Router /pets/{petID} [get]
97 | func (cntrl *PetController) FindPet(w http.ResponseWriter, r *http.Request) {
98 | IDStr := chi.URLParam(r, "petID")
99 |
100 | ID, err := uniqueEntityId.ParseID(IDStr)
101 | if err != nil {
102 | http.Error(w, "Bad Request: Invalid ID", http.StatusBadRequest)
103 | return
104 | }
105 |
106 | pet, err := cntrl.Usecase.FindByID(ID)
107 | if err != nil {
108 | http.Error(w, err.Error(), http.StatusInternalServerError)
109 | return
110 | }
111 |
112 | if err := json.NewEncoder(w).Encode(&pet); err != nil {
113 | http.Error(w, "Failed to encode pet", http.StatusInternalServerError)
114 | return
115 | }
116 |
117 | w.WriteHeader(http.StatusOK)
118 | }
119 |
120 | // List all pets from a provided user id.
121 | // @Summary List pets by user id
122 | // @Description List all pets owned by the user corresponding to the provided user ID
123 | // @Tags User
124 | // @Accept json
125 | // @Produce json
126 | // @Param userID path string true "ID of the User"
127 | // @Success 200 {object} entity.Pet
128 | // @Failure 400
129 | // @Failure 500
130 | // @Router /user/{userID}/my-pets [get]
131 | func (cntrl *PetController) ListUserPets(w http.ResponseWriter, r *http.Request) {
132 | IDStr := chi.URLParam(r, "userID")
133 |
134 | userID, err := uniqueEntityId.ParseID(IDStr)
135 | if err != nil {
136 | http.Error(w, "Bad Request: Invalid userID", http.StatusBadRequest)
137 | return
138 | }
139 |
140 | pets, err := cntrl.Usecase.ListUserPets(userID)
141 | if err != nil {
142 | http.Error(w, err.Error(), http.StatusInternalServerError)
143 | return
144 | }
145 |
146 | if err := json.NewEncoder(w).Encode(&pets); err != nil {
147 | http.Error(w, "Failed to encode pets", http.StatusInternalServerError)
148 | return
149 | }
150 |
151 | w.WriteHeader(http.StatusOK)
152 | }
153 |
154 | // Add Pet to the database.
155 | // @Summary Create Pet by petDto
156 | // @Description Sends the Pet's registration data via the request body for persistence in the database.
157 | // @Tags Pet
158 | // @Accept json
159 | // @Produce json
160 | // @Param petDto body dto.PetInsertDto true "Pet object information for registration"
161 | // @Success 201
162 | // @Failure 400
163 | // @Failure 500
164 | // @Router /pets/ [post]
165 | func (cntrl *PetController) CreatePet(w http.ResponseWriter, r *http.Request) {
166 | var petToSave dto.PetInsertDto
167 |
168 | err := json.NewDecoder(r.Body).Decode(&petToSave)
169 | defer r.Body.Close()
170 |
171 | if err != nil {
172 | fmt.Printf("Invalid request: could not decode pet data from request body %s", err.Error())
173 |
174 | w.WriteHeader(http.StatusBadRequest)
175 | json_err := json.NewEncoder(w).Encode(errors.ErrInvalidBody{
176 | Description: "The body is invalid",
177 | })
178 | if json_err != nil {
179 | logger.Error("error encoding json", err)
180 | w.WriteHeader(http.StatusInternalServerError)
181 | }
182 | return
183 | }
184 |
185 | err = petToSave.Validate()
186 | if err != nil {
187 | fmt.Printf("Invalid request: could not validate pet data from request body %s", err.Error())
188 |
189 | w.WriteHeader(http.StatusBadRequest)
190 | json_err := json.NewEncoder(w).Encode(err)
191 | if json_err != nil {
192 | logger.Error("error encoding json", err)
193 | w.WriteHeader(http.StatusInternalServerError)
194 | }
195 | return
196 | }
197 |
198 | err = cntrl.Usecase.Save(petToSave)
199 |
200 | if err != nil {
201 | fmt.Printf("Error in usecase: %s", err.Error())
202 |
203 | err := err.Error()
204 |
205 | w.WriteHeader(http.StatusBadRequest)
206 | json_err := json.NewEncoder(w).Encode(err)
207 | if json_err != nil {
208 | logger.Error("error encoding json", err)
209 | w.WriteHeader(http.StatusInternalServerError)
210 | }
211 | return
212 | }
213 |
214 | w.WriteHeader(http.StatusCreated)
215 | }
216 |
217 | // ListAllPets Retrieves the list of all pets.
218 | // @Summary View list of all pets.
219 | // @Description Public route for viewing all pets.
220 | // @Tags Pet
221 | // @Produce json
222 | // @Success 200 {object} entity.Pet
223 | // @Failure 400
224 | // @Failure 500
225 | // @Router /pets/ [get]
226 | func (cntrl *PetController) ListAllPets(w http.ResponseWriter, r *http.Request) {
227 | encoderAdapter := encoder.NewEncoderAdapter(config.GetEnvConfig().JWT_SECRET)
228 | var pageNumber int
229 | var err error
230 | var pets []*entity.Pet
231 | pageStr := r.URL.Query()
232 |
233 | if pageStr.Get("page") == "" {
234 | pageNumber = 1
235 | } else {
236 | pageNumber, err = strconv.Atoi(pageStr.Get("page"))
237 | }
238 |
239 | if err != nil {
240 | http.Error(w, "Bad Request: Invalid page number", http.StatusBadRequest)
241 | return
242 | }
243 |
244 | if pageNumber < 0 {
245 | http.Error(w, "Bad Request: Page number cannot be negative", http.StatusBadRequest)
246 | return
247 | }
248 |
249 | authHeader := r.Header.Get("Authorization")
250 | isUnauthorized := true
251 |
252 | headerSplited := strings.Split(authHeader, " ")
253 | if len(headerSplited) == 2 {
254 | bearerToken := headerSplited[1]
255 |
256 | userclaims := encoderAdapter.ParseAccessToken(bearerToken)
257 | isUnauthorized = userclaims.ExpiresAt != 0 && userclaims.ExpiresAt < time.Now().Unix()
258 | }
259 |
260 | pets, err = cntrl.Usecase.ListPetsByPage(pageNumber, isUnauthorized)
261 |
262 | if err != nil {
263 | http.Error(w, "Failed to retrieve pets", http.StatusInternalServerError)
264 | return
265 | }
266 |
267 | if err = json.NewEncoder(w).Encode(&pets); err != nil {
268 | http.Error(w, "Failed to encode pets", http.StatusInternalServerError)
269 | return
270 | }
271 | w.WriteHeader(http.StatusOK)
272 | }
273 |
--------------------------------------------------------------------------------
/api/errors/errors.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | type ErrInvalidID struct {
4 | Description string `json:"description"`
5 | }
6 |
7 | type ErrInvalidBody struct {
8 | Description string `json:"description"`
9 | }
10 |
11 | func (e *ErrInvalidID) Error() string {
12 | return e.Description
13 | }
14 |
15 | func (e *ErrInvalidBody) Error() string {
16 | return e.Description
17 | }
18 |
--------------------------------------------------------------------------------
/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "pet-dex-backend/v2/api/controllers"
8 | "pet-dex-backend/v2/api/routes"
9 | "pet-dex-backend/v2/infra/config"
10 | "pet-dex-backend/v2/infra/db"
11 | "pet-dex-backend/v2/pkg/encoder"
12 | "pet-dex-backend/v2/pkg/hasher"
13 | "pet-dex-backend/v2/pkg/sso"
14 | "pet-dex-backend/v2/usecase"
15 |
16 | _ "pet-dex-backend/v2/swagger"
17 |
18 | "github.com/jmoiron/sqlx"
19 | )
20 |
21 | // @title PetDex: Documentação API
22 | // @version 1.0
23 | // @description Esta página se destina a documentação da API do projeto PetDex Backend
24 |
25 | // @contact.name DevHatt
26 | // @contact.url https://github.com/devhatt
27 |
28 | // @license.name MIT license
29 | // @license.url https://github.com/devhatt/pet-dex-backend?tab=MIT-1-ov-file#readme
30 |
31 | // @host localhost:3000/api
32 | // @BasePath /
33 | func main() {
34 | envVariables, err := config.LoadEnv(".")
35 | if err != nil {
36 | panic(err)
37 | }
38 |
39 | databaseUrl := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", envVariables.DB_USER, envVariables.DB_PASSWORD, envVariables.DB_HOST, envVariables.DB_PORT, envVariables.DB_DATABASE)
40 | sqlxDb := sqlx.MustConnect("mysql", databaseUrl)
41 | dbPetRepo := db.NewPetRepository(sqlxDb)
42 | dbUserRepo := db.NewUserRepository(sqlxDb)
43 | dbOngRepo := db.NewOngRepository(sqlxDb)
44 | hash := hasher.NewHasher()
45 | bdBreedRepo := db.NewBreedRepository(sqlxDb)
46 |
47 | encoder := encoder.NewEncoderAdapter(envVariables.JWT_SECRET)
48 |
49 | googleSsoGt := sso.NewGoogleGateway(envVariables)
50 | facebookSsoGt := sso.NewFacebookGateway(envVariables)
51 |
52 | ssoProvider := sso.NewProvider(googleSsoGt, facebookSsoGt)
53 |
54 | breedUsecase := usecase.NewBreedUseCase(bdBreedRepo)
55 | uusercase := usecase.NewUserUsecase(dbUserRepo, hash, encoder, ssoProvider)
56 | petUsecase := usecase.NewPetUseCase(dbPetRepo)
57 | ongUsecase := usecase.NewOngUseCase(dbOngRepo, dbUserRepo, hash)
58 | breedController := controllers.NewBreedController(breedUsecase)
59 | petController := controllers.NewPetController(petUsecase)
60 | userController := controllers.NewUserController(uusercase)
61 | ongController := controllers.NewOngcontroller(ongUsecase)
62 | controllers := routes.Controllers{
63 | PetController: petController,
64 | UserController: userController,
65 | BreedController: breedController,
66 | OngController: ongController,
67 | }
68 | router := routes.InitializeRouter(controllers)
69 |
70 | fmt.Printf("running on port %v \n", envVariables.API_PORT)
71 | log.Fatal(http.ListenAndServe(":"+envVariables.API_PORT, router))
72 | }
73 |
--------------------------------------------------------------------------------
/api/middlewares/auth.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "net/http"
5 | "pet-dex-backend/v2/infra/config"
6 | "pet-dex-backend/v2/pkg/encoder"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func AuthMiddleware(next http.Handler) http.Handler {
12 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13 | encoder := encoder.NewEncoderAdapter(config.GetEnvConfig().JWT_SECRET)
14 |
15 | authHeader := r.Header.Get("Authorization")
16 | if authHeader == "" {
17 | w.WriteHeader(401)
18 | return
19 | }
20 | headerSplited := strings.Split(authHeader, " ")
21 | if len(headerSplited) != 2 {
22 | w.WriteHeader(401)
23 | return
24 | }
25 | bearerToken := headerSplited[1]
26 | if bearerToken == "" {
27 | w.WriteHeader(401)
28 | return
29 | }
30 | userclaims := encoder.ParseAccessToken(bearerToken)
31 | if userclaims.ExpiresAt != 0 && userclaims.ExpiresAt < time.Now().Unix() {
32 | w.WriteHeader(401)
33 | return
34 | }
35 | r.Header.Add("userId", userclaims.Id)
36 | next.ServeHTTP(w, r)
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/api/middlewares/cors.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/go-chi/cors"
7 | )
8 |
9 | func CorsMiddleware() func(http.Handler) http.Handler {
10 | return cors.Handler(cors.Options{
11 | AllowedOrigins: []string{"*"},
12 | AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
13 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
14 | ExposedHeaders: []string{"Link"},
15 | AllowCredentials: true,
16 | MaxAge: 300, // Maximum value not ignored by any of major browsers
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/api/routes/router.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/go-chi/chi/v5"
7 | "github.com/go-chi/chi/v5/middleware"
8 | )
9 |
10 | func InitializeRouter(contrllers Controllers) *chi.Mux {
11 | router := chi.NewRouter()
12 |
13 | router.Use(middleware.AllowContentType("application/json"))
14 | router.Use(middleware.Heartbeat("/ping"))
15 | if os.Getenv("ENVIRONMENT") != "DEVELOPMENT" {
16 | router.Use(middleware.Logger)
17 | }
18 |
19 | InitRoutes(contrllers, router)
20 | return router
21 | }
22 |
--------------------------------------------------------------------------------
/api/routes/routes.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "pet-dex-backend/v2/api/controllers"
5 | "pet-dex-backend/v2/api/middlewares"
6 |
7 | "github.com/go-chi/chi/v5"
8 | "github.com/go-chi/chi/v5/middleware"
9 | httpSwagger "github.com/swaggo/http-swagger"
10 | )
11 |
12 | type Controllers struct {
13 | PetController *controllers.PetController
14 | UserController *controllers.UserController
15 | OngController *controllers.OngController
16 | BreedController *controllers.BreedController
17 | }
18 |
19 | func InitRoutes(controllers Controllers, c *chi.Mux) {
20 |
21 | c.Route("/api", func(r chi.Router) {
22 | r.Use(middlewares.CorsMiddleware())
23 | r.Use(middleware.AllowContentType("application/json"))
24 |
25 | r.Group(func(private chi.Router) {
26 | private.Use(middlewares.AuthMiddleware)
27 |
28 | private.Route("/pets", func(r chi.Router) {
29 | r.Route("/breeds", func(r chi.Router) {
30 | r.Get("/", controllers.BreedController.List)
31 | })
32 | r.Get("/{petID}", controllers.PetController.FindPet)
33 | r.Post("/", controllers.PetController.CreatePet)
34 | })
35 |
36 | private.Route("/ongs", func(r chi.Router) {
37 | r.Post("/", controllers.OngController.Insert)
38 | r.Get("/", controllers.OngController.List)
39 | r.Get("/{ongID}", controllers.OngController.FindByID)
40 | r.Patch("/{ongID}", controllers.OngController.Update)
41 | r.Delete("/{ongID}", controllers.OngController.Delete)
42 | })
43 |
44 | private.Route("/user", func(r chi.Router) {
45 | r.Get("/{userID}/my-pets", controllers.PetController.ListUserPets)
46 | r.Patch("/{userID}/pets/{petID}", controllers.PetController.Update)
47 | r.Patch("/{userID}", controllers.UserController.Update)
48 | r.Get("/{userID}", controllers.UserController.FindByID)
49 | r.Delete("/{userID}", controllers.UserController.Delete)
50 | })
51 | private.Route("/settings", func(r chi.Router) {
52 | r.Patch("/push-notifications", controllers.UserController.UpdatePushNotificationSettings)
53 | })
54 | })
55 |
56 | r.Group(func(public chi.Router) {
57 | public.Post("/user/create-account", controllers.UserController.Insert)
58 | public.Post("/user/{provider}/login", controllers.UserController.ProviderLogin)
59 | public.Post("/user/login", controllers.UserController.Login)
60 | public.Get("/pets/", controllers.PetController.ListAllPets)
61 | public.Get("/swagger/*", httpSwagger.Handler(
62 | httpSwagger.URL("doc.json"), //The url endpoint to API definition
63 | ))
64 | })
65 |
66 | })
67 | }
68 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "pet-dex-backend/v2/pkg/migration"
7 | )
8 |
9 | func main() {
10 |
11 | upFlag := flag.Bool("up", false, "Run migrations UP")
12 | flag.Parse()
13 |
14 | if *upFlag {
15 | fmt.Println("Running Migrations UP...")
16 | migration.Up()
17 | fmt.Println("Migrations executed!")
18 | return
19 | }
20 |
21 |
22 | var number string
23 | fmt.Println("Migrations CLI")
24 | fmt.Println("Type the number of the command desired:\n1-Migrations UP\n2-Migrations DOWN\n3-Create a new migration")
25 | _, err := fmt.Scan(&number)
26 | if err != nil {
27 | fmt.Println("Error while reading the values", err)
28 | }
29 |
30 |
31 | if number == "1" {
32 | fmt.Println("Running Migrations UP...")
33 | migration.Up()
34 | fmt.Println("Migrations executed!")
35 | return
36 | }
37 |
38 | if number == "2" {
39 | fmt.Println("Running Migrations DOWN...")
40 | migration.Down()
41 | fmt.Println("Migrations executed!")
42 | return
43 | }
44 |
45 | if number == "3" {
46 | fmt.Println("Type the name of the migration desired:")
47 | var name string
48 | _, err := fmt.Scan(&name)
49 | if err != nil {
50 | fmt.Println("Error while reading the values", err)
51 | }
52 | fmt.Println("Creating a new migration...")
53 | migration.Create(name)
54 | fmt.Println("Migration created!")
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | volumes:
2 | cache:
3 | driver: local
4 | cache-test:
5 | driver: local
6 | networks:
7 | pet-dex-dev:
8 | driver: bridge
9 | pet-dex-integration-tests:
10 | driver: bridge
11 |
12 | services:
13 | api:
14 | image: cosmtrek/air
15 | profiles:
16 | - development
17 | hostname: api
18 | working_dir: /app
19 | environment:
20 | - PORT=${API_PORT}
21 | - ENVIRONMENT=${ENVIRONMENT}
22 | ports:
23 | - "${API_PORT}:${API_PORT}"
24 | volumes:
25 | - ./:/app
26 | depends_on:
27 | cache:
28 | condition: service_started
29 | db:
30 | condition: service_healthy
31 | env_file:
32 | - .env.example
33 | - .env
34 | networks:
35 | - pet-dex-dev
36 |
37 | db: &db # TODO: Quando subir dev se não tiver migration tem q rodar
38 | image: mariadb:11.4.1-rc-jammy
39 | profiles:
40 | - development
41 | hostname: db
42 | environment:
43 | - MARIADB_DATABASE=${DB_DATABASE}
44 | - MARIADB_USER=${DB_USER}
45 | - MARIADB_PASSWORD=${DB_PASSWORD}
46 | - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
47 | - PORT=${DB_PORT}
48 | ports:
49 | - "${DB_PORT}:${DB_PORT}"
50 | depends_on:
51 | cache:
52 | condition: service_started
53 | healthcheck:
54 | test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
55 | start_period: 20s
56 | interval: 20s
57 | timeout: 10s
58 | retries: 3
59 | volumes:
60 | - ./data:/var/lib/mysql
61 | env_file:
62 | - .env.example
63 | - .env
64 | networks:
65 | - pet-dex-dev
66 |
67 | cache: &cache
68 | image: redis:7.2.4-alpine
69 | profiles:
70 | - development
71 | hostname: pet-dex-cache
72 | restart: always
73 | environment:
74 | REDIS_PASSWORD: ${REDIS_PASSWORD}
75 | ports:
76 | - "${REDIS_PORT}:${REDIS_PORT}"
77 | command: redis-server --save "" --requirepass ${REDIS_PASSWORD}
78 | volumes:
79 | - ./cache:/data
80 | env_file:
81 | - .env.example
82 | - .env
83 | networks:
84 | - pet-dex-dev
85 |
86 | api-test:
87 | build:
88 | context: .
89 | dockerfile: Dockerfile
90 | profiles:
91 | - integration-tests
92 | hostname: api-tests
93 | environment:
94 | PORT: ${API_PORT}
95 | ENVIRONMENT: ${ENVIRONMENT}
96 | MIGRATIONS_PATH: ${MIGRATIONS_PATH}
97 | INTEGRATION: true
98 | ports:
99 | - "${API_PORT}:${API_PORT}"
100 | depends_on:
101 | cache-test:
102 | condition: service_healthy
103 | db-test:
104 | condition: service_healthy
105 | env_file:
106 | - .env.example
107 | - .env
108 | networks:
109 | - pet-dex-integration-tests
110 |
111 | cache-test:
112 | <<: *cache
113 | profiles:
114 | - integration-tests
115 | hostname: cache-test
116 | healthcheck:
117 | test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
118 | start_period: 1m
119 | interval: 1m
120 | timeout: 10s
121 | retries: 3
122 | volumes:
123 | - ./cache:/data-teste
124 | env_file:
125 | - .env.example
126 | networks:
127 | - pet-dex-integration-tests
128 |
129 | db-test:
130 | <<: *db
131 | depends_on:
132 | cache-test:
133 | condition: service_healthy
134 | profiles:
135 | - integration-tests
136 | volumes:
137 | - ./data-teste:/var/lib/mysql
138 | healthcheck:
139 | test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
140 | start_period: 1m
141 | interval: 1m
142 | timeout: 10s
143 | retries: 3
144 | env_file:
145 | - .env.example
146 | - .env
147 | networks:
148 | - pet-dex-integration-tests
149 |
--------------------------------------------------------------------------------
/docs/database/migration.md:
--------------------------------------------------------------------------------
1 |
2 | # Migration
3 |
4 | ## Sumário
5 |
6 | 1. [Dependências](#dependências)
7 |
8 | 2. [Novas Migrations](#criando-novas-migrations)
9 |
10 | 3. [Executando Migrations](#executando-as-migrations)
11 |
12 | ### Dependências
13 |
14 | As migrations utilizam a CLI do Golang Migrate, sendo assim é necessário instalar em seu ambiente
15 | de desenvolvimento:
16 |
17 | * [Golang Migrate Instalação](https://github.com/golang-migrate/migrate/blob/master/cmd/migrate/README.md)
18 |
19 | Também é necessário ter o GNU Make instalado para executar os comandos das migrations:
20 |
21 | * [Make](https://www.gnu.org/software/make/)
22 |
23 | ### Criando novas migrations
24 |
25 | Para criar novos arquivos de migrations, execute o comando abaixo:
26 |
27 | ```bash
28 | make create-migrations
29 | ```
30 |
31 | Ele criará na pasta `migrations` dois arquivos, sendo um para **UP** e outro para **DOWN**.
32 | No primeiro você digitará o código `sql` para executar a mudança necessária no banco (incluir uma nova tabela,
33 | uma coluna nova e etc), já para o DOWN você escreverá o necessário para reverter essas mudanças caso seja preciso.
34 |
35 | ### Executando as migrations
36 |
37 | Para executar as migrations previamente criadas é necessário rodar um dos dois comandos abaixo:
38 |
39 | ```bash
40 | make run-migrations-up
41 | ```
42 | ```bash
43 | make run-migrations-down
44 | ```
45 |
46 | Como já deve ter percebido o primeiro comando executa as mudanças desejadas e o segundo reverte essas mudanças.
--------------------------------------------------------------------------------
/docs/infra/infra.md:
--------------------------------------------------------------------------------
1 |
2 | # Infra
3 |
4 | ## Sumário
5 |
6 | 1. [Logger](#logger)
7 | 1. [Funcionalidade](#funcionalidade)
8 | 2. [Como usar](#como-usar)
9 |
10 | ### Logger
11 |
12 | path: `infra/config/logger.go`
13 |
14 | ##### Funcionalidade
15 |
16 | Ferramenta para registrar mensagens durante a execução do programa.
17 | Utilidades:
18 |
19 | 1. Registro de eventos
20 | 2. Depuração
21 | 3. Monitoramento
22 |
23 | ##### Como usar
24 |
25 | Para instanciar o logger em algum lugar da aplicação é necessário usar o package `config`. Ao topo do arquivo, declare o logger com o escopo do arquivo, vamos considerar um controller:
26 |
27 | ```go
28 | //
29 |
30 | package controllers
31 |
32 | import (
33 | "pet-dex-backend/v2/infra/config"
34 | ... //imports
35 | )
36 |
37 | var logger = config.GetLogger("pet-controller") // Instânciando o logger
38 |
39 | ... // controller
40 |
41 | func (pc *PetController) Create(w http.ResponseWriter, r *http.Request){
42 | ...
43 | logger.Error(err) // Utilizando
44 | logger.Info("Olha lá")
45 | }
46 | ```
47 |
48 | > É necessário passar o contexto, o logger vai ser executado pelo parâmetro da função
49 |
50 | O logger tem alguns métodos:
51 |
52 | - Debug - Debugf
53 | - Info - Infof
54 | - Warn - Warnf
55 | - Error - Errorf
56 |
--------------------------------------------------------------------------------
/entity/address.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "pet-dex-backend/v2/entity/dto"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | )
7 |
8 | type Address struct {
9 | ID uniqueEntityId.ID `json:"id" db:"id"`
10 | UserId uniqueEntityId.ID `json:"userId" db:"userId"`
11 | Address string `json:"address" db:"address"`
12 | City string `json:"city" db:"city"`
13 | State string `json:"state" db:"state"`
14 | Latitude float64 `json:"latitude" db:"latitude"`
15 | Longitude float64 `json:"longitude" db:"longitude"`
16 | }
17 |
18 | func NewAddress(address dto.AddressInsertDto) *Address {
19 | return &Address{
20 | ID: uniqueEntityId.NewID(),
21 | UserId: address.UserId,
22 | Address: "",
23 | City: address.City,
24 | State: address.State,
25 | Latitude: address.Latitude,
26 | Longitude: address.Longitude,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/entity/breed.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import "pet-dex-backend/v2/pkg/uniqueEntityId"
4 |
5 | type Breed struct {
6 | ID uniqueEntityId.ID `json:"id"`
7 | Name string `json:"name"`
8 | Specie string `json:"specie"`
9 | Size string `json:"size"`
10 | Description string `json:"description"`
11 | Height string `json:"height"`
12 | Weight string `json:"weight"`
13 | PhysicalChar string `json:"physical_char"`
14 | Disposition string `json:"disposition"`
15 | IdealFor string `json:"ideal_for"`
16 | Fur string `json:"fur"`
17 | ImgUrl string `json:"img_url"`
18 | Weather string `json:"weather"`
19 | Dressage string `json:"dressage"`
20 | OrgID string `json:"org_id"`
21 | LifeExpectancy string `json:"life_expectancy"`
22 | }
23 |
--------------------------------------------------------------------------------
/entity/dto/address_insert_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "pet-dex-backend/v2/pkg/uniqueEntityId"
5 | )
6 |
7 | type AddressInsertDto struct {
8 | UserId uniqueEntityId.ID `json:"userId" db:"userId"`
9 | Address string `json:"address" db:"address"`
10 | City string `json:"city" db:"city"`
11 | State string `json:"state" db:"state"`
12 | Latitude float64 `json:"latitude" db:"latitude"`
13 | Longitude float64 `json:"longitude" db:"longitude"`
14 | }
15 |
--------------------------------------------------------------------------------
/entity/dto/breed_list.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import "pet-dex-backend/v2/pkg/uniqueEntityId"
4 |
5 | type BreedList struct {
6 | ID uniqueEntityId.ID `json:"id" example:"0e0b8399-1bf1-4ed5-a2f4-b5789ddf5df0"`
7 | Name string `json:"name" example:"Pastor Alemão"`
8 | ImgUrl string `json:"img_url" example:"https://images.unsplash.com/photo-1530281700549-e82e7bf110d6?q=80&w=1888&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"`
9 | }
10 |
11 | func (breed *BreedList) Validate() bool {
12 | return (breed.Name != "" &&
13 | breed.ImgUrl != "")
14 | }
15 |
--------------------------------------------------------------------------------
/entity/dto/link_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | type LinkDto struct {
4 | URL string `json:"url" example:"https://www.facebook.com/"`
5 | Text string `json:"text" example:"Facebook da Ong"`
6 | }
7 |
--------------------------------------------------------------------------------
/entity/dto/ong_delete_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "pet-dex-backend/v2/pkg/uniqueEntityId"
5 | )
6 |
7 | type OngDeleteDto struct {
8 | ID uniqueEntityId.ID `json:"id"`
9 | }
10 |
--------------------------------------------------------------------------------
/entity/dto/ong_insert_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type OngInsertDto struct {
8 | User UserInsertDto
9 | OpeningHours string `json:"openingHours"`
10 | AdoptionPolicy string `json:"adoptionPolicy"`
11 | BirthDate *time.Time `json:"birthdate"`
12 | Links []Link `json:"links"`
13 | CreatedAt *time.Time `json:"createdAt" db:"created_at"`
14 | UpdatedAt *time.Time `json:"updatedAt" db:"updated_at"`
15 | }
16 |
17 | type Link struct {
18 | URL string
19 | Text string
20 | }
21 |
--------------------------------------------------------------------------------
/entity/dto/ong_list_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "pet-dex-backend/v2/pkg/uniqueEntityId"
5 | )
6 |
7 | type OngListMapper struct {
8 | ID uniqueEntityId.ID `json:"id" db:"id"`
9 | UserID uniqueEntityId.ID `json:"userId" db:"userId"`
10 | Name string `json:"name" db:"name"`
11 | Address string `json:"address" db:"address"`
12 | City string `json:"city" db:"city"`
13 | State string `json:"state" db:"state"`
14 | Phone string `json:"phone" db:"phone"`
15 | OpeningHours string `json:"openingHours" db:"openingHours"`
16 | AdoptionPolicy string `json:"adoptionPolicy" db:"adoptionPolicy"`
17 | Links string `json:"links" db:"links"`
18 | }
19 |
--------------------------------------------------------------------------------
/entity/dto/ong_update_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | type OngUpdateDto struct {
4 | Phone string `json:"phone" db:"phone" example:"119596995887"`
5 | User UserUpdateDto
6 | OpeningHours string `json:"openingHours" example:"08:00"`
7 | AdoptionPolicy string `json:"adoptionPolicy" example:"não pode rato"`
8 | Links []LinkDto `json:"links"`
9 | }
10 |
--------------------------------------------------------------------------------
/entity/dto/pet_insert_dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "errors"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | "regexp"
7 | "time"
8 | )
9 |
10 | var notEmptyRegex = regexp.MustCompile(`^\S+$`)
11 |
12 | var nameRegex = regexp.MustCompile(`^[a-zA-Z0-9\s]+$`)
13 |
14 | var sizeRegex = regexp.MustCompile(`^(small|medium|large|giant)$`)
15 |
16 | type PetInsertDto struct {
17 | Name string `json:"name" example:"Thor"`
18 | UserID uniqueEntityId.ID `json:"user_id" example:"fa1b8ae8-5351-11ef-8f02-0242ac130003"`
19 | BreedID uniqueEntityId.ID `json:"breed_id" example:"0e0b8399-1bf1-4ed5-a2f4-b5789ddf5df0"`
20 | AdoptionDate *time.Time `json:"adoption_date" example:"2008-01-02T15:04:05Z"`
21 | Birthdate *time.Time `json:"birthdate" example:"2006-01-02T15:04:05Z"`
22 | Weight float64 `json:"weight" example:"4.1"`
23 | Size string `json:"size" example:"medium"`
24 | }
25 |
26 | func (p *PetInsertDto) Validate() error {
27 | if !nameRegex.MatchString(p.Name) {
28 | return errors.New("name cannot be empty")
29 | }
30 | if len(p.Name) > 80 {
31 | return errors.New("name cannot exceed 80 characters")
32 | }
33 | if p.Name == "" {
34 | return errors.New("name cannot be empty")
35 | }
36 | if !sizeRegex.MatchString(p.Size) {
37 | return errors.New("size can be only small, medium, large or giant")
38 | }
39 | if !notEmptyRegex.MatchString(p.UserID.String()) {
40 | return errors.New("UserID cannot be empty")
41 | }
42 | if !notEmptyRegex.MatchString(p.BreedID.String()) {
43 | return errors.New("BreedID cannot be empty")
44 | }
45 | if p.Weight < 0 {
46 | return errors.New("weight cannot be negative")
47 | }
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/entity/dto/pet_update.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "pet-dex-backend/v2/pkg/uniqueEntityId"
5 | "time"
6 | )
7 |
8 | type PetUpdateDto struct {
9 | Name string `json:"name" example:"Spike"`
10 | Size string `json:"size" example:"small"`
11 | Weight float64 `json:"weight" example:"4.8"`
12 | WeightMeasure string `json:"weight_measure" example:"kg"`
13 | AdoptionDate time.Time `json:"adoption_date" example:"2008-01-02T00:00:00Z"`
14 | Birthdate time.Time `json:"birthdate" example:"2006-01-02T00:00:00Z"`
15 | Comorbidity string `json:"comorbidity" example:"asma"`
16 | Tags string `json:"tags" example:"Dog"`
17 | Castrated *bool `json:"castrated" example:"true"`
18 | AvailableToAdoption *bool `json:"available_to_adoption" example:"true"`
19 | BreedID uniqueEntityId.ID `json:"breed_id" example:"0e0b8399-1bf1-4ed5-a2f4-b5789ddf5df0"`
20 | Vaccines []VaccinesDto `json:"vaccines"`
21 | NeedSpecialCare SpecialCareDto `json:"special_care"`
22 | }
23 |
24 | type VaccinesDto struct {
25 | Name string `json:"name" example:"PetVax"`
26 | Date time.Time `json:"date" example:"2007-01-02T00:00:00Z"`
27 | DoctorCRM string `json:"doctor_crm" example:"000000"`
28 | }
29 |
30 | type SpecialCareDto struct {
31 | Needed *bool `json:"neededSpecialCare" example:"true"`
32 | Description string `json:"descriptionSpecialCare" example:"obesity"`
33 | }
34 |
--------------------------------------------------------------------------------
/entity/dto/user_change_password.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "errors"
5 | "pet-dex-backend/v2/pkg/utils"
6 | )
7 |
8 | type UserChangePasswordDto struct {
9 | OldPassword string `json:"oldPassword"`
10 | NewPassword string `json:"newPassword"`
11 | NewPasswordAgain string `json:"newPasswordAgain"`
12 | }
13 |
14 | func (u *UserChangePasswordDto) Validate() error {
15 | if u.NewPassword == "" {
16 | return errors.New("password cannot be empty")
17 | }
18 | if u.OldPassword == u.NewPassword {
19 | return errors.New("old password cannot be the same as new password")
20 | }
21 | if u.NewPassword != u.NewPasswordAgain {
22 | return errors.New("new passwords do not match")
23 | }
24 |
25 | if !utils.IsValidPassword(u.NewPassword) {
26 | return errors.New("new password must be at least 6 characters long and contain at least one uppercase letter and one special character")
27 | }
28 | return nil
29 | }
30 |
--------------------------------------------------------------------------------
/entity/dto/user_change_password.dto_test.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestChangePasswordValidate(t *testing.T) {
11 | cases := map[string]struct {
12 | userChangePassword UserChangePasswordDto
13 | expected error
14 | }{
15 | "Valid Password": {
16 | userChangePassword: UserChangePasswordDto{
17 | OldPassword: "oldPassword123!",
18 | NewPassword: "NewPassword123!",
19 | NewPasswordAgain: "NewPassword123!",
20 | },
21 | expected: nil,
22 | },
23 | "Empty New Password": {
24 | userChangePassword: UserChangePasswordDto{
25 | OldPassword: "oldPassword123!",
26 | NewPassword: "",
27 | NewPasswordAgain: "NewPassword123!",
28 | },
29 | expected: errors.New("password cannot be empty"),
30 | },
31 | "New Password is equal to Old Password": {
32 | userChangePassword: UserChangePasswordDto{
33 | OldPassword: "oldPassword123!",
34 | NewPassword: "oldPassword123!",
35 | NewPasswordAgain: "NewPassword123!",
36 | },
37 | expected: errors.New("old password cannot be the same as new password"),
38 | },
39 | "New Passwords does not match": {
40 | userChangePassword: UserChangePasswordDto{
41 | OldPassword: "oldPassword123!",
42 | NewPassword: "NewPassword123!",
43 | NewPasswordAgain: "DifferentNewPassword123!",
44 | },
45 | expected: errors.New("new passwords do not match"),
46 | },
47 | "New Password does not match the security requirements": {
48 | userChangePassword: UserChangePasswordDto{
49 | OldPassword: "oldPassword123!",
50 | NewPassword: "password123!",
51 | NewPasswordAgain: "password123!",
52 | },
53 | expected: errors.New("new password must be at least 6 characters long and contain at least one uppercase letter and one special character"),
54 | },
55 | }
56 |
57 | for name, test := range cases {
58 | t.Run(name, func(t *testing.T) {
59 | result := test.userChangePassword.Validate()
60 | assert.Equal(t, test.expected, result, test.expected)
61 | })
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/entity/dto/user_insert.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "fmt"
5 | "net/mail"
6 | "pet-dex-backend/v2/pkg/utils"
7 | "slices"
8 | "time"
9 | )
10 |
11 | var userTypes = []string{"juridica", "fisica"}
12 |
13 | type UserInsertDto struct {
14 | Name string `json:"name" example:"Claúdio"`
15 | Type string `json:"type" example:"fisica"`
16 | Document string `json:"document" example:"12345678900"`
17 | AvatarURL string `json:"avatar_url" example:"https://example.com/avatar.jpg"`
18 | Email string `json:"email" example:"claudio@example.com"`
19 | Phone string `json:"phone" example:"21912345678"`
20 | Pass string `json:"pass" example:"Senhasegur@123"`
21 | BirthDate *time.Time `json:"birthdate" example:"2006-01-02T15:04:05Z"`
22 | City string `json:"city" example:"São Paulo"`
23 | State string `json:"state" example:"São Paulo"`
24 | Role string `json:"role" example:"developer"`
25 | }
26 |
27 | func (u *UserInsertDto) Validate() error {
28 | if u.Name == "" {
29 | return fmt.Errorf("invalid name")
30 | }
31 |
32 | _, err := mail.ParseAddress(u.Email)
33 |
34 | if err != nil {
35 | return fmt.Errorf("invalid email")
36 | }
37 |
38 | if !slices.Contains(userTypes, u.Type) {
39 | return fmt.Errorf("type can only be 'juridica' or 'fisica'")
40 | }
41 |
42 | if u.Pass == "" {
43 | return fmt.Errorf("password cannot be empty")
44 | }
45 |
46 | if !utils.IsValidPassword(u.Pass) {
47 | return fmt.Errorf("invalid password format")
48 | }
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/entity/dto/user_login.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import "errors"
4 |
5 | type UserLoginDto struct {
6 | Email string `json:"email"`
7 | Password string `json:"password"`
8 | }
9 |
10 | func (u *UserLoginDto) Validate() error {
11 | if u.Email == "" {
12 | return errors.New("email cannot be empty")
13 | }
14 | if u.Password == "" {
15 | return errors.New("password cannot be empty")
16 | }
17 | return nil
18 | }
19 |
--------------------------------------------------------------------------------
/entity/dto/user_push_notification_enabled.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | type UserPushNotificationEnabled struct {
4 | PushNotificationEnabled bool `json:"pushNotificationsEnabled"`
5 | }
6 |
--------------------------------------------------------------------------------
/entity/dto/user_sso.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | type UserSSODto struct {
4 | Name string `json:"name"`
5 | Email string `json:"email"`
6 | }
7 |
--------------------------------------------------------------------------------
/entity/dto/user_update.dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type UserUpdateDto struct {
8 | Name string `json:"name" db:"name"`
9 | Document string `json:"document" db:"document"`
10 | AvatarURL string `json:"avatar_url" db:"avatarUrl"`
11 | Email string `json:"email" db:"email"`
12 | Phone string `json:"phone" db:"phone"`
13 | Role string `json:"role"`
14 | BirthDate *time.Time `json:"birthdate"`
15 | PushNotificationsEnabled *bool `json:"pushNotificationsEnabled" db:"pushNotificationsEnabled"`
16 | }
17 |
--------------------------------------------------------------------------------
/entity/ong.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "pet-dex-backend/v2/entity/dto"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | "time"
7 | )
8 |
9 | type Ong struct {
10 | ID uniqueEntityId.ID `json:"id" db:"id"`
11 | UserID uniqueEntityId.ID `db:"userId"`
12 | User User `json:"user"`
13 | Phone string `json:"phone" db:"phone"`
14 | OpeningHours string `json:"openingHours" db:"openingHours"`
15 | AdoptionPolicy string `json:"adoptionPolicy" db:"adoptionPolicy"`
16 | Links []dto.LinkDto `json:"links"`
17 |
18 | CreatedAt *time.Time `json:"createdAt" db:"created_at"`
19 | UpdatedAt *time.Time `json:"updatedAt" db:"updated_at"`
20 | DeletedAt *time.Time `json:"deletedAt" db:"deleted_at"`
21 | }
22 |
23 | func NewOng(ong dto.OngInsertDto) *Ong {
24 | ongId := uniqueEntityId.NewID()
25 |
26 | user := NewUser(ong.User)
27 |
28 | return &Ong{
29 | ID: ongId,
30 | UserID: user.ID,
31 | User: *user,
32 | Phone: user.Phone,
33 | Links: []dto.LinkDto{},
34 | OpeningHours: ong.OpeningHours,
35 | AdoptionPolicy: ong.AdoptionPolicy,
36 | }
37 | }
38 |
39 | func OngToUpdate(ong dto.OngUpdateDto) (*Ong, error) {
40 | user := User{
41 | Name: ong.User.Name,
42 | Document: ong.User.Document,
43 | AvatarURL: ong.User.AvatarURL,
44 | Email: ong.User.Email,
45 | Phone: ong.User.Phone,
46 | BirthDate: ong.User.BirthDate,
47 | }
48 |
49 | return &Ong{
50 | User: user,
51 | Phone: user.Phone,
52 | Links: ong.Links,
53 | OpeningHours: ong.OpeningHours,
54 | AdoptionPolicy: ong.AdoptionPolicy,
55 | }, nil
56 | }
57 |
--------------------------------------------------------------------------------
/entity/pet.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "time"
5 |
6 | "pet-dex-backend/v2/entity/dto"
7 | "pet-dex-backend/v2/pkg/uniqueEntityId"
8 | )
9 |
10 | type Pet struct {
11 | ID uniqueEntityId.ID `json:"id"`
12 | UserID uniqueEntityId.ID `json:"user_id" db:"userId"`
13 | BreedID uniqueEntityId.ID `json:"breed_id" db:"breedId"`
14 | Name string `json:"name"`
15 | Size string `json:"size"`
16 | Weight float64 `json:"weight"`
17 | WeightMeasure string `json:"weight_measure"`
18 | AdoptionDate time.Time `json:"adoption_date" db:"adoptionDate"`
19 | Birthdate time.Time `json:"birthdate"`
20 | Comorbidity string `json:"comorbidity"`
21 | Tags string `json:"tags"`
22 | Castrated *bool `json:"castrated"`
23 | AvailableToAdoption *bool `json:"available_to_adoption"`
24 | BreedName string `json:"breed_name"`
25 | ImageUrl string `json:"image_url"`
26 | Vaccines []Vaccines `json:"vaccines"`
27 | NeedSpecialCare SpecialCare `json:"special_care"`
28 | }
29 |
30 | type Vaccines struct {
31 | ID uniqueEntityId.ID `json:"id"`
32 | PetID uniqueEntityId.ID `json:"pet_id"`
33 | Name string `json:"name"`
34 | Date time.Time `json:"date"`
35 | DoctorCRM string `json:"doctor_crm"`
36 | }
37 |
38 | type SpecialCare struct {
39 | Needed *bool `json:"neededSpecialCare"`
40 | Description string `json:"descriptionSpecialCare"`
41 | }
42 |
43 | func NewPet(userId, breedId uniqueEntityId.ID, size, name string, weight float64, adoptionDate, birthdate *time.Time) *Pet {
44 | petId := uniqueEntityId.NewID()
45 |
46 | return &Pet{
47 | ID: petId,
48 | UserID: userId,
49 | BreedID: breedId,
50 | Size: size,
51 | Name: name,
52 | Weight: weight,
53 | AdoptionDate: *adoptionDate,
54 | Birthdate: *birthdate,
55 | }
56 | }
57 |
58 | func PetToEntity(dto *dto.PetUpdateDto) *Pet {
59 | vaccines := make([]Vaccines, len(dto.Vaccines))
60 | for i, v := range dto.Vaccines {
61 | vaccines[i] = Vaccines{
62 | Name: v.Name,
63 | Date: v.Date,
64 | DoctorCRM: v.DoctorCRM,
65 | }
66 | }
67 | specialCare := SpecialCare{
68 | Needed: dto.NeedSpecialCare.Needed,
69 | Description: dto.NeedSpecialCare.Description,
70 | }
71 |
72 | return &Pet{
73 | Name: dto.Name,
74 | Size: dto.Size,
75 | Weight: dto.Weight,
76 | WeightMeasure: dto.WeightMeasure,
77 | AdoptionDate: dto.AdoptionDate,
78 | Birthdate: dto.Birthdate,
79 | Comorbidity: dto.Comorbidity,
80 | Tags: dto.Tags,
81 | Castrated: dto.Castrated,
82 | AvailableToAdoption: dto.AvailableToAdoption,
83 | BreedID: dto.BreedID,
84 | Vaccines: vaccines,
85 | NeedSpecialCare: specialCare,
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/entity/user.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "pet-dex-backend/v2/entity/dto"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | "time"
7 | )
8 |
9 | type User struct {
10 | ID uniqueEntityId.ID `json:"id" db:"id"`
11 | Name string `json:"name" db:"name"`
12 | Type string `json:"type" db:"type"`
13 | Document string `json:"document" db:"document"`
14 | AvatarURL string `json:"avatar_url" db:"avatarUrl"`
15 | Email string `json:"email" db:"email"`
16 | Phone string `json:"phone" db:"phone"`
17 | Pass string `json:"pass" db:"pass"`
18 | PushNotificationsEnabled *bool `json:"pushNotificationsEnabled" db:"pushNotificationsEnabled"`
19 | BirthDate *time.Time `json:"birthdate"`
20 | Role string `json:"role" db:"role"`
21 | CreatedAt *time.Time `json:"createdAt" db:"created_at"`
22 | UpdatedAt *time.Time `json:"updatedAt" db:"updated_at"`
23 | Adresses Address `json:"addresses"`
24 | }
25 |
26 | func NewUser(user dto.UserInsertDto) *User {
27 | userId := uniqueEntityId.NewID()
28 |
29 | var addressDto dto.AddressInsertDto
30 | addressDto.UserId = userId
31 | addressDto.City = user.City
32 | addressDto.State = user.State
33 |
34 | address := NewAddress(addressDto)
35 |
36 | return &User{
37 | ID: userId,
38 | Name: user.Name,
39 | Type: user.Type,
40 | Document: user.Document,
41 | AvatarURL: user.AvatarURL,
42 | Email: user.Email,
43 | Phone: user.Phone,
44 | Pass: user.Pass,
45 | BirthDate: user.BirthDate,
46 | Role: user.Role,
47 | Adresses: *address,
48 | }
49 | }
50 |
51 | func UserToUpdate(dto dto.UserUpdateDto) *User {
52 | user := &User{
53 | Name: dto.Name,
54 | Document: dto.Document,
55 | AvatarURL: dto.AvatarURL,
56 | Email: dto.Email,
57 | Phone: dto.Phone,
58 | BirthDate: dto.BirthDate,
59 | Role: dto.Role,
60 | }
61 |
62 | if dto.BirthDate == nil {
63 | user.BirthDate = nil
64 | }
65 |
66 | return user
67 | }
68 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module pet-dex-backend/v2
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/go-chi/cors v1.2.1
7 | github.com/golang-jwt/jwt v3.2.2+incompatible
8 | github.com/golang-migrate/migrate/v4 v4.17.1
9 | github.com/google/uuid v1.4.0
10 | github.com/huandu/facebook/v2 v2.7.3
11 | github.com/jmoiron/sqlx v1.3.5
12 | github.com/stretchr/testify v1.9.0
13 | github.com/swaggo/http-swagger v1.3.4
14 | github.com/swaggo/swag v1.16.3
15 | golang.org/x/oauth2 v0.21.0
16 | )
17 |
18 | require (
19 | github.com/stretchr/objx v0.5.2 // indirect
20 | golang.org/x/crypto v0.25.0
21 | )
22 |
23 | require (
24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
25 | github.com/go-chi/chi/v5 v5.0.11
26 | github.com/go-sql-driver/mysql v1.7.1
27 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
28 | github.com/spf13/viper v1.18.2
29 | )
30 |
31 | require (
32 | cloud.google.com/go/compute/metadata v0.3.0 // indirect
33 | github.com/KyleBanks/depth v1.2.1 // indirect
34 | github.com/fsnotify/fsnotify v1.7.0 // indirect
35 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
36 | github.com/go-openapi/jsonreference v0.21.0 // indirect
37 | github.com/go-openapi/spec v0.21.0 // indirect
38 | github.com/go-openapi/swag v0.23.0 // indirect
39 | github.com/hashicorp/errwrap v1.1.0 // indirect
40 | github.com/hashicorp/go-multierror v1.1.1 // indirect
41 | github.com/hashicorp/hcl v1.0.0 // indirect
42 | github.com/josharian/intern v1.0.0 // indirect
43 | github.com/magiconair/properties v1.8.7 // indirect
44 | github.com/mailru/easyjson v0.7.7 // indirect
45 | github.com/mitchellh/mapstructure v1.5.0 // indirect
46 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect
47 | github.com/rogpeppe/go-internal v1.12.0 // indirect
48 | github.com/sagikazarmark/locafero v0.4.0 // indirect
49 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
50 | github.com/sourcegraph/conc v0.3.0 // indirect
51 | github.com/spf13/afero v1.11.0 // indirect
52 | github.com/spf13/cast v1.6.0 // indirect
53 | github.com/spf13/pflag v1.0.5 // indirect
54 | github.com/subosito/gotenv v1.6.0 // indirect
55 | github.com/swaggo/files v1.0.1 // indirect
56 | go.uber.org/atomic v1.9.0 // indirect
57 | go.uber.org/multierr v1.9.0 // indirect
58 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
59 | golang.org/x/net v0.27.0 // indirect
60 | golang.org/x/sys v0.22.0 // indirect
61 | golang.org/x/text v0.16.0 // indirect
62 | golang.org/x/tools v0.23.0 // indirect
63 | gopkg.in/ini.v1 v1.67.0 // indirect
64 | gopkg.in/yaml.v3 v3.0.1 // indirect
65 | )
66 |
--------------------------------------------------------------------------------
/infra/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | _ "github.com/go-sql-driver/mysql"
5 | )
6 |
7 | var (
8 | // db *sql.DB
9 | logger *Logger
10 | StandardDateLayout = "2006-01-02"
11 | )
12 |
13 | // func InitConfigs() error {
14 | // var err error
15 | // env := GetEnvConfig()
16 |
17 | // db, err = sql.Open("mysql", env.DBUrl)
18 | // if err != nil {
19 | // panic(err)
20 | // }
21 |
22 | // return nil
23 | // }
24 |
25 | // func GetDB() *sql.DB {
26 | // return db
27 | // }
28 |
29 | func GetLogger(p string) *Logger {
30 | logger = NewLogger(p)
31 | return logger
32 | }
33 |
--------------------------------------------------------------------------------
/infra/config/env.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/spf13/viper"
4 |
5 | var env *Envconfig
6 |
7 | type Envconfig struct {
8 | DB_USER string `mapstructure:"DB_USER"`
9 | DB_PASSWORD string `mapstructure:"DB_PASSWORD"`
10 | DB_DATABASE string `mapstructure:"DB_DATABASE"`
11 | DB_HOST string `mapstructure:"DB_HOST"`
12 | DB_PORT string `mapstructure:"DB_PORT"`
13 | API_PORT string `mapstructure:"API_PORT"`
14 | ENV string `mapstructure:"ENVIRONMENT"`
15 | MIGRATIONS_PATH string `mapstructure:"MIGRATIONS_PATH"`
16 | JWT_SECRET string `mapstructure:"JWT_SECRET"`
17 | GOOGLE_OAUTH_CLIENT_ID string `mapstructure:"GOOGLE_OAUTH_CLIENT_ID"`
18 | GOOGLE_OAUTH_CLIENT_SECRET string `mapstructure:"GOOGLE_OAUTH_CLIENT_SECRET"`
19 | GOOGLE_REDIRECT_URL string `mapstructure:"GOOGLE_REDIRECT_URL"`
20 | FACEBOOK_APP_ID string `mapstructure:"FACEBOOK_APP_ID"`
21 | FACEBOOK_APP_SECRET string `mapstructure:"FACEBOOK_APP_SECRET"`
22 | MIGRATION_HOST string `mapstructure:"MIGRATION_HOST"`
23 | }
24 |
25 | func GetEnvConfig() *Envconfig {
26 | return env
27 | }
28 |
29 | func LoadEnv(path string) (*Envconfig, error) {
30 | viper.SetConfigName("app_config")
31 | viper.SetConfigType("env")
32 | viper.AddConfigPath(path)
33 | viper.SetConfigFile(".env")
34 | viper.AutomaticEnv()
35 |
36 | err := viper.ReadInConfig()
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | err = viper.Unmarshal(&env)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | return env, nil
47 | }
48 |
--------------------------------------------------------------------------------
/infra/config/logger.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "io"
5 | "log"
6 | "os"
7 | )
8 |
9 | type Logger struct {
10 | debug *log.Logger
11 | info *log.Logger
12 | warning *log.Logger
13 | err *log.Logger
14 | writer io.Writer
15 | }
16 |
17 | func NewLogger(p string) *Logger {
18 | writer := io.Writer(os.Stdout)
19 | logger := log.New(writer, p, log.Ldate|log.Ltime)
20 | return &Logger{
21 | debug: log.New(writer, "DEBUG: ", logger.Flags()),
22 | info: log.New(writer, "INFO: ", logger.Flags()),
23 | warning: log.New(writer, "WARNING: ", logger.Flags()),
24 | err: log.New(writer, "ERROR: ", logger.Flags()),
25 | writer: writer,
26 | }
27 | }
28 |
29 | func (l *Logger) Debug(v ...interface{}) {
30 | l.debug.Println(v...)
31 | }
32 | func (l *Logger) Info(v ...interface{}) {
33 | l.info.Println(v...)
34 | }
35 | func (l *Logger) Warn(v ...interface{}) {
36 | l.warning.Println(v...)
37 | }
38 | func (l *Logger) Error(v ...interface{}) {
39 | l.err.Println(v...)
40 | }
41 |
42 | func (l *Logger) Debugf(format string, v ...interface{}) {
43 | l.debug.Printf(format, v...)
44 | }
45 | func (l *Logger) Infof(format string, v ...interface{}) {
46 | l.info.Printf(format, v...)
47 | }
48 | func (l *Logger) Warnf(format string, v ...interface{}) {
49 | l.warning.Printf(format, v...)
50 | }
51 | func (l *Logger) Errorf(format string, v ...interface{}) {
52 | l.err.Printf(format, v...)
53 | }
54 |
--------------------------------------------------------------------------------
/infra/db/breed_repository.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "pet-dex-backend/v2/entity"
7 | "pet-dex-backend/v2/entity/dto"
8 | "pet-dex-backend/v2/infra/config"
9 | "pet-dex-backend/v2/interfaces"
10 | "pet-dex-backend/v2/pkg/uniqueEntityId"
11 |
12 | "github.com/jmoiron/sqlx"
13 | )
14 |
15 | var logger = config.GetLogger("breed-repository")
16 |
17 | type BreedRepository struct {
18 | dbconnection *sqlx.DB
19 | }
20 |
21 | func NewBreedRepository(dbconn *sqlx.DB) interfaces.BreedRepository {
22 | return &BreedRepository{
23 | dbconnection: dbconn,
24 | }
25 | }
26 |
27 | func (breedRepository *BreedRepository) List() (breeds []*dto.BreedList, err error) {
28 | rows, err := breedRepository.dbconnection.Query(`
29 | SELECT
30 | id,
31 | name,
32 | imgUrl
33 | FROM breeds`)
34 | if err != nil {
35 | logger.Error("error listing breeds", err)
36 | return nil, fmt.Errorf("error listing breeds: %w", err)
37 | }
38 | defer rows.Close()
39 |
40 | for rows.Next() {
41 | var breed dto.BreedList
42 | err := rows.Scan(
43 | &breed.ID,
44 | &breed.Name,
45 | &breed.ImgUrl,
46 | )
47 | if err != nil {
48 | logger.Error("error scanning breeds", err)
49 | return nil, fmt.Errorf("error scanning breeds: %w", err)
50 | }
51 | breeds = append(breeds, &breed)
52 | }
53 |
54 | return breeds, nil
55 | }
56 |
57 | func (breedRepository *BreedRepository) FindByID(ID uniqueEntityId.ID) (*entity.Breed, error) {
58 | row, err := breedRepository.dbconnection.Query(`
59 | SELECT
60 | id,
61 | name,
62 | specie,
63 | size,
64 | description,
65 | height,
66 | weight,
67 | physicalChar,
68 | disposition,
69 | idealFor,
70 | fur,
71 | imgUrl,
72 | weather,
73 | dressage,
74 | lifeExpectancy
75 | FROM breeds
76 | WHERE
77 | id = ?;`,
78 | ID,
79 | )
80 |
81 | if err != nil {
82 | return nil, fmt.Errorf("error retrieving breed %d: %w", ID, err)
83 | }
84 | defer row.Close()
85 |
86 | if !row.Next() {
87 | return nil, sql.ErrNoRows
88 | }
89 |
90 | var breed entity.Breed;
91 | err = row.Scan(&breed.ID, &breed.Name, &breed.Specie, &breed.Size, &breed.Description, &breed.Height, &breed.Weight, &breed.PhysicalChar, &breed.Disposition, &breed.IdealFor, &breed.Fur, &breed.ImgUrl, &breed.Weather, &breed.Dressage, &breed.LifeExpectancy)
92 | if err!= nil {
93 | return nil, fmt.Errorf("error scanning row into breed: %w", err)
94 | }
95 |
96 | if err := row.Err(); err != nil {
97 | return nil, fmt.Errorf("error iterating over breed rows: %w", err)
98 | }
99 |
100 | return &breed, nil
101 | }
--------------------------------------------------------------------------------
/infra/db/ong_repository.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity"
6 | "pet-dex-backend/v2/entity/dto"
7 | "pet-dex-backend/v2/infra/config"
8 | "pet-dex-backend/v2/interfaces"
9 | "pet-dex-backend/v2/pkg/uniqueEntityId"
10 | "time"
11 |
12 | "github.com/jmoiron/sqlx"
13 | )
14 |
15 | type OngRepository struct {
16 | dbconnection *sqlx.DB
17 | logger config.Logger
18 | }
19 |
20 | func NewOngRepository(db *sqlx.DB) interfaces.OngRepository {
21 | return &OngRepository{
22 | dbconnection: db,
23 | logger: *config.GetLogger("ong-repository"),
24 | }
25 | }
26 |
27 | func (or *OngRepository) Save(ong *entity.Ong) error {
28 |
29 | _, err := or.dbconnection.NamedExec("INSERT INTO legal_persons (id, userId, phone, links, openingHours, adoptionPolicy) VALUES (:id, :userId, :phone, :links, :openingHours, :adoptionPolicy)", &ong)
30 |
31 | if err != nil {
32 | logger.Error("error on ong repository: ", err)
33 | err = fmt.Errorf("error on saving ong")
34 | return err
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func (or *OngRepository) List(limit, offset int, sortBy, order string) (ongs []*dto.OngListMapper, err error) {
41 | query := fmt.Sprintf(`
42 | SELECT
43 | legal_persons.id,
44 | legal_persons.userId,
45 | legal_persons.phone,
46 | legal_persons.openingHours,
47 | legal_persons.links,
48 | users.name,
49 | addresses.address,
50 | addresses.city,
51 | addresses.state
52 | FROM
53 | legal_persons
54 | INNER JOIN
55 | users ON legal_persons.userId = users.id
56 | INNER JOIN
57 | addresses ON legal_persons.userId = addresses.userId
58 | ORDER BY
59 | %s %s
60 | LIMIT ? OFFSET ?`, sortBy, order)
61 | rows, err := or.dbconnection.Queryx(query, limit, offset)
62 | if err != nil {
63 | logger.Error("error listing ongs", err)
64 | return nil, fmt.Errorf("error listing ongs: %w", err)
65 | }
66 | defer rows.Close()
67 |
68 | for rows.Next() {
69 | var ong dto.OngListMapper
70 | err := rows.StructScan(&ong)
71 | if err != nil {
72 | logger.Error("error scanning ongs", err)
73 | return nil, fmt.Errorf("error scanning ongs: %w", err)
74 | }
75 | ongs = append(ongs, &ong)
76 | }
77 |
78 | return ongs, nil
79 | }
80 |
81 | func (or *OngRepository) FindByID(ID uniqueEntityId.ID) (*dto.OngListMapper, error) {
82 | var ong dto.OngListMapper
83 |
84 | query := `
85 | SELECT
86 | legal_persons.id,
87 | legal_persons.userId,
88 | legal_persons.phone,
89 | legal_persons.openingHours,
90 | legal_persons.links,
91 | users.name,
92 | addresses.address,
93 | addresses.city,
94 | addresses.state
95 | FROM
96 | legal_persons
97 | INNER JOIN
98 | users ON legal_persons.userId = users.id
99 | INNER JOIN
100 | addresses ON legal_persons.userId = addresses.userId
101 | WHERE
102 | legal_persons.id = ?`
103 |
104 | err := or.dbconnection.Get(&ong, query, ID)
105 |
106 | if err != nil {
107 | logger.Error("error on ong repository: ", err)
108 | err = fmt.Errorf("error retrieving ong %d: %w", ID, err)
109 | return nil, err
110 | }
111 |
112 | return &ong, nil
113 | }
114 |
115 | func (or *OngRepository) Update(id uniqueEntityId.ID, ongToUpdate entity.Ong) error {
116 |
117 | query := "UPDATE legal_persons SET"
118 | var values []interface{}
119 |
120 | if ongToUpdate.Phone != "" {
121 | query = query + " phone =?"
122 | values = append(values, ongToUpdate.Phone)
123 | }
124 |
125 | if ongToUpdate.OpeningHours != "" {
126 | query = query + " openingHours =?"
127 | values = append(values, ongToUpdate.OpeningHours)
128 | }
129 |
130 | if ongToUpdate.AdoptionPolicy != "" {
131 | query = query + " adoptionPolicy =?"
132 | values = append(values, ongToUpdate.AdoptionPolicy)
133 | }
134 |
135 | if len(ongToUpdate.Links) != 0 {
136 | query = query + " links =?"
137 | values = append(values, ongToUpdate.Links)
138 | }
139 |
140 | query = query + " updated_at =?,"
141 | values = append(values, time.Now())
142 |
143 | n := len(query)
144 | query = query[:n-1] + " WHERE id =?"
145 | values = append(values, id)
146 |
147 | fmt.Printf("Query to update: %s", query)
148 |
149 | _, err := or.dbconnection.Exec(query, values...)
150 |
151 | if err != nil {
152 | logger.Error("error on ong repository: ", err)
153 | err = fmt.Errorf("error on updating ong")
154 | return err
155 | }
156 |
157 | return nil
158 | }
159 |
160 | func (or *OngRepository) FindById(id uniqueEntityId.ID) (*entity.Ong, error) {
161 | return nil, nil
162 | }
163 |
164 | func (or *OngRepository) Delete(id uniqueEntityId.ID) error {
165 | query := "UPDATE legal_persons SET deleted_at = ? WHERE id = ?"
166 | _, err := or.dbconnection.Exec(query, time.Now(), id)
167 |
168 | if err != nil {
169 | or.logger.Error("error on ong repository: ", err)
170 | err = fmt.Errorf("error on soft deleting ong")
171 | return err
172 | }
173 |
174 | return nil
175 | }
176 |
--------------------------------------------------------------------------------
/infra/db/pet_repository.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "pet-dex-backend/v2/entity"
7 | "pet-dex-backend/v2/infra/config"
8 | "pet-dex-backend/v2/interfaces"
9 | "time"
10 |
11 | "github.com/google/uuid"
12 | "github.com/jmoiron/sqlx"
13 |
14 | "pet-dex-backend/v2/pkg/uniqueEntityId"
15 | )
16 |
17 | type PetRepository struct {
18 | dbconnection *sqlx.DB
19 | }
20 |
21 | func NewPetRepository(dbconn *sqlx.DB) interfaces.PetRepository {
22 | return &PetRepository{
23 | dbconnection: dbconn,
24 | }
25 | }
26 |
27 | func (pr *PetRepository) Save(petToSave *entity.Pet) error {
28 | _, err := pr.dbconnection.NamedExec("INSERT INTO pets (name, weight, size, adoptionDate, birthdate, breedId, userId) VALUES (:name, :weight, :size, :adoptionDate, :birthdate, :breedId, :userId)", &petToSave)
29 |
30 | if err != nil {
31 | err = fmt.Errorf("error saving pet: %w", err)
32 | fmt.Println(err)
33 | return err
34 | }
35 | return nil
36 | }
37 |
38 | func (pr *PetRepository) FindByID(ID uniqueEntityId.ID) (*entity.Pet, error) {
39 | row, err := pr.dbconnection.Query(`
40 | SELECT
41 | p.id,
42 | p.name,
43 | p.breedId,
44 | p.size,
45 | p.weight,
46 | p.adoptionDate,
47 | p.birthdate,
48 | p.comorbidity,
49 | p.tags,
50 | p.castrated,
51 | p.availableToAdoption,
52 | p.userId,
53 | p.neededSpecialCare,
54 | p.descriptionSpecialCare,
55 | b.name AS breed_name,
56 | pi.url AS pet_image_url
57 | FROM
58 | pets p
59 | JOIN breeds b ON p.breedId = b.id
60 | LEFT JOIN pets_image pi ON p.id = pi.petId
61 | WHERE
62 | p.id = ?`,
63 | ID,
64 | )
65 | if err != nil {
66 | return nil, fmt.Errorf("error retrieving pet %d: %w", ID, err)
67 | }
68 |
69 | defer row.Close()
70 |
71 | if !row.Next() {
72 | return nil, sql.ErrNoRows
73 | }
74 |
75 | var pet entity.Pet
76 | var adoptionDateStr string
77 | var birthdateStr string
78 |
79 | if err := row.Scan(
80 | &pet.ID,
81 | &pet.Name,
82 | &pet.BreedID,
83 | &pet.Size,
84 | &pet.Weight,
85 | &adoptionDateStr,
86 | &birthdateStr,
87 | &pet.Comorbidity,
88 | &pet.Tags,
89 | &pet.Castrated,
90 | &pet.AvailableToAdoption,
91 | &pet.UserID,
92 | &pet.NeedSpecialCare.Needed,
93 | &pet.NeedSpecialCare.Description,
94 | &pet.BreedName,
95 | &pet.ImageUrl,
96 | ); err != nil {
97 | return nil, fmt.Errorf("error scanning pet: %w", err)
98 | }
99 |
100 | if pet.AdoptionDate, err = time.Parse(config.StandardDateLayout, adoptionDateStr); err != nil {
101 | return nil, fmt.Errorf("error parsing adoptionDate: %w", err)
102 | }
103 | if pet.Birthdate, err = time.Parse(config.StandardDateLayout, birthdateStr); err != nil {
104 | return nil, fmt.Errorf("error parsing birthdate: %w", err)
105 | }
106 |
107 | if err := row.Err(); err != nil {
108 | return nil, fmt.Errorf("error iterating over pet rows: %w", err)
109 | }
110 |
111 | return &pet, nil
112 | }
113 | func (pr *PetRepository) Update(petID string, userID string, petToUpdate *entity.Pet) error {
114 |
115 | query := "UPDATE pets SET"
116 | values := []interface{}{}
117 |
118 | if petToUpdate.Name != "" {
119 | query = query + " name =?,"
120 | values = append(values, petToUpdate.Name)
121 | }
122 |
123 | if petToUpdate.BreedID != uuid.Nil {
124 | query = query + " breedId =?,"
125 | values = append(values, petToUpdate.BreedID)
126 | }
127 |
128 | if petToUpdate.Size != "" {
129 | query = query + " size =?,"
130 | values = append(values, petToUpdate.Size)
131 | }
132 |
133 | if petToUpdate.Weight != 0 {
134 | query = query + " weight =?,"
135 | values = append(values, petToUpdate.Weight)
136 | }
137 |
138 | if !petToUpdate.AdoptionDate.IsZero() {
139 | query = query + " adoptionDate = ?,"
140 | values = append(values, petToUpdate.AdoptionDate)
141 | }
142 |
143 | if !petToUpdate.Birthdate.IsZero() {
144 | query = query + " birthdate = ?,"
145 | values = append(values, petToUpdate.Birthdate)
146 | }
147 |
148 | if petToUpdate.Comorbidity != "" {
149 | query = query + " comorbidity = ?,"
150 | values = append(values, petToUpdate.Comorbidity)
151 | }
152 |
153 | if petToUpdate.Tags != "" {
154 | query = query + " tags = ?,"
155 | values = append(values, petToUpdate.Tags)
156 | }
157 |
158 | if petToUpdate.Castrated != nil {
159 | query = query + " castrated = ?,"
160 | values = append(values, petToUpdate.Castrated)
161 | }
162 |
163 | if petToUpdate.AvailableToAdoption != nil {
164 | query = query + " availableToAdoption = ?,"
165 | values = append(values, petToUpdate.AvailableToAdoption)
166 | }
167 |
168 | if petToUpdate.UserID != uuid.Nil {
169 | query = query + " userId = ?,"
170 | values = append(values, petToUpdate.UserID)
171 | }
172 |
173 | if petToUpdate.NeedSpecialCare.Needed != nil {
174 | query = query + " neededSpecialCare = ?,"
175 | values = append(values, petToUpdate.NeedSpecialCare.Needed)
176 | query = query + " descriptionSpecialCare = ?,"
177 | values = append(values, petToUpdate.NeedSpecialCare.Description)
178 | }
179 |
180 | n := len(query)
181 | query = query[:n-1] + " WHERE id =?"
182 | values = append(values, petID)
183 |
184 | _, err := pr.dbconnection.Exec(query, values...)
185 | if err != nil {
186 | return fmt.Errorf("error updating pet: %w \\n", err)
187 | }
188 |
189 | _, err = pr.dbconnection.Exec("DELETE FROM vaccines WHERE petId = ?", petID)
190 | if err != nil {
191 | return fmt.Errorf("error removing existing vaccines: %w", err)
192 | }
193 |
194 | for _, vaccine := range petToUpdate.Vaccines {
195 | _, err := pr.dbconnection.Exec(
196 | "INSERT INTO vaccines (id, petId, name, date, doctorCRM) VALUES (?, ?, ?, ?, ?)",
197 | uniqueEntityId.NewID(), petID, vaccine.Name, vaccine.Date, vaccine.DoctorCRM,
198 | )
199 | if err != nil {
200 | return fmt.Errorf("error adding new vaccine: %w", err)
201 | }
202 | }
203 | return nil
204 | }
205 |
206 | func (pr *PetRepository) ListByUser(userID uniqueEntityId.ID) (pets []*entity.Pet, err error) {
207 | rows, err := pr.dbconnection.Query(`
208 | SELECT
209 | p.id,
210 | p.name,
211 | p.breedId,
212 | p.size,
213 | p.weight,
214 | p.adoptionDate,
215 | p.birthdate,
216 | p.comorbidity,
217 | p.tags,
218 | p.castrated,
219 | p.availableToAdoption,
220 | p.userId,
221 | p.neededSpecialCare,
222 | p.descriptionSpecialCare,
223 | b.name AS breed_name,
224 | pi.url AS pet_image_url
225 | FROM
226 | pets p
227 | JOIN breeds b ON p.breedId = b.id
228 | LEFT JOIN pets_image pi ON p.id = pi.petId
229 | WHERE
230 | p.userId = ?`,
231 | userID,
232 | )
233 | if err != nil {
234 | return nil, fmt.Errorf("error retrieving pets for user %d: %w", userID, err)
235 | }
236 | defer rows.Close()
237 |
238 | for rows.Next() {
239 | var pet entity.Pet
240 | var adoptionDateStr string
241 | var birthdateStr string
242 | var needed bool
243 | var description string
244 |
245 | if err = rows.Scan(
246 | &pet.ID,
247 | &pet.Name,
248 | &pet.BreedID,
249 | &pet.Size,
250 | &pet.Weight,
251 | &adoptionDateStr,
252 | &birthdateStr,
253 | &pet.Comorbidity,
254 | &pet.Tags,
255 | &pet.Castrated,
256 | &pet.AvailableToAdoption,
257 | &pet.UserID,
258 | &pet.BreedName,
259 | &pet.NeedSpecialCare.Needed,
260 | &pet.NeedSpecialCare.Description,
261 | &pet.ImageUrl,
262 | ); err != nil {
263 | return nil, fmt.Errorf("error scanning pet row: %w", err)
264 | }
265 |
266 | if pet.AdoptionDate, err = time.Parse(config.StandardDateLayout, adoptionDateStr); err != nil {
267 | return nil, fmt.Errorf("error parsing adoptionDate: %w", err)
268 | }
269 | if pet.Birthdate, err = time.Parse(config.StandardDateLayout, birthdateStr); err != nil {
270 | return nil, fmt.Errorf("error parsing birthdate: %w", err)
271 | }
272 | pet.NeedSpecialCare = entity.SpecialCare{
273 | Needed: &needed,
274 | Description: description,
275 | }
276 |
277 | pets = append(pets, &pet)
278 | }
279 |
280 | if err := rows.Err(); err != nil {
281 | return nil, fmt.Errorf("error iterating over pet rows: %w", err)
282 | }
283 |
284 | return pets, nil
285 | }
286 |
287 | func (pr *PetRepository) ListAllByPage(page int) (pets []*entity.Pet, err error) {
288 | offset := (page - 1) * 12
289 | rows, err := pr.dbconnection.Query(`
290 | SELECT
291 | p.id,
292 | p.name,
293 | p.breedId,
294 | p.birthdate,
295 | p.availableToAdoption,
296 | b.name AS breed_name,
297 | pi.url AS pet_image_url
298 | FROM
299 | pets p
300 | JOIN breeds b ON p.breedId = b.id
301 | LEFT JOIN pets_image pi ON p.id = pi.petId
302 | LIMIT 12
303 | OFFSET ?`, offset)
304 |
305 | if err != nil {
306 | return nil, fmt.Errorf("error retrieving pets: %w", err)
307 | }
308 |
309 | defer rows.Close()
310 |
311 | for rows.Next() {
312 | var pet entity.Pet
313 | var birthdateStr string
314 |
315 | if err = rows.Scan(
316 | &pet.ID,
317 | &pet.Name,
318 | &pet.BreedID,
319 | &birthdateStr,
320 | &pet.AvailableToAdoption,
321 | &pet.BreedName,
322 | &pet.ImageUrl,
323 | ); err != nil {
324 | return nil, fmt.Errorf("error scanning pet row: %w", err)
325 | }
326 |
327 | if pet.Birthdate, err = time.Parse(config.StandardDateLayout, birthdateStr); err != nil {
328 | return nil, fmt.Errorf("error parsing birthdate: %w", err)
329 | }
330 |
331 | pets = append(pets, &pet)
332 | }
333 |
334 | if err := rows.Err(); err != nil {
335 | return nil, fmt.Errorf("error iterating over pet rows: %w", err)
336 | }
337 |
338 | return pets, nil
339 | }
340 |
--------------------------------------------------------------------------------
/infra/db/user_repository.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity"
6 | "pet-dex-backend/v2/infra/config"
7 | "pet-dex-backend/v2/interfaces"
8 | "pet-dex-backend/v2/pkg/uniqueEntityId"
9 | "time"
10 |
11 | "github.com/jmoiron/sqlx"
12 | )
13 |
14 | type UserRepository struct {
15 | dbconnection *sqlx.DB
16 | logger config.Logger
17 | }
18 |
19 | func NewUserRepository(dbconn *sqlx.DB) interfaces.UserRepository {
20 | return &UserRepository{
21 | dbconnection: dbconn,
22 | logger: *config.GetLogger("ong-repository"),
23 | }
24 | }
25 |
26 | func (ur *UserRepository) Delete(id uniqueEntityId.ID) error {
27 | _, err := ur.dbconnection.Exec("UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", id)
28 |
29 | if err != nil {
30 | ur.logger.Error("#UserRepository.Delete error: %w", err)
31 | return fmt.Errorf("error on delete user")
32 | }
33 |
34 | return nil
35 | }
36 |
37 | func (ur *UserRepository) Save(user *entity.User) error {
38 | _, err := ur.dbconnection.NamedExec("INSERT INTO users (id, name, type, document, avatarUrl, email, phone, pass, role) VALUES (:id, :name, :type, :document, :avatarUrl, :email, :phone, :pass, :role)", &user)
39 |
40 | if err != nil {
41 | ur.logger.Error("error saving user: ", err)
42 | return err
43 | }
44 |
45 | return nil
46 | }
47 |
48 | func (ur *UserRepository) SaveAddress(addr *entity.Address) error {
49 | _, err := ur.dbconnection.NamedExec("INSERT INTO addresses (id, userId, address, city, state, latitude, longitude) VALUES (:id, :userId, :address, :city, :state, :latitude, :longitude)", &addr)
50 |
51 | if err != nil {
52 | ur.logger.Error("error saving address: ", err)
53 | return err
54 | }
55 |
56 | return nil
57 | }
58 |
59 | func (ur *UserRepository) FindAddressByUserID(userID uniqueEntityId.ID) (*entity.Address, error) {
60 | var address entity.Address
61 |
62 | err := ur.dbconnection.Get(&address,
63 | `SELECT
64 | a.id,
65 | a.address,
66 | a.city,
67 | a.state,
68 | a.latitude,
69 | a.longitude,
70 | FROM
71 | addresses a
72 | WHERE
73 | a.userId = ?`,
74 | userID,
75 | )
76 | if err != nil {
77 | ur.logger.Error("error retrieving address: ", err)
78 | err = fmt.Errorf("error retrieving address %d: %w", userID, err)
79 | return nil, err
80 | }
81 |
82 | return &address, nil
83 | }
84 |
85 | func (ur *UserRepository) Update(userID uniqueEntityId.ID, userToUpdate entity.User) error {
86 |
87 | query := "UPDATE users SET"
88 | values := []interface{}{}
89 |
90 | if userToUpdate.Name != "" {
91 | query = query + " name =?,"
92 | values = append(values, userToUpdate.Name)
93 | }
94 |
95 | if userToUpdate.Document != "" {
96 | query = query + " document =?,"
97 | values = append(values, userToUpdate.Document)
98 | }
99 |
100 | if userToUpdate.AvatarURL != "" {
101 | query = query + " avatarUrl =?,"
102 | values = append(values, userToUpdate.AvatarURL)
103 | }
104 |
105 | if userToUpdate.Email != "" {
106 | query = query + " email =?,"
107 | values = append(values, userToUpdate.Email)
108 | }
109 |
110 | if userToUpdate.Phone != "" {
111 | query = query + " phone =?,"
112 | values = append(values, userToUpdate.Phone)
113 | }
114 |
115 | if userToUpdate.BirthDate != nil {
116 | query = query + " birthdate =?,"
117 | values = append(values, userToUpdate.BirthDate)
118 | }
119 |
120 | if userToUpdate.Role != "" {
121 | query = query + " role =?,"
122 | values = append(values, userToUpdate.Role)
123 | }
124 |
125 | if userToUpdate.PushNotificationsEnabled != nil {
126 | query = query + " pushNotificationsEnabled =?,"
127 | values = append(values, userToUpdate.PushNotificationsEnabled)
128 | }
129 |
130 | query = query + " updated_at =?,"
131 | values = append(values, time.Now())
132 |
133 | n := len(query)
134 | query = query[:n-1] + " WHERE id =?"
135 | values = append(values, userID)
136 |
137 | fmt.Printf("Query to update: %s", query)
138 |
139 | _, err := ur.dbconnection.Exec(query, values...)
140 |
141 | if err != nil {
142 | ur.logger.Error(fmt.Errorf("#UserRepository.Update error: %w", err))
143 | return fmt.Errorf("error on update user")
144 | }
145 |
146 | return nil
147 | }
148 |
149 | func (ur *UserRepository) FindByID(ID uniqueEntityId.ID) (*entity.User, error) {
150 | var user entity.User
151 |
152 | err := ur.dbconnection.Get(&user,
153 | `SELECT
154 | u.id,
155 | u.name,
156 | u.birthdate,
157 | u.document,
158 | u.avatarUrl,
159 | u.email,
160 | u.phone,
161 | u.role,
162 | u.pass
163 | FROM
164 | users u
165 | WHERE
166 | u.id = ?`,
167 | ID,
168 | )
169 | if err != nil {
170 | ur.logger.Error("error retrieving user: ", err)
171 | err = fmt.Errorf("error retrieving user %d: %w", ID, err)
172 | return nil, err
173 | }
174 |
175 | return &user, nil
176 | }
177 |
178 | func (ur *UserRepository) FindByEmail(email string) (*entity.User, error) {
179 | var user entity.User
180 |
181 | err := ur.dbconnection.Get(&user,
182 | `SELECT
183 | u.id,
184 | u.name,
185 | u.email,
186 | u.pass
187 | FROM
188 | users u
189 | WHERE
190 | u.email = ?`,
191 | email,
192 | )
193 | if err != nil {
194 | ur.logger.Error("error retrieving user: ", err)
195 | err = fmt.Errorf("error retrieving user %s: %w", email, err)
196 | return nil, err
197 | }
198 |
199 | return &user, nil
200 | }
201 |
202 | func (ur *UserRepository) List() (users []entity.User, err error) {
203 | return nil, nil
204 | }
205 |
206 | func (ur *UserRepository) ChangePassword(userId uniqueEntityId.ID, newPassword string) error {
207 |
208 | query := "UPDATE users SET pass = ?,"
209 | var values []interface{}
210 |
211 | values = append(values, newPassword)
212 |
213 | query = query + " updated_at =?,"
214 | values = append(values, time.Now())
215 |
216 | n := len(query)
217 | query = query[:n-1] + " WHERE id =?"
218 | values = append(values, userId)
219 |
220 | fmt.Printf("Query to update: %s", query)
221 |
222 | _, err := ur.dbconnection.Exec(query, values...)
223 |
224 | if err != nil {
225 | ur.logger.Error(fmt.Errorf("#UserRepository.ChangePassword error: %w", err))
226 | return fmt.Errorf("error on changing user password")
227 | }
228 |
229 | return nil
230 | }
231 |
--------------------------------------------------------------------------------
/infra/mail_repository.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 | "pet-dex-backend/v2/infra/config"
6 | "pet-dex-backend/v2/interfaces"
7 | "pet-dex-backend/v2/pkg/mail"
8 | )
9 |
10 | type MailRepository struct {
11 | mailPkg mail.IMail
12 | mailMessage mail.Message
13 | logger config.Logger
14 | }
15 |
16 | func NewMailRepository(mpkg mail.IMail, msg mail.Message) interfaces.Emailrepository {
17 | return &MailRepository{
18 | mailPkg: mpkg,
19 | mailMessage: msg,
20 | logger: *config.GetLogger("mail-repository"),
21 | }
22 | }
23 |
24 | func (mr *MailRepository) SendConfirmationEmail(user *entity.User) error {
25 |
26 | to := []string{user.Email}
27 |
28 | message := mail.NewMessage(to, "olaaaa este é um email de confirmação!!!")
29 |
30 | err := mr.mailPkg.Send(message)
31 |
32 | if err != nil {
33 | mr.logger.Error("error on mail repository: ", err)
34 | return err
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func (mr *MailRepository) SendNotificationEmail(message string, recipient string) error {
41 |
42 | to := []string{recipient}
43 |
44 | msg := mail.NewMessage(to, message)
45 |
46 | err := mr.mailPkg.Send(msg)
47 |
48 | if err != nil {
49 | mr.logger.Error("error on mail repository: ", err)
50 | return err
51 | }
52 |
53 | return nil
54 | }
55 |
56 | //TODO:
57 | //ler testes
58 |
--------------------------------------------------------------------------------
/integration-tests/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devhatt/pet-dex-backend/42b37eb12762c7c9305974a699ea4e9349a5cf68/integration-tests/.keep
--------------------------------------------------------------------------------
/interfaces/address_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | )
7 |
8 | type AdressRepo interface {
9 | SaveAddress(addr *entity.Address) error
10 | FindAddressByUserID(ID uniqueEntityId.ID) (*entity.Address, error)
11 | }
12 |
--------------------------------------------------------------------------------
/interfaces/breed_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 | "pet-dex-backend/v2/entity/dto"
6 | "pet-dex-backend/v2/pkg/uniqueEntityId"
7 | )
8 |
9 | type BreedRepository interface {
10 | List() (breeds []*dto.BreedList, err error)
11 | FindByID(ID uniqueEntityId.ID) (*entity.Breed, error)
12 | }
13 |
--------------------------------------------------------------------------------
/interfaces/encoder.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import "github.com/golang-jwt/jwt"
4 |
5 | type UserClaims struct {
6 | Id string `json:"id"`
7 | Name string `json:"name"`
8 | Email string `json:"email"`
9 | Role string `json:"role"`
10 | jwt.StandardClaims
11 | }
12 |
13 | type Encoder interface {
14 | NewAccessToken(claims UserClaims) (string, error)
15 | ParseAccessToken(accessToken string) *UserClaims
16 | }
17 |
--------------------------------------------------------------------------------
/interfaces/hasher.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | type Hasher interface {
4 | Hash(key string) (string, error)
5 | Compare(key, toCompare string) bool
6 | }
--------------------------------------------------------------------------------
/interfaces/mail_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import "pet-dex-backend/v2/entity"
4 |
5 | type Emailrepository interface {
6 | SendConfirmationEmail(user *entity.User) error
7 | SendNotificationEmail(message string, recipient string) error
8 | }
9 |
--------------------------------------------------------------------------------
/interfaces/ong_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 | "pet-dex-backend/v2/entity/dto"
6 | "pet-dex-backend/v2/pkg/uniqueEntityId"
7 | )
8 |
9 | type OngRepository interface {
10 | Save(ong *entity.Ong) error
11 | List(limit, offset int, sortBy, order string) (ongs []*dto.OngListMapper, err error)
12 | FindByID(ID uniqueEntityId.ID) (*dto.OngListMapper, error)
13 | Update(id uniqueEntityId.ID, ong entity.Ong) error
14 | Delete(id uniqueEntityId.ID) error
15 | }
16 |
--------------------------------------------------------------------------------
/interfaces/pet_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 |
6 | "pet-dex-backend/v2/pkg/uniqueEntityId"
7 | )
8 |
9 | type PetRepository interface {
10 | ListByUser(userID uniqueEntityId.ID) ([]*entity.Pet, error)
11 | FindByID(ID uniqueEntityId.ID) (*entity.Pet, error)
12 |
13 | Save(pet *entity.Pet) error
14 | Update(petID string, userID string, petToUpdate *entity.Pet) error
15 | ListAllByPage(page int) ([]*entity.Pet, error)
16 | }
17 |
--------------------------------------------------------------------------------
/interfaces/sso.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import "pet-dex-backend/v2/entity/dto"
4 |
5 | type SingleSignOnGateway interface {
6 | GetUserDetails(accessToken string) (*dto.UserSSODto, error)
7 | Name() string
8 | }
9 |
10 | type SingleSignOnProvider interface {
11 | GetUserDetails(provider, accessToken string) (*dto.UserSSODto, error)
12 | }
13 |
--------------------------------------------------------------------------------
/interfaces/user_repository.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | import (
4 | "pet-dex-backend/v2/entity"
5 | "pet-dex-backend/v2/pkg/uniqueEntityId"
6 | )
7 |
8 | type UserRepository interface {
9 | Save(user *entity.User) error
10 | Update(userID uniqueEntityId.ID, user entity.User) error
11 | Delete(id uniqueEntityId.ID) error
12 | FindByID(ID uniqueEntityId.ID) (*entity.User, error)
13 | FindByEmail(email string) (*entity.User, error)
14 | List() ([]entity.User, error)
15 | ChangePassword(userId uniqueEntityId.ID, newPassword string) error
16 | AdressRepo
17 | }
18 |
--------------------------------------------------------------------------------
/main:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devhatt/pet-dex-backend/42b37eb12762c7c9305974a699ea4e9349a5cf68/main
--------------------------------------------------------------------------------
/migrations/20240508222543_init_db.down.sql:
--------------------------------------------------------------------------------
1 | SET FOREIGN_KEY_CHECKS = 0;
2 |
3 | drop table if exists vaccines cascade;
4 |
5 | drop table if exists pets_image cascade;
6 |
7 | drop table if exists pets cascade;
8 |
9 | drop table if exists breeds cascade;
10 |
11 | drop table if exists addresses cascade;
12 |
13 | drop table if exists legal_persons cascade;
14 |
15 | drop table if exists users cascade;
16 |
17 | drop table if exists person cascade;
18 |
19 | SET FOREIGN_KEY_CHECKS = 1;
--------------------------------------------------------------------------------
/migrations/20240508222543_init_db.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS users
2 | (
3 | id uuid primary key default UUID(),
4 | name varchar(120),
5 | type varchar(20) check(type IN ('fisica', 'juridica')),
6 | birthdate date,
7 | document VARCHAR(14),
8 | avatarUrl varchar(255),
9 | email varchar(128) not null,
10 | pass VARCHAR(100) NOT NULL,
11 | phone varchar(12) not null,
12 | created_at TIMESTAMP NOT NULL DEFAULT NOW(),
13 | updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
14 | deleted_at TIMESTAMP,
15 | UNIQUE INDEX idx_email (email)
16 | );
17 |
18 | CREATE TABLE IF NOT EXISTS legal_persons
19 | (
20 | id uuid primary key default UUID(),
21 | userId uuid REFERENCES users (id),
22 | phone varchar(12) not null,
23 | links varchar(255),
24 | openingHours varchar(120) not null,
25 | adoptionPolicy longtext not null
26 | );
27 |
28 | CREATE TABLE IF NOT EXISTS addresses
29 | (
30 | id uuid primary key default UUID(),
31 | userId uuid REFERENCES users (id),
32 | address varchar(255),
33 | city varchar(50),
34 | state varchar(20),
35 | latitude float,
36 | longitude float
37 | );
38 |
39 | CREATE TABLE IF NOT EXISTS breeds
40 | (
41 | id uuid primary key default UUID(),
42 | name varchar(255) not null,
43 | specie varchar(255) not null,
44 | size varchar(20) check(size IN ('small', 'medium', 'large', 'giant')),
45 | description varchar(255),
46 | height varchar(10),
47 | weight varchar(10),
48 | physicalChar varchar(255),
49 | disposition varchar(255),
50 | idealFor varchar(255),
51 | fur varchar(50),
52 | imgUrl varchar(255),
53 | weather varchar(255),
54 | dressage varchar(255),
55 | lifeExpectancy varchar(30)
56 | );
57 |
58 | CREATE TABLE IF NOT EXISTS pets
59 | (
60 | id uuid primary key default UUID(),
61 | name varchar(128) not null,
62 | breedId uuid REFERENCES breeds(id),
63 | size varchar(20) check(size IN ('small', 'medium', 'large', 'giant')),
64 | weight decimal(3, 2) not null,
65 | weightMeasure varchar(2) check(weightMeasure IN ('kg', 'lb')),
66 | adoptionDate date not null,
67 | birthdate date not null,
68 | comorbidity varchar(255),
69 | tags varchar(255),
70 | castrated bool,
71 | availableToAdoption bool default true,
72 | userId uuid REFERENCES users (id)
73 | );
74 |
75 | CREATE TABLE IF NOT EXISTS pets_image
76 | (
77 | id uuid primary key default UUID(),
78 | url varchar(255),
79 | petId uuid REFERENCES pets (id)
80 | );
81 |
82 | CREATE TABLE IF NOT EXISTS vaccines
83 | (
84 | id uuid primary key default UUID(),
85 | petId uuid REFERENCES pets (id),
86 | name varchar(128) not null,
87 | date date not null,
88 | doctorCRM varchar(15) not null
89 | );
--------------------------------------------------------------------------------
/migrations/20240508222652_add_needed_care.down.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE pets DROP COLUMN neededSpecialCare;
2 | ALTER TABLE pets DROP COLUMN descriptionSpecialCare;
--------------------------------------------------------------------------------
/migrations/20240508222652_add_needed_care.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE pets ADD COLUMN neededSpecialCare BOOLEAN;
2 | ALTER TABLE pets ADD COLUMN descriptionSpecialCare VARCHAR(255) NULL;
--------------------------------------------------------------------------------
/migrations/20240610154621_add_push_notification_settings.down.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devhatt/pet-dex-backend/42b37eb12762c7c9305974a699ea4e9349a5cf68/migrations/20240610154621_add_push_notification_settings.down.sql
--------------------------------------------------------------------------------
/migrations/20240610154621_add_push_notification_settings.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE users ADD COLUMN PushNotificationsEnabled BOOLEAN;
--------------------------------------------------------------------------------
/migrations/20240613185301_add_user_roles.down.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE users
2 | DROP COLUMN role;
--------------------------------------------------------------------------------
/migrations/20240613185301_add_user_roles.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE users
2 | ADD role VARCHAR(255);
--------------------------------------------------------------------------------
/migrations/20240722165154_add_deletedAt_legal_persons.down.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devhatt/pet-dex-backend/42b37eb12762c7c9305974a699ea4e9349a5cf68/migrations/20240722165154_add_deletedAt_legal_persons.down.sql
--------------------------------------------------------------------------------
/migrations/20240722165154_add_deletedAt_legal_persons.up.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE legal_persons ADD COLUMN deletedAt TIMESTAMP;
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_AdressRepo.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | entity "pet-dex-backend/v2/entity"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 |
10 | uuid "github.com/google/uuid"
11 | )
12 |
13 | // MockAdressRepo is an autogenerated mock type for the AdressRepo type
14 | type MockAdressRepo struct {
15 | mock.Mock
16 | }
17 |
18 | type MockAdressRepo_Expecter struct {
19 | mock *mock.Mock
20 | }
21 |
22 | func (_m *MockAdressRepo) EXPECT() *MockAdressRepo_Expecter {
23 | return &MockAdressRepo_Expecter{mock: &_m.Mock}
24 | }
25 |
26 | // FindAddressByUserID provides a mock function with given fields: ID
27 | func (_m *MockAdressRepo) FindAddressByUserID(ID uuid.UUID) (*entity.Address, error) {
28 | ret := _m.Called(ID)
29 |
30 | if len(ret) == 0 {
31 | panic("no return value specified for FindAddressByUserID")
32 | }
33 |
34 | var r0 *entity.Address
35 | var r1 error
36 | if rf, ok := ret.Get(0).(func(uuid.UUID) (*entity.Address, error)); ok {
37 | return rf(ID)
38 | }
39 | if rf, ok := ret.Get(0).(func(uuid.UUID) *entity.Address); ok {
40 | r0 = rf(ID)
41 | } else {
42 | if ret.Get(0) != nil {
43 | r0 = ret.Get(0).(*entity.Address)
44 | }
45 | }
46 |
47 | if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
48 | r1 = rf(ID)
49 | } else {
50 | r1 = ret.Error(1)
51 | }
52 |
53 | return r0, r1
54 | }
55 |
56 | // MockAdressRepo_FindAddressByUserID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindAddressByUserID'
57 | type MockAdressRepo_FindAddressByUserID_Call struct {
58 | *mock.Call
59 | }
60 |
61 | // FindAddressByUserID is a helper method to define mock.On call
62 | // - ID uuid.UUID
63 | func (_e *MockAdressRepo_Expecter) FindAddressByUserID(ID interface{}) *MockAdressRepo_FindAddressByUserID_Call {
64 | return &MockAdressRepo_FindAddressByUserID_Call{Call: _e.mock.On("FindAddressByUserID", ID)}
65 | }
66 |
67 | func (_c *MockAdressRepo_FindAddressByUserID_Call) Run(run func(ID uuid.UUID)) *MockAdressRepo_FindAddressByUserID_Call {
68 | _c.Call.Run(func(args mock.Arguments) {
69 | run(args[0].(uuid.UUID))
70 | })
71 | return _c
72 | }
73 |
74 | func (_c *MockAdressRepo_FindAddressByUserID_Call) Return(_a0 *entity.Address, _a1 error) *MockAdressRepo_FindAddressByUserID_Call {
75 | _c.Call.Return(_a0, _a1)
76 | return _c
77 | }
78 |
79 | func (_c *MockAdressRepo_FindAddressByUserID_Call) RunAndReturn(run func(uuid.UUID) (*entity.Address, error)) *MockAdressRepo_FindAddressByUserID_Call {
80 | _c.Call.Return(run)
81 | return _c
82 | }
83 |
84 | // SaveAddress provides a mock function with given fields: addr
85 | func (_m *MockAdressRepo) SaveAddress(addr *entity.Address) error {
86 | ret := _m.Called(addr)
87 |
88 | if len(ret) == 0 {
89 | panic("no return value specified for SaveAddress")
90 | }
91 |
92 | var r0 error
93 | if rf, ok := ret.Get(0).(func(*entity.Address) error); ok {
94 | r0 = rf(addr)
95 | } else {
96 | r0 = ret.Error(0)
97 | }
98 |
99 | return r0
100 | }
101 |
102 | // MockAdressRepo_SaveAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveAddress'
103 | type MockAdressRepo_SaveAddress_Call struct {
104 | *mock.Call
105 | }
106 |
107 | // SaveAddress is a helper method to define mock.On call
108 | // - addr *entity.Address
109 | func (_e *MockAdressRepo_Expecter) SaveAddress(addr interface{}) *MockAdressRepo_SaveAddress_Call {
110 | return &MockAdressRepo_SaveAddress_Call{Call: _e.mock.On("SaveAddress", addr)}
111 | }
112 |
113 | func (_c *MockAdressRepo_SaveAddress_Call) Run(run func(addr *entity.Address)) *MockAdressRepo_SaveAddress_Call {
114 | _c.Call.Run(func(args mock.Arguments) {
115 | run(args[0].(*entity.Address))
116 | })
117 | return _c
118 | }
119 |
120 | func (_c *MockAdressRepo_SaveAddress_Call) Return(_a0 error) *MockAdressRepo_SaveAddress_Call {
121 | _c.Call.Return(_a0)
122 | return _c
123 | }
124 |
125 | func (_c *MockAdressRepo_SaveAddress_Call) RunAndReturn(run func(*entity.Address) error) *MockAdressRepo_SaveAddress_Call {
126 | _c.Call.Return(run)
127 | return _c
128 | }
129 |
130 | // NewMockAdressRepo creates a new instance of MockAdressRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
131 | // The first argument is typically a *testing.T value.
132 | func NewMockAdressRepo(t interface {
133 | mock.TestingT
134 | Cleanup(func())
135 | }) *MockAdressRepo {
136 | mock := &MockAdressRepo{}
137 | mock.Mock.Test(t)
138 |
139 | t.Cleanup(func() { mock.AssertExpectations(t) })
140 |
141 | return mock
142 | }
143 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_BreedRepository.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | entity "pet-dex-backend/v2/entity"
7 | dto "pet-dex-backend/v2/entity/dto"
8 |
9 | mock "github.com/stretchr/testify/mock"
10 |
11 | uuid "github.com/google/uuid"
12 | )
13 |
14 | // MockBreedRepository is an autogenerated mock type for the BreedRepository type
15 | type MockBreedRepository struct {
16 | mock.Mock
17 | }
18 |
19 | type MockBreedRepository_Expecter struct {
20 | mock *mock.Mock
21 | }
22 |
23 | func (_m *MockBreedRepository) EXPECT() *MockBreedRepository_Expecter {
24 | return &MockBreedRepository_Expecter{mock: &_m.Mock}
25 | }
26 |
27 | // FindByID provides a mock function with given fields: ID
28 | func (_m *MockBreedRepository) FindByID(ID uuid.UUID) (*entity.Breed, error) {
29 | ret := _m.Called(ID)
30 |
31 | if len(ret) == 0 {
32 | panic("no return value specified for FindByID")
33 | }
34 |
35 | var r0 *entity.Breed
36 | var r1 error
37 | if rf, ok := ret.Get(0).(func(uuid.UUID) (*entity.Breed, error)); ok {
38 | return rf(ID)
39 | }
40 | if rf, ok := ret.Get(0).(func(uuid.UUID) *entity.Breed); ok {
41 | r0 = rf(ID)
42 | } else {
43 | if ret.Get(0) != nil {
44 | r0 = ret.Get(0).(*entity.Breed)
45 | }
46 | }
47 |
48 | if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
49 | r1 = rf(ID)
50 | } else {
51 | r1 = ret.Error(1)
52 | }
53 |
54 | return r0, r1
55 | }
56 |
57 | // MockBreedRepository_FindByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByID'
58 | type MockBreedRepository_FindByID_Call struct {
59 | *mock.Call
60 | }
61 |
62 | // FindByID is a helper method to define mock.On call
63 | // - ID uuid.UUID
64 | func (_e *MockBreedRepository_Expecter) FindByID(ID interface{}) *MockBreedRepository_FindByID_Call {
65 | return &MockBreedRepository_FindByID_Call{Call: _e.mock.On("FindByID", ID)}
66 | }
67 |
68 | func (_c *MockBreedRepository_FindByID_Call) Run(run func(ID uuid.UUID)) *MockBreedRepository_FindByID_Call {
69 | _c.Call.Run(func(args mock.Arguments) {
70 | run(args[0].(uuid.UUID))
71 | })
72 | return _c
73 | }
74 |
75 | func (_c *MockBreedRepository_FindByID_Call) Return(_a0 *entity.Breed, _a1 error) *MockBreedRepository_FindByID_Call {
76 | _c.Call.Return(_a0, _a1)
77 | return _c
78 | }
79 |
80 | func (_c *MockBreedRepository_FindByID_Call) RunAndReturn(run func(uuid.UUID) (*entity.Breed, error)) *MockBreedRepository_FindByID_Call {
81 | _c.Call.Return(run)
82 | return _c
83 | }
84 |
85 | // List provides a mock function with given fields:
86 | func (_m *MockBreedRepository) List() ([]*dto.BreedList, error) {
87 | ret := _m.Called()
88 |
89 | if len(ret) == 0 {
90 | panic("no return value specified for List")
91 | }
92 |
93 | var r0 []*dto.BreedList
94 | var r1 error
95 | if rf, ok := ret.Get(0).(func() ([]*dto.BreedList, error)); ok {
96 | return rf()
97 | }
98 | if rf, ok := ret.Get(0).(func() []*dto.BreedList); ok {
99 | r0 = rf()
100 | } else {
101 | if ret.Get(0) != nil {
102 | r0 = ret.Get(0).([]*dto.BreedList)
103 | }
104 | }
105 |
106 | if rf, ok := ret.Get(1).(func() error); ok {
107 | r1 = rf()
108 | } else {
109 | r1 = ret.Error(1)
110 | }
111 |
112 | return r0, r1
113 | }
114 |
115 | // MockBreedRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
116 | type MockBreedRepository_List_Call struct {
117 | *mock.Call
118 | }
119 |
120 | // List is a helper method to define mock.On call
121 | func (_e *MockBreedRepository_Expecter) List() *MockBreedRepository_List_Call {
122 | return &MockBreedRepository_List_Call{Call: _e.mock.On("List")}
123 | }
124 |
125 | func (_c *MockBreedRepository_List_Call) Run(run func()) *MockBreedRepository_List_Call {
126 | _c.Call.Run(func(args mock.Arguments) {
127 | run()
128 | })
129 | return _c
130 | }
131 |
132 | func (_c *MockBreedRepository_List_Call) Return(breeds []*dto.BreedList, err error) *MockBreedRepository_List_Call {
133 | _c.Call.Return(breeds, err)
134 | return _c
135 | }
136 |
137 | func (_c *MockBreedRepository_List_Call) RunAndReturn(run func() ([]*dto.BreedList, error)) *MockBreedRepository_List_Call {
138 | _c.Call.Return(run)
139 | return _c
140 | }
141 |
142 | // NewMockBreedRepository creates a new instance of MockBreedRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
143 | // The first argument is typically a *testing.T value.
144 | func NewMockBreedRepository(t interface {
145 | mock.TestingT
146 | Cleanup(func())
147 | }) *MockBreedRepository {
148 | mock := &MockBreedRepository{}
149 | mock.Mock.Test(t)
150 |
151 | t.Cleanup(func() { mock.AssertExpectations(t) })
152 |
153 | return mock
154 | }
155 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_Hasher.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import mock "github.com/stretchr/testify/mock"
6 |
7 | // MockHasher is an autogenerated mock type for the Hasher type
8 | type MockHasher struct {
9 | mock.Mock
10 | }
11 |
12 | type MockHasher_Expecter struct {
13 | mock *mock.Mock
14 | }
15 |
16 | func (_m *MockHasher) EXPECT() *MockHasher_Expecter {
17 | return &MockHasher_Expecter{mock: &_m.Mock}
18 | }
19 |
20 | // Compare provides a mock function with given fields: key, toCompare
21 | func (_m *MockHasher) Compare(key string, toCompare string) bool {
22 | ret := _m.Called(key, toCompare)
23 |
24 | if len(ret) == 0 {
25 | panic("no return value specified for Compare")
26 | }
27 |
28 | var r0 bool
29 | if rf, ok := ret.Get(0).(func(string, string) bool); ok {
30 | r0 = rf(key, toCompare)
31 | } else {
32 | r0 = ret.Get(0).(bool)
33 | }
34 |
35 | return r0
36 | }
37 |
38 | // MockHasher_Compare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Compare'
39 | type MockHasher_Compare_Call struct {
40 | *mock.Call
41 | }
42 |
43 | // Compare is a helper method to define mock.On call
44 | // - key string
45 | // - toCompare string
46 | func (_e *MockHasher_Expecter) Compare(key interface{}, toCompare interface{}) *MockHasher_Compare_Call {
47 | return &MockHasher_Compare_Call{Call: _e.mock.On("Compare", key, toCompare)}
48 | }
49 |
50 | func (_c *MockHasher_Compare_Call) Run(run func(key string, toCompare string)) *MockHasher_Compare_Call {
51 | _c.Call.Run(func(args mock.Arguments) {
52 | run(args[0].(string), args[1].(string))
53 | })
54 | return _c
55 | }
56 |
57 | func (_c *MockHasher_Compare_Call) Return(_a0 bool) *MockHasher_Compare_Call {
58 | _c.Call.Return(_a0)
59 | return _c
60 | }
61 |
62 | func (_c *MockHasher_Compare_Call) RunAndReturn(run func(string, string) bool) *MockHasher_Compare_Call {
63 | _c.Call.Return(run)
64 | return _c
65 | }
66 |
67 | // Hash provides a mock function with given fields: key
68 | func (_m *MockHasher) Hash(key string) (string, error) {
69 | ret := _m.Called(key)
70 |
71 | if len(ret) == 0 {
72 | panic("no return value specified for Hash")
73 | }
74 |
75 | var r0 string
76 | var r1 error
77 | if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
78 | return rf(key)
79 | }
80 | if rf, ok := ret.Get(0).(func(string) string); ok {
81 | r0 = rf(key)
82 | } else {
83 | r0 = ret.Get(0).(string)
84 | }
85 |
86 | if rf, ok := ret.Get(1).(func(string) error); ok {
87 | r1 = rf(key)
88 | } else {
89 | r1 = ret.Error(1)
90 | }
91 |
92 | return r0, r1
93 | }
94 |
95 | // MockHasher_Hash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Hash'
96 | type MockHasher_Hash_Call struct {
97 | *mock.Call
98 | }
99 |
100 | // Hash is a helper method to define mock.On call
101 | // - key string
102 | func (_e *MockHasher_Expecter) Hash(key interface{}) *MockHasher_Hash_Call {
103 | return &MockHasher_Hash_Call{Call: _e.mock.On("Hash", key)}
104 | }
105 |
106 | func (_c *MockHasher_Hash_Call) Run(run func(key string)) *MockHasher_Hash_Call {
107 | _c.Call.Run(func(args mock.Arguments) {
108 | run(args[0].(string))
109 | })
110 | return _c
111 | }
112 |
113 | func (_c *MockHasher_Hash_Call) Return(_a0 string, _a1 error) *MockHasher_Hash_Call {
114 | _c.Call.Return(_a0, _a1)
115 | return _c
116 | }
117 |
118 | func (_c *MockHasher_Hash_Call) RunAndReturn(run func(string) (string, error)) *MockHasher_Hash_Call {
119 | _c.Call.Return(run)
120 | return _c
121 | }
122 |
123 | // NewMockHasher creates a new instance of MockHasher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
124 | // The first argument is typically a *testing.T value.
125 | func NewMockHasher(t interface {
126 | mock.TestingT
127 | Cleanup(func())
128 | }) *MockHasher {
129 | mock := &MockHasher{}
130 | mock.Mock.Test(t)
131 |
132 | t.Cleanup(func() { mock.AssertExpectations(t) })
133 |
134 | return mock
135 | }
136 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_OngRepository.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | entity "pet-dex-backend/v2/entity"
7 | dto "pet-dex-backend/v2/entity/dto"
8 |
9 | mock "github.com/stretchr/testify/mock"
10 |
11 | uuid "github.com/google/uuid"
12 | )
13 |
14 | // MockOngRepository is an autogenerated mock type for the OngRepository type
15 | type MockOngRepository struct {
16 | mock.Mock
17 | }
18 |
19 | // Delete implements interfaces.OngRepository.
20 | func (_m *MockOngRepository) Delete(id uuid.UUID) error {
21 | ret := _m.Called(id)
22 | return ret.Error(0)
23 | }
24 |
25 | type MockOngRepository_Expecter struct {
26 | mock *mock.Mock
27 | }
28 |
29 | func (_m *MockOngRepository) EXPECT() *MockOngRepository_Expecter {
30 | return &MockOngRepository_Expecter{mock: &_m.Mock}
31 | }
32 |
33 | // FindByID provides a mock function with given fields: ID
34 | func (_m *MockOngRepository) FindByID(ID uuid.UUID) (*dto.OngListMapper, error) {
35 | ret := _m.Called(ID)
36 |
37 | if len(ret) == 0 {
38 | panic("no return value specified for FindByID")
39 | }
40 |
41 | var r0 *dto.OngListMapper
42 | var r1 error
43 | if rf, ok := ret.Get(0).(func(uuid.UUID) (*dto.OngListMapper, error)); ok {
44 | return rf(ID)
45 | }
46 | if rf, ok := ret.Get(0).(func(uuid.UUID) *dto.OngListMapper); ok {
47 | r0 = rf(ID)
48 | } else {
49 | if ret.Get(0) != nil {
50 | r0 = ret.Get(0).(*dto.OngListMapper)
51 | }
52 | }
53 |
54 | if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
55 | r1 = rf(ID)
56 | } else {
57 | r1 = ret.Error(1)
58 | }
59 |
60 | return r0, r1
61 | }
62 |
63 | // MockOngRepository_FindByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByID'
64 | type MockOngRepository_FindByID_Call struct {
65 | *mock.Call
66 | }
67 |
68 | // FindByID is a helper method to define mock.On call
69 | // - ID uuid.UUID
70 | func (_e *MockOngRepository_Expecter) FindByID(ID interface{}) *MockOngRepository_FindByID_Call {
71 | return &MockOngRepository_FindByID_Call{Call: _e.mock.On("FindByID", ID)}
72 | }
73 |
74 | func (_c *MockOngRepository_FindByID_Call) Run(run func(ID uuid.UUID)) *MockOngRepository_FindByID_Call {
75 | _c.Call.Run(func(args mock.Arguments) {
76 | run(args[0].(uuid.UUID))
77 | })
78 | return _c
79 | }
80 |
81 | func (_c *MockOngRepository_FindByID_Call) Return(_a0 *entity.Ong, _a1 error) *MockOngRepository_FindByID_Call {
82 | _c.Call.Return(_a0, _a1)
83 | return _c
84 | }
85 |
86 | func (_c *MockOngRepository_FindByID_Call) RunAndReturn(run func(uuid.UUID) (*entity.Ong, error)) *MockOngRepository_FindByID_Call {
87 | _c.Call.Return(run)
88 | return _c
89 | }
90 |
91 | // List provides a mock function with given fields: limit, offset, sortBy, order
92 | func (_m *MockOngRepository) List(limit int, offset int, sortBy string, order string) ([]*dto.OngListMapper, error) {
93 | ret := _m.Called(limit, offset, sortBy, order)
94 |
95 | if len(ret) == 0 {
96 | panic("no return value specified for List")
97 | }
98 |
99 | var r0 []*dto.OngListMapper
100 | var r1 error
101 | if rf, ok := ret.Get(0).(func(int, int, string, string) ([]*dto.OngListMapper, error)); ok {
102 | return rf(limit, offset, sortBy, order)
103 | }
104 | if rf, ok := ret.Get(0).(func(int, int, string, string) []*dto.OngListMapper); ok {
105 | r0 = rf(limit, offset, sortBy, order)
106 | } else {
107 | if ret.Get(0) != nil {
108 | r0 = ret.Get(0).([]*dto.OngListMapper)
109 | }
110 | }
111 |
112 | if rf, ok := ret.Get(1).(func(int, int, string, string) error); ok {
113 | r1 = rf(limit, offset, sortBy, order)
114 | } else {
115 | r1 = ret.Error(1)
116 | }
117 |
118 | return r0, r1
119 | }
120 |
121 | // MockOngRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
122 | type MockOngRepository_List_Call struct {
123 | *mock.Call
124 | }
125 |
126 | // List is a helper method to define mock.On call
127 | // - limit int
128 | // - offset int
129 | // - sortBy string
130 | // - order string
131 | func (_e *MockOngRepository_Expecter) List(limit interface{}, offset interface{}, sortBy interface{}, order interface{}) *MockOngRepository_List_Call {
132 | return &MockOngRepository_List_Call{Call: _e.mock.On("List", limit, offset, sortBy, order)}
133 | }
134 |
135 | func (_c *MockOngRepository_List_Call) Run(run func(limit int, offset int, sortBy string, order string)) *MockOngRepository_List_Call {
136 | _c.Call.Run(func(args mock.Arguments) {
137 | run(args[0].(int), args[1].(int), args[2].(string), args[3].(string))
138 | })
139 | return _c
140 | }
141 |
142 | func (_c *MockOngRepository_List_Call) Return(ongs []*dto.OngListMapper, err error) *MockOngRepository_List_Call {
143 | _c.Call.Return(ongs, err)
144 | return _c
145 | }
146 |
147 | func (_c *MockOngRepository_List_Call) RunAndReturn(run func(int, int, string, string) ([]*dto.OngListMapper, error)) *MockOngRepository_List_Call {
148 | _c.Call.Return(run)
149 | return _c
150 | }
151 |
152 | // Save provides a mock function with given fields: ong
153 | func (_m *MockOngRepository) Save(ong *entity.Ong) error {
154 | ret := _m.Called(ong)
155 |
156 | if len(ret) == 0 {
157 | panic("no return value specified for Save")
158 | }
159 |
160 | var r0 error
161 | if rf, ok := ret.Get(0).(func(*entity.Ong) error); ok {
162 | r0 = rf(ong)
163 | } else {
164 | r0 = ret.Error(0)
165 | }
166 |
167 | return r0
168 | }
169 |
170 | // MockOngRepository_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
171 | type MockOngRepository_Save_Call struct {
172 | *mock.Call
173 | }
174 |
175 | // Save is a helper method to define mock.On call
176 | // - ong *entity.Ong
177 | func (_e *MockOngRepository_Expecter) Save(ong interface{}) *MockOngRepository_Save_Call {
178 | return &MockOngRepository_Save_Call{Call: _e.mock.On("Save", ong)}
179 | }
180 |
181 | func (_c *MockOngRepository_Save_Call) Run(run func(ong *entity.Ong)) *MockOngRepository_Save_Call {
182 | _c.Call.Run(func(args mock.Arguments) {
183 | run(args[0].(*entity.Ong))
184 | })
185 | return _c
186 | }
187 |
188 | func (_c *MockOngRepository_Save_Call) Return(_a0 error) *MockOngRepository_Save_Call {
189 | _c.Call.Return(_a0)
190 | return _c
191 | }
192 |
193 | func (_c *MockOngRepository_Save_Call) RunAndReturn(run func(*entity.Ong) error) *MockOngRepository_Save_Call {
194 | _c.Call.Return(run)
195 | return _c
196 | }
197 |
198 | // Update provides a mock function with given fields: id, ong
199 | func (_m *MockOngRepository) Update(id uuid.UUID, ong entity.Ong) error {
200 | ret := _m.Called(id, ong)
201 |
202 | if len(ret) == 0 {
203 | panic("no return value specified for Update")
204 | }
205 |
206 | var r0 error
207 | if rf, ok := ret.Get(0).(func(uuid.UUID, entity.Ong) error); ok {
208 | r0 = rf(id, ong)
209 | } else {
210 | r0 = ret.Error(0)
211 | }
212 |
213 | return r0
214 | }
215 |
216 | // MockOngRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
217 | type MockOngRepository_Update_Call struct {
218 | *mock.Call
219 | }
220 |
221 | // Update is a helper method to define mock.On call
222 | // - id uuid.UUID
223 | // - ong entity.Ong
224 | func (_e *MockOngRepository_Expecter) Update(id interface{}, ong interface{}) *MockOngRepository_Update_Call {
225 | return &MockOngRepository_Update_Call{Call: _e.mock.On("Update", id, ong)}
226 | }
227 |
228 | func (_c *MockOngRepository_Update_Call) Run(run func(id uuid.UUID, ong entity.Ong)) *MockOngRepository_Update_Call {
229 | _c.Call.Run(func(args mock.Arguments) {
230 | run(args[0].(uuid.UUID), args[1].(entity.Ong))
231 | })
232 | return _c
233 | }
234 |
235 | func (_c *MockOngRepository_Update_Call) Return(_a0 error) *MockOngRepository_Update_Call {
236 | _c.Call.Return(_a0)
237 | return _c
238 | }
239 |
240 | func (_c *MockOngRepository_Update_Call) RunAndReturn(run func(uuid.UUID, entity.Ong) error) *MockOngRepository_Update_Call {
241 | _c.Call.Return(run)
242 | return _c
243 | }
244 |
245 | // NewMockOngRepository creates a new instance of MockOngRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
246 | // The first argument is typically a *testing.T value.
247 | func NewMockOngRepository(t interface {
248 | mock.TestingT
249 | Cleanup(func())
250 | }) *MockOngRepository {
251 | mock := &MockOngRepository{}
252 | mock.Mock.Test(t)
253 |
254 | t.Cleanup(func() { mock.AssertExpectations(t) })
255 |
256 | return mock
257 | }
258 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_PetRepository.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | entity "pet-dex-backend/v2/entity"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 |
10 | uuid "github.com/google/uuid"
11 | )
12 |
13 | // MockPetRepository is an autogenerated mock type for the PetRepository type
14 | type MockPetRepository struct {
15 | mock.Mock
16 | }
17 |
18 | type MockPetRepository_Expecter struct {
19 | mock *mock.Mock
20 | }
21 |
22 | func (_m *MockPetRepository) EXPECT() *MockPetRepository_Expecter {
23 | return &MockPetRepository_Expecter{mock: &_m.Mock}
24 | }
25 |
26 | // FindByID provides a mock function with given fields: ID
27 | func (_m *MockPetRepository) FindByID(ID uuid.UUID) (*entity.Pet, error) {
28 | ret := _m.Called(ID)
29 |
30 | if len(ret) == 0 {
31 | panic("no return value specified for FindByID")
32 | }
33 |
34 | var r0 *entity.Pet
35 | var r1 error
36 | if rf, ok := ret.Get(0).(func(uuid.UUID) (*entity.Pet, error)); ok {
37 | return rf(ID)
38 | }
39 | if rf, ok := ret.Get(0).(func(uuid.UUID) *entity.Pet); ok {
40 | r0 = rf(ID)
41 | } else {
42 | if ret.Get(0) != nil {
43 | r0 = ret.Get(0).(*entity.Pet)
44 | }
45 | }
46 |
47 | if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
48 | r1 = rf(ID)
49 | } else {
50 | r1 = ret.Error(1)
51 | }
52 |
53 | return r0, r1
54 | }
55 |
56 | // MockPetRepository_FindByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindByID'
57 | type MockPetRepository_FindByID_Call struct {
58 | *mock.Call
59 | }
60 |
61 | // FindByID is a helper method to define mock.On call
62 | // - ID uuid.UUID
63 | func (_e *MockPetRepository_Expecter) FindByID(ID interface{}) *MockPetRepository_FindByID_Call {
64 | return &MockPetRepository_FindByID_Call{Call: _e.mock.On("FindByID", ID)}
65 | }
66 |
67 | func (_c *MockPetRepository_FindByID_Call) Run(run func(ID uuid.UUID)) *MockPetRepository_FindByID_Call {
68 | _c.Call.Run(func(args mock.Arguments) {
69 | run(args[0].(uuid.UUID))
70 | })
71 | return _c
72 | }
73 |
74 | func (_c *MockPetRepository_FindByID_Call) Return(_a0 *entity.Pet, _a1 error) *MockPetRepository_FindByID_Call {
75 | _c.Call.Return(_a0, _a1)
76 | return _c
77 | }
78 |
79 | func (_c *MockPetRepository_FindByID_Call) RunAndReturn(run func(uuid.UUID) (*entity.Pet, error)) *MockPetRepository_FindByID_Call {
80 | _c.Call.Return(run)
81 | return _c
82 | }
83 |
84 | // ListAllByPage provides a mock function with given fields: page
85 | func (_m *MockPetRepository) ListAllByPage(page int) ([]*entity.Pet, error) {
86 | ret := _m.Called(page)
87 |
88 | if len(ret) == 0 {
89 | panic("no return value specified for ListAllByPage")
90 | }
91 |
92 | var r0 []*entity.Pet
93 | var r1 error
94 | if rf, ok := ret.Get(0).(func(int) ([]*entity.Pet, error)); ok {
95 | return rf(page)
96 | }
97 | if rf, ok := ret.Get(0).(func(int) []*entity.Pet); ok {
98 | r0 = rf(page)
99 | } else {
100 | if ret.Get(0) != nil {
101 | r0 = ret.Get(0).([]*entity.Pet)
102 | }
103 | }
104 |
105 | if rf, ok := ret.Get(1).(func(int) error); ok {
106 | r1 = rf(page)
107 | } else {
108 | r1 = ret.Error(1)
109 | }
110 |
111 | return r0, r1
112 | }
113 |
114 | // MockPetRepository_ListAllByPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAllByPage'
115 | type MockPetRepository_ListAllByPage_Call struct {
116 | *mock.Call
117 | }
118 |
119 | // ListAllByPage is a helper method to define mock.On call
120 | // - page int
121 | func (_e *MockPetRepository_Expecter) ListAllByPage(page interface{}) *MockPetRepository_ListAllByPage_Call {
122 | return &MockPetRepository_ListAllByPage_Call{Call: _e.mock.On("ListAllByPage", page)}
123 | }
124 |
125 | func (_c *MockPetRepository_ListAllByPage_Call) Run(run func(page int)) *MockPetRepository_ListAllByPage_Call {
126 | _c.Call.Run(func(args mock.Arguments) {
127 | run(args[0].(int))
128 | })
129 | return _c
130 | }
131 |
132 | func (_c *MockPetRepository_ListAllByPage_Call) Return(_a0 []*entity.Pet, _a1 error) *MockPetRepository_ListAllByPage_Call {
133 | _c.Call.Return(_a0, _a1)
134 | return _c
135 | }
136 |
137 | func (_c *MockPetRepository_ListAllByPage_Call) RunAndReturn(run func(int) ([]*entity.Pet, error)) *MockPetRepository_ListAllByPage_Call {
138 | _c.Call.Return(run)
139 | return _c
140 | }
141 |
142 | // ListByUser provides a mock function with given fields: userID
143 | func (_m *MockPetRepository) ListByUser(userID uuid.UUID) ([]*entity.Pet, error) {
144 | ret := _m.Called(userID)
145 |
146 | if len(ret) == 0 {
147 | panic("no return value specified for ListByUser")
148 | }
149 |
150 | var r0 []*entity.Pet
151 | var r1 error
152 | if rf, ok := ret.Get(0).(func(uuid.UUID) ([]*entity.Pet, error)); ok {
153 | return rf(userID)
154 | }
155 | if rf, ok := ret.Get(0).(func(uuid.UUID) []*entity.Pet); ok {
156 | r0 = rf(userID)
157 | } else {
158 | if ret.Get(0) != nil {
159 | r0 = ret.Get(0).([]*entity.Pet)
160 | }
161 | }
162 |
163 | if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
164 | r1 = rf(userID)
165 | } else {
166 | r1 = ret.Error(1)
167 | }
168 |
169 | return r0, r1
170 | }
171 |
172 | // MockPetRepository_ListByUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListByUser'
173 | type MockPetRepository_ListByUser_Call struct {
174 | *mock.Call
175 | }
176 |
177 | // ListByUser is a helper method to define mock.On call
178 | // - userID uuid.UUID
179 | func (_e *MockPetRepository_Expecter) ListByUser(userID interface{}) *MockPetRepository_ListByUser_Call {
180 | return &MockPetRepository_ListByUser_Call{Call: _e.mock.On("ListByUser", userID)}
181 | }
182 |
183 | func (_c *MockPetRepository_ListByUser_Call) Run(run func(userID uuid.UUID)) *MockPetRepository_ListByUser_Call {
184 | _c.Call.Run(func(args mock.Arguments) {
185 | run(args[0].(uuid.UUID))
186 | })
187 | return _c
188 | }
189 |
190 | func (_c *MockPetRepository_ListByUser_Call) Return(_a0 []*entity.Pet, _a1 error) *MockPetRepository_ListByUser_Call {
191 | _c.Call.Return(_a0, _a1)
192 | return _c
193 | }
194 |
195 | func (_c *MockPetRepository_ListByUser_Call) RunAndReturn(run func(uuid.UUID) ([]*entity.Pet, error)) *MockPetRepository_ListByUser_Call {
196 | _c.Call.Return(run)
197 | return _c
198 | }
199 |
200 | // Save provides a mock function with given fields: pet
201 | func (_m *MockPetRepository) Save(pet *entity.Pet) error {
202 | ret := _m.Called(pet)
203 |
204 | if len(ret) == 0 {
205 | panic("no return value specified for Save")
206 | }
207 |
208 | var r0 error
209 | if rf, ok := ret.Get(0).(func(*entity.Pet) error); ok {
210 | r0 = rf(pet)
211 | } else {
212 | r0 = ret.Error(0)
213 | }
214 |
215 | return r0
216 | }
217 |
218 | // MockPetRepository_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
219 | type MockPetRepository_Save_Call struct {
220 | *mock.Call
221 | }
222 |
223 | // Save is a helper method to define mock.On call
224 | // - pet *entity.Pet
225 | func (_e *MockPetRepository_Expecter) Save(pet interface{}) *MockPetRepository_Save_Call {
226 | return &MockPetRepository_Save_Call{Call: _e.mock.On("Save", pet)}
227 | }
228 |
229 | func (_c *MockPetRepository_Save_Call) Run(run func(pet *entity.Pet)) *MockPetRepository_Save_Call {
230 | _c.Call.Run(func(args mock.Arguments) {
231 | run(args[0].(*entity.Pet))
232 | })
233 | return _c
234 | }
235 |
236 | func (_c *MockPetRepository_Save_Call) Return(_a0 error) *MockPetRepository_Save_Call {
237 | _c.Call.Return(_a0)
238 | return _c
239 | }
240 |
241 | func (_c *MockPetRepository_Save_Call) RunAndReturn(run func(*entity.Pet) error) *MockPetRepository_Save_Call {
242 | _c.Call.Return(run)
243 | return _c
244 | }
245 |
246 | // Update provides a mock function with given fields: petID, userID, petToUpdate
247 | func (_m *MockPetRepository) Update(petID string, userID string, petToUpdate *entity.Pet) error {
248 | ret := _m.Called(petID, userID, petToUpdate)
249 |
250 | if len(ret) == 0 {
251 | panic("no return value specified for Update")
252 | }
253 |
254 | var r0 error
255 | if rf, ok := ret.Get(0).(func(string, string, *entity.Pet) error); ok {
256 | r0 = rf(petID, userID, petToUpdate)
257 | } else {
258 | r0 = ret.Error(0)
259 | }
260 |
261 | return r0
262 | }
263 |
264 | // MockPetRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
265 | type MockPetRepository_Update_Call struct {
266 | *mock.Call
267 | }
268 |
269 | // Update is a helper method to define mock.On call
270 | // - petID string
271 | // - userID string
272 | // - petToUpdate *entity.Pet
273 | func (_e *MockPetRepository_Expecter) Update(petID interface{}, userID interface{}, petToUpdate interface{}) *MockPetRepository_Update_Call {
274 | return &MockPetRepository_Update_Call{Call: _e.mock.On("Update", petID, userID, petToUpdate)}
275 | }
276 |
277 | func (_c *MockPetRepository_Update_Call) Run(run func(petID string, userID string, petToUpdate *entity.Pet)) *MockPetRepository_Update_Call {
278 | _c.Call.Run(func(args mock.Arguments) {
279 | run(args[0].(string), args[1].(string), args[2].(*entity.Pet))
280 | })
281 | return _c
282 | }
283 |
284 | func (_c *MockPetRepository_Update_Call) Return(_a0 error) *MockPetRepository_Update_Call {
285 | _c.Call.Return(_a0)
286 | return _c
287 | }
288 |
289 | func (_c *MockPetRepository_Update_Call) RunAndReturn(run func(string, string, *entity.Pet) error) *MockPetRepository_Update_Call {
290 | _c.Call.Return(run)
291 | return _c
292 | }
293 |
294 | // NewMockPetRepository creates a new instance of MockPetRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
295 | // The first argument is typically a *testing.T value.
296 | func NewMockPetRepository(t interface {
297 | mock.TestingT
298 | Cleanup(func())
299 | }) *MockPetRepository {
300 | mock := &MockPetRepository{}
301 | mock.Mock.Test(t)
302 |
303 | t.Cleanup(func() { mock.AssertExpectations(t) })
304 |
305 | return mock
306 | }
307 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_SingleSignOnGateway.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | dto "pet-dex-backend/v2/entity/dto"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 | )
10 |
11 | // MockSingleSignOnGateway is an autogenerated mock type for the SingleSignOnGateway type
12 | type MockSingleSignOnGateway struct {
13 | mock.Mock
14 | }
15 |
16 | type MockSingleSignOnGateway_Expecter struct {
17 | mock *mock.Mock
18 | }
19 |
20 | func (_m *MockSingleSignOnGateway) EXPECT() *MockSingleSignOnGateway_Expecter {
21 | return &MockSingleSignOnGateway_Expecter{mock: &_m.Mock}
22 | }
23 |
24 | // GetUserDetails provides a mock function with given fields: accessToken
25 | func (_m *MockSingleSignOnGateway) GetUserDetails(accessToken string) (*dto.UserSSODto, error) {
26 | ret := _m.Called(accessToken)
27 |
28 | if len(ret) == 0 {
29 | panic("no return value specified for GetUserDetails")
30 | }
31 |
32 | var r0 *dto.UserSSODto
33 | var r1 error
34 | if rf, ok := ret.Get(0).(func(string) (*dto.UserSSODto, error)); ok {
35 | return rf(accessToken)
36 | }
37 | if rf, ok := ret.Get(0).(func(string) *dto.UserSSODto); ok {
38 | r0 = rf(accessToken)
39 | } else {
40 | if ret.Get(0) != nil {
41 | r0 = ret.Get(0).(*dto.UserSSODto)
42 | }
43 | }
44 |
45 | if rf, ok := ret.Get(1).(func(string) error); ok {
46 | r1 = rf(accessToken)
47 | } else {
48 | r1 = ret.Error(1)
49 | }
50 |
51 | return r0, r1
52 | }
53 |
54 | // MockSingleSignOnGateway_GetUserDetails_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserDetails'
55 | type MockSingleSignOnGateway_GetUserDetails_Call struct {
56 | *mock.Call
57 | }
58 |
59 | // GetUserDetails is a helper method to define mock.On call
60 | // - accessToken string
61 | func (_e *MockSingleSignOnGateway_Expecter) GetUserDetails(accessToken interface{}) *MockSingleSignOnGateway_GetUserDetails_Call {
62 | return &MockSingleSignOnGateway_GetUserDetails_Call{Call: _e.mock.On("GetUserDetails", accessToken)}
63 | }
64 |
65 | func (_c *MockSingleSignOnGateway_GetUserDetails_Call) Run(run func(accessToken string)) *MockSingleSignOnGateway_GetUserDetails_Call {
66 | _c.Call.Run(func(args mock.Arguments) {
67 | run(args[0].(string))
68 | })
69 | return _c
70 | }
71 |
72 | func (_c *MockSingleSignOnGateway_GetUserDetails_Call) Return(_a0 *dto.UserSSODto, _a1 error) *MockSingleSignOnGateway_GetUserDetails_Call {
73 | _c.Call.Return(_a0, _a1)
74 | return _c
75 | }
76 |
77 | func (_c *MockSingleSignOnGateway_GetUserDetails_Call) RunAndReturn(run func(string) (*dto.UserSSODto, error)) *MockSingleSignOnGateway_GetUserDetails_Call {
78 | _c.Call.Return(run)
79 | return _c
80 | }
81 |
82 | // Name provides a mock function with given fields:
83 | func (_m *MockSingleSignOnGateway) Name() string {
84 | ret := _m.Called()
85 |
86 | if len(ret) == 0 {
87 | panic("no return value specified for Name")
88 | }
89 |
90 | var r0 string
91 | if rf, ok := ret.Get(0).(func() string); ok {
92 | r0 = rf()
93 | } else {
94 | r0 = ret.Get(0).(string)
95 | }
96 |
97 | return r0
98 | }
99 |
100 | // MockSingleSignOnGateway_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
101 | type MockSingleSignOnGateway_Name_Call struct {
102 | *mock.Call
103 | }
104 |
105 | // Name is a helper method to define mock.On call
106 | func (_e *MockSingleSignOnGateway_Expecter) Name() *MockSingleSignOnGateway_Name_Call {
107 | return &MockSingleSignOnGateway_Name_Call{Call: _e.mock.On("Name")}
108 | }
109 |
110 | func (_c *MockSingleSignOnGateway_Name_Call) Run(run func()) *MockSingleSignOnGateway_Name_Call {
111 | _c.Call.Run(func(args mock.Arguments) {
112 | run()
113 | })
114 | return _c
115 | }
116 |
117 | func (_c *MockSingleSignOnGateway_Name_Call) Return(_a0 string) *MockSingleSignOnGateway_Name_Call {
118 | _c.Call.Return(_a0)
119 | return _c
120 | }
121 |
122 | func (_c *MockSingleSignOnGateway_Name_Call) RunAndReturn(run func() string) *MockSingleSignOnGateway_Name_Call {
123 | _c.Call.Return(run)
124 | return _c
125 | }
126 |
127 | // NewMockSingleSignOnGateway creates a new instance of MockSingleSignOnGateway. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
128 | // The first argument is typically a *testing.T value.
129 | func NewMockSingleSignOnGateway(t interface {
130 | mock.TestingT
131 | Cleanup(func())
132 | }) *MockSingleSignOnGateway {
133 | mock := &MockSingleSignOnGateway{}
134 | mock.Mock.Test(t)
135 |
136 | t.Cleanup(func() { mock.AssertExpectations(t) })
137 |
138 | return mock
139 | }
140 |
--------------------------------------------------------------------------------
/mocks/pet-dex-backend/v2/interfaces/mock_SingleSignOnProvider.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.43.2. DO NOT EDIT.
2 |
3 | package interfaces
4 |
5 | import (
6 | dto "pet-dex-backend/v2/entity/dto"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 | )
10 |
11 | // MockSingleSignOnProvider is an autogenerated mock type for the SingleSignOnProvider type
12 | type MockSingleSignOnProvider struct {
13 | mock.Mock
14 | }
15 |
16 | type MockSingleSignOnProvider_Expecter struct {
17 | mock *mock.Mock
18 | }
19 |
20 | func (_m *MockSingleSignOnProvider) EXPECT() *MockSingleSignOnProvider_Expecter {
21 | return &MockSingleSignOnProvider_Expecter{mock: &_m.Mock}
22 | }
23 |
24 | // GetUserDetails provides a mock function with given fields: provider, accessToken
25 | func (_m *MockSingleSignOnProvider) GetUserDetails(provider string, accessToken string) (*dto.UserSSODto, error) {
26 | ret := _m.Called(provider, accessToken)
27 |
28 | if len(ret) == 0 {
29 | panic("no return value specified for GetUserDetails")
30 | }
31 |
32 | var r0 *dto.UserSSODto
33 | var r1 error
34 | if rf, ok := ret.Get(0).(func(string, string) (*dto.UserSSODto, error)); ok {
35 | return rf(provider, accessToken)
36 | }
37 | if rf, ok := ret.Get(0).(func(string, string) *dto.UserSSODto); ok {
38 | r0 = rf(provider, accessToken)
39 | } else {
40 | if ret.Get(0) != nil {
41 | r0 = ret.Get(0).(*dto.UserSSODto)
42 | }
43 | }
44 |
45 | if rf, ok := ret.Get(1).(func(string, string) error); ok {
46 | r1 = rf(provider, accessToken)
47 | } else {
48 | r1 = ret.Error(1)
49 | }
50 |
51 | return r0, r1
52 | }
53 |
54 | // MockSingleSignOnProvider_GetUserDetails_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserDetails'
55 | type MockSingleSignOnProvider_GetUserDetails_Call struct {
56 | *mock.Call
57 | }
58 |
59 | // GetUserDetails is a helper method to define mock.On call
60 | // - provider string
61 | // - accessToken string
62 | func (_e *MockSingleSignOnProvider_Expecter) GetUserDetails(provider interface{}, accessToken interface{}) *MockSingleSignOnProvider_GetUserDetails_Call {
63 | return &MockSingleSignOnProvider_GetUserDetails_Call{Call: _e.mock.On("GetUserDetails", provider, accessToken)}
64 | }
65 |
66 | func (_c *MockSingleSignOnProvider_GetUserDetails_Call) Run(run func(provider string, accessToken string)) *MockSingleSignOnProvider_GetUserDetails_Call {
67 | _c.Call.Run(func(args mock.Arguments) {
68 | run(args[0].(string), args[1].(string))
69 | })
70 | return _c
71 | }
72 |
73 | func (_c *MockSingleSignOnProvider_GetUserDetails_Call) Return(_a0 *dto.UserSSODto, _a1 error) *MockSingleSignOnProvider_GetUserDetails_Call {
74 | _c.Call.Return(_a0, _a1)
75 | return _c
76 | }
77 |
78 | func (_c *MockSingleSignOnProvider_GetUserDetails_Call) RunAndReturn(run func(string, string) (*dto.UserSSODto, error)) *MockSingleSignOnProvider_GetUserDetails_Call {
79 | _c.Call.Return(run)
80 | return _c
81 | }
82 |
83 | // NewMockSingleSignOnProvider creates a new instance of MockSingleSignOnProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
84 | // The first argument is typically a *testing.T value.
85 | func NewMockSingleSignOnProvider(t interface {
86 | mock.TestingT
87 | Cleanup(func())
88 | }) *MockSingleSignOnProvider {
89 | mock := &MockSingleSignOnProvider{}
90 | mock.Mock.Test(t)
91 |
92 | t.Cleanup(func() { mock.AssertExpectations(t) })
93 |
94 | return mock
95 | }
96 |
--------------------------------------------------------------------------------
/pkg/encoder/encoder.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "pet-dex-backend/v2/interfaces"
5 |
6 | "github.com/golang-jwt/jwt"
7 | )
8 |
9 | type EncoderAdapter struct {
10 | secret string
11 | }
12 |
13 | func NewEncoderAdapter(secret string) *EncoderAdapter {
14 | return &EncoderAdapter{
15 | secret: secret,
16 | }
17 | }
18 |
19 | func (e *EncoderAdapter) NewAccessToken(claims interfaces.UserClaims) (string, error) {
20 | accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
21 | return accessToken.SignedString([]byte(e.secret))
22 | }
23 |
24 | func (e *EncoderAdapter) ParseAccessToken(accessToken string) *interfaces.UserClaims {
25 | parsedAccessToken, _ := jwt.ParseWithClaims(accessToken, &interfaces.UserClaims{}, func(token *jwt.Token) (interface{}, error) {
26 | return []byte(e.secret), nil
27 | })
28 | return parsedAccessToken.Claims.(*interfaces.UserClaims)
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/encoder/encoder_test.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "pet-dex-backend/v2/interfaces"
5 | "testing"
6 | "time"
7 |
8 | "github.com/golang-jwt/jwt"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | var FAKE_SECRET = "jwt_secret_key"
13 |
14 | func TestCreateEncoderAdapter(t *testing.T) {
15 | encoder := NewEncoderAdapter(FAKE_SECRET)
16 | assert.NotNil(t, encoder)
17 | }
18 |
19 | func TestCreateNewAccessToken(t *testing.T) {
20 | encoder := NewEncoderAdapter(FAKE_SECRET)
21 | claims := interfaces.UserClaims{
22 | Id: "any_user_id",
23 | Name: "any_user_name",
24 | Email: "any_user_email",
25 | Role: "any_user_role",
26 | StandardClaims: jwt.StandardClaims{
27 | ExpiresAt: time.Now().Add(time.Hour).Unix(),
28 | },
29 | }
30 | jwt, err := encoder.NewAccessToken(claims)
31 | assert.Nil(t, err)
32 | assert.NotNil(t, jwt)
33 | assert.IsType(t, "string", jwt)
34 | }
35 |
36 | func TestParseNewAccessToken(t *testing.T) {
37 | encoder := NewEncoderAdapter(FAKE_SECRET)
38 | claims := interfaces.UserClaims{
39 | Id: "any_user_id",
40 | Name: "any_user_name",
41 | Email: "any_user_email",
42 | Role: "any_user_role",
43 | StandardClaims: jwt.StandardClaims{
44 | ExpiresAt: time.Now().Add(time.Hour).Unix(),
45 | },
46 | }
47 | jwt, _ := encoder.NewAccessToken(claims)
48 | claimsParsed := encoder.ParseAccessToken(jwt)
49 | assert.NotNil(t, claimsParsed)
50 | assert.Equal(t, claimsParsed.Id, claims.Id)
51 | assert.Equal(t, claimsParsed.Name, claims.Name)
52 | assert.Equal(t, claimsParsed.Email, claims.Email)
53 | assert.Equal(t, claimsParsed.StandardClaims.ExpiresAt, claims.StandardClaims.ExpiresAt)
54 | assert.NotEqual(t, claimsParsed, claims)
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/hasher/hasher.go:
--------------------------------------------------------------------------------
1 | package hasher
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/interfaces"
6 |
7 | "golang.org/x/crypto/bcrypt"
8 | )
9 |
10 | const saltRound = 8
11 |
12 | type Hasher struct {
13 | }
14 |
15 | func NewHasher() interfaces.Hasher {
16 | return &Hasher{}
17 | }
18 |
19 | func (h *Hasher) Hash(key string) (string, error) {
20 | var err error
21 | var bytes []byte
22 | if (len(key) != 0) {
23 | bytes, err = bcrypt.GenerateFromPassword([]byte(key), saltRound)
24 | } else {
25 | err = fmt.Errorf("empty string given")
26 | }
27 |
28 | if err != nil {
29 | fmt.Println("#Hasher.Hash error: %w", err)
30 | err = fmt.Errorf("error on hashing")
31 | return "", err
32 | }
33 | return string(bytes), err
34 | }
35 |
36 | func (h *Hasher) Compare(key, toCompare string) bool {
37 | err := bcrypt.CompareHashAndPassword([]byte(toCompare), []byte(key))
38 | return err == nil
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/hasher/hasher_test.go:
--------------------------------------------------------------------------------
1 | package hasher
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewHasher(t *testing.T) {
11 | var expectedType = &Hasher{}
12 | hasher := NewHasher()
13 | assert.IsTypef(t, expectedType, hasher, "error: New Hasher not returns a *Hasher{} struct", nil)
14 | }
15 |
16 | func TestHash(t *testing.T) {
17 | //ARRANGE
18 | cases := map[string]struct {
19 | input string
20 | expectedError error
21 | }{
22 | "success": {
23 | input: "my-pass",
24 | expectedError: nil,
25 | },
26 | }
27 |
28 | for name, test := range cases {
29 | t.Run(name, func(t *testing.T) {
30 | //ACT
31 | hasher := NewHasher()
32 | hash, err := hasher.Hash(test.input)
33 |
34 | //ASSERT
35 | assert.NotEqual(t, test.input, hash, "expected output mismatch got %s", hash, test.input)
36 | assert.ErrorIs(t, test.expectedError, err, "expected error mismatch")
37 | })
38 | }
39 | }
40 |
41 | func TestHashFail(t *testing.T) {
42 | //ARRANGE
43 | cases := map[string]struct {
44 | input string
45 | expectedError error
46 | }{
47 | "failForEmpty": {
48 | input: "",
49 | expectedError: fmt.Errorf("error on hashing"),
50 | },
51 | }
52 |
53 | for name, test := range cases {
54 | t.Run(name, func(t *testing.T) {
55 | //ACT
56 | hasher := NewHasher()
57 | _, err := hasher.Hash(test.input)
58 |
59 | //ASSERT
60 | assert.Equal(t, test.expectedError, err, "expected error mismatch")
61 | })
62 | }
63 | }
64 |
65 | func TestCompare(t *testing.T) {
66 | //ARRANGE
67 | cases := map[string]struct {
68 | pass string
69 | hash string
70 | expected bool
71 | }{
72 | "success": {
73 | pass: "my-pass",
74 | expected: true,
75 | },
76 | }
77 |
78 | for name, test := range cases {
79 | t.Run(name, func(t *testing.T) {
80 | //ACT
81 | hasher := NewHasher()
82 | hash, _ := hasher.Hash(test.pass)
83 | result := hasher.Compare(test.pass, hash)
84 |
85 | //ASSERT
86 | assert.Equal(t, test.expected, result, "expected output mismatch got %t expected %t", result, test.expected)
87 | })
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/mail/README.md:
--------------------------------------------------------------------------------
1 | # Pacote de Email
2 | Para criar e enviar um email usando o pacote de email, você vai precisar dessas três coisas:
3 |
4 | - Criar a Configuração de Email: Você precisa configurar o email com suas credenciais (email, senha, provedor, host e porta).
5 |
6 | - Compor a Mensagem de Email: Defina o destinatário, o conteúdo da mensagem, o assunto e qualquer anexo.
7 |
8 | - Enviar o Email: Use a configuração e a mensagem para enviar o email.
9 |
10 | e utilizar o pacote:
11 | ```
12 | package mail
13 | ```
14 |
15 | ## 1. Criar configuração de email
16 | Para criar um email, você deve primeiro configurar o email. Para isso, use a função **CreateConfig**, passando o email, a senha (emailSecret), o provedor, o endereço do host e a porta do host.
17 |
18 | assim:
19 |
20 | cfg, err := CreateConfig("example@gmail.com", "secret", "smtp.gmail.com", "smtp.gmail.com", "587")
21 |
22 | e vai retornar um struct como essa:
23 |
24 | ```
25 | {
26 | EmailAdress string
27 | EmailSecretPassword string
28 | Provider string
29 | HostAddress string
30 | HostPort string
31 | }
32 | ```
33 |
34 | ## 2. Criar a Instância do Email
35 | Para criar o próprio email, passe a configuração criada na etapa anterior:
36 |
37 | mail := NewMail(cfg)
38 |
39 | Isso retornará uma struct com a configuração passada.
40 |
41 | assim:
42 |
43 | ```
44 | {
45 | Config *Config
46 | }
47 | ```
48 |
49 | ## 3. Criar a Mensagem de Email
50 | Para enviar um email, é necessário definir o conteúdo do email. Use a função **NewMessage** para criar uma nova mensagem. Passe o(s) destinatário(s) e o conteúdo HTML:
51 |
52 | message := NewMessage("recipient@example.com", "Hello, World!
")
53 |
54 | Isso retorna uma struct Mensagem com os seguintes campos:
55 |
56 | ```
57 | {
58 | From string
59 | To []string
60 | Html string
61 | Subject string
62 | Cc []string
63 | Bcc []string
64 | ReplyTo []string
65 | Attachments Attachment
66 | }
67 | ```
68 |
69 | ## 4. Adicionando Anexos
70 | Se o email precisar incluir anexos, use o método AttachFile:
71 |
72 | err := message.AttachFile("/path/to/file.txt")
73 |
74 | ## Enviar o Email
75 | Para enviar o email, use o método Send.
76 |
77 | err := mail.Send(message)
78 |
79 | ## 6. Exemplo Completo
80 | Aqui está um exemplo completo de como configurar a configuração, criar o email, anexar um arquivo e enviá-lo:
81 |
82 | ```
83 | cfg, err := CreateConfig("example@gmail.com", "secret", "smtp.gmail.com", "smtp.gmail.com", "587")
84 | if err != nil {
85 | log.Fatalf("Failed to create email config: %v", err)
86 | }
87 |
88 | mail := NewMail(cfg)
89 |
90 | message := NewMessage("recipient@example.com", "Hello, World!
")
91 | message.Subject = "Welcome Email"
92 |
93 | err = message.AttachFile("/path/to/file.txt")
94 | if err != nil {
95 | log.Fatalf("Failed to attach file: %v", err)
96 | }
97 |
98 | err = mail.Send(message)
99 | if err != nil {
100 | log.Fatalf("Failed to send email: %v", err)
101 | }
102 | ```
--------------------------------------------------------------------------------
/pkg/mail/config.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "fmt"
5 | "net/smtp"
6 | )
7 |
8 | type Config struct {
9 | EmailAdress string
10 | EmailSecretPassword string
11 | Provider string
12 | HostAddress string
13 | HostPort string
14 | }
15 |
16 | func (c *Config) validate() error {
17 | if !ValidateEmail(c.EmailAdress) {
18 | return fmt.Errorf("ConfigEmailAddress must be a valid email address")
19 | }
20 |
21 | if c.EmailSecretPassword == "" {
22 | return fmt.Errorf("Secret password cannot be empty")
23 | }
24 |
25 | if c.Provider == "" {
26 | return fmt.Errorf("Provider cannot be empty")
27 | }
28 |
29 | if c.HostAddress == "" {
30 | return fmt.Errorf("Host address cannot be empty")
31 | }
32 |
33 | if c.HostPort == "" {
34 | return fmt.Errorf("Host port cannot be empty")
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func CreateConfig(emailAddress, emailSecret, provider, hostAddress, hostPort string) (*Config, error) {
41 | cfg := Config{
42 | EmailAdress: emailAddress,
43 | EmailSecretPassword: emailSecret,
44 | Provider: provider,
45 | HostAddress: hostAddress,
46 | HostPort: hostPort,
47 | }
48 |
49 | err := cfg.validate()
50 | if err != nil {
51 | return nil, err
52 | }
53 | return &cfg, nil
54 | }
55 |
56 | func (c *Config) setAuth() smtp.Auth {
57 | auth := smtp.PlainAuth("", c.EmailAdress, c.EmailSecretPassword, c.Provider)
58 |
59 | return auth
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/mail/mail.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "fmt"
5 | "net/smtp"
6 | )
7 |
8 | type Mail struct {
9 | Config *Config
10 | }
11 |
12 | type IMail interface {
13 | Send(message *Message) error
14 | }
15 |
16 | func NewMail(config *Config) *Mail {
17 | return &Mail{
18 | Config: config,
19 | }
20 | }
21 |
22 | func (m *Mail) Send(message *Message) error {
23 | emailValid := ValidateEmail(message.From)
24 | if !emailValid {
25 | return fmt.Errorf("invalid email address")
26 | }
27 |
28 | auth := m.Config.setAuth()
29 | hostAddres := fmt.Sprintf("%s:%s", m.Config.HostAddress, ":"+m.Config.HostPort)
30 | err := smtp.SendMail(hostAddres, auth, message.From, message.To, message.ToBytes())
31 | if err != nil {
32 | return err
33 | }
34 |
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/mail/mail_test.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCreateConfigForMailPkg(t *testing.T) {
10 | cfg, err := CreateConfig("example@gmail.com", "secret", "smtp.gmail.com", "smtp.gmail.com", "587")
11 |
12 | assert.Nil(t, err)
13 | assert.Equal(t, cfg.EmailAdress, "example@gmail.com")
14 | assert.Equal(t, cfg.EmailSecretPassword, "secret")
15 | assert.Equal(t, cfg.Provider, "smtp.gmail.com")
16 | assert.Equal(t, cfg.HostAddress, "smtp.gmail.com")
17 | assert.Equal(t, cfg.HostPort, "587")
18 | assert.NotNil(t, cfg)
19 | }
20 |
21 | func TestCreateMail(t *testing.T) {
22 | cfg, err := CreateConfig("example@gmail.com", "secret", "smtp.gmail.com", "smtp.gmail.com", "587")
23 | assert.Nil(t, err)
24 | assert.Equal(t, cfg.EmailAdress, "example@gmail.com")
25 |
26 | mail := NewMail(cfg)
27 | assert.NotNil(t, mail)
28 | }
29 |
30 | func TestErrorOnCreateConfigMissing(t *testing.T) {
31 | configsParamethers := []struct {
32 | EmailAdress string
33 | EmailSecretPassword string
34 | Provider string
35 | HostAddress string
36 | HostPort string
37 | ErrorMessage string
38 | }{
39 | {EmailAdress: "example@.com", EmailSecretPassword: "secret", Provider: "smtp.gmail.com", HostAddress: "smtp.gmail.com", HostPort: "587", ErrorMessage: "ConfigEmailAddress must be a valid email address"},
40 | {EmailAdress: "example@gmail.com", EmailSecretPassword: "", Provider: "smtp.gmail.com", HostAddress: "smtp.gmail.com", HostPort: "587", ErrorMessage: "Secret password cannot be empty"},
41 | {EmailAdress: "example@gmail.com", EmailSecretPassword: "secret", Provider: "", HostAddress: "smtp.gmail.com", HostPort: "587", ErrorMessage: "Provider cannot be empty"},
42 | {EmailAdress: "example@gmail.com", EmailSecretPassword: "secret", Provider: "smtp.gmail.com", HostAddress: "", HostPort: "587", ErrorMessage: "Host address cannot be empty"},
43 | {EmailAdress: "example@gmail.com", EmailSecretPassword: "secret", Provider: "smtp.gmail.com", HostAddress: "smtp.gmail.com", HostPort: "", ErrorMessage: "Host port cannot be empty"},
44 | }
45 |
46 | for _, config := range configsParamethers {
47 | cfg, err := CreateConfig(config.EmailAdress, config.EmailSecretPassword, config.Provider, config.HostAddress, config.HostPort)
48 |
49 | assert.Nil(t, cfg)
50 | assert.NotNil(t, err)
51 | assert.Error(t, err)
52 | assert.Error(t, err, config.ErrorMessage)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/mail/message.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "fmt"
7 | "mime/multipart"
8 | "net/http"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | type Message struct {
15 | From string
16 | To []string
17 | Html string
18 | Subject string
19 | Cc []string
20 | Bcc []string
21 | ReplyTo []string
22 | Attachments Attachment
23 | }
24 |
25 | type Attachment = map[string][]byte
26 |
27 | func NewMessage(to []string, msg string) *Message {
28 | return &Message{
29 | To: to,
30 | Html: msg,
31 | }
32 | }
33 |
34 | func (m *Message) AttachFile(path string) error {
35 | fileBytes, err := os.ReadFile(path)
36 | if err != nil {
37 | return err
38 | }
39 |
40 | _, filename := filepath.Split(path)
41 | m.Attachments[filename] = fileBytes
42 |
43 | return nil
44 | }
45 |
46 | func (m *Message) ToBytes() []byte {
47 | buffer := bytes.NewBuffer(nil)
48 | withAttachments := len(m.Attachments) > 0
49 |
50 | buffer.WriteString(fmt.Sprintf("Subject: %s\n", m.Subject))
51 | buffer.WriteString(fmt.Sprintf("To: %s\n", strings.Join(m.To, ",")))
52 | if len(m.Cc) > 0 {
53 | buffer.WriteString(fmt.Sprintf("Cc: %s\n", strings.Join(m.Cc, ",")))
54 | }
55 |
56 | if len(m.Bcc) > 0 {
57 | buffer.WriteString(fmt.Sprintf("Bcc: %s\n", strings.Join(m.Bcc, ",")))
58 | }
59 |
60 | buffer.WriteString("MIME-Version: 1.0\n")
61 | writer := multipart.NewWriter(buffer)
62 | boundary := writer.Boundary()
63 | if withAttachments {
64 | buffer.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary))
65 | buffer.WriteString(fmt.Sprintf("--%s\n", boundary))
66 | } else {
67 | buffer.WriteString("Content-Type: text/plain; charset=utf-8\n")
68 | }
69 |
70 | buffer.WriteString(m.Html)
71 | if withAttachments {
72 | for k, v := range m.Attachments {
73 | buffer.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
74 | buffer.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(v)))
75 | buffer.WriteString("Content-Transfer-Encoding: base64\n")
76 | buffer.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", k))
77 |
78 | b := make([]byte, base64.StdEncoding.EncodedLen(len(v)))
79 | base64.StdEncoding.Encode(b, v)
80 | buffer.Write(b)
81 | buffer.WriteString(fmt.Sprintf("\n--%s", boundary))
82 | }
83 |
84 | buffer.WriteString("--")
85 | }
86 |
87 | return buffer.Bytes()
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/mail/validate.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import "regexp"
4 |
5 | func ValidateEmail(e string) bool {
6 | emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
7 | return emailRegex.MatchString(e)
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/migration/migration.go:
--------------------------------------------------------------------------------
1 | package migration
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "log"
7 | "os"
8 | "pet-dex-backend/v2/infra/config"
9 | "time"
10 |
11 | _ "github.com/go-sql-driver/mysql"
12 | "github.com/golang-migrate/migrate/v4"
13 | "github.com/golang-migrate/migrate/v4/database/mysql"
14 | _ "github.com/golang-migrate/migrate/v4/source/file"
15 | )
16 |
17 | func Up() {
18 | env, err := config.LoadEnv("../")
19 | if err != nil {
20 | log.Fatalf("Failed to load .env file: %v\n", err)
21 | }
22 | databaseUrl := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", env.DB_USER, env.DB_PASSWORD, env.MIGRATION_HOST, env.DB_PORT, env.DB_DATABASE)
23 | db, err := sql.Open("mysql", databaseUrl)
24 | if err != nil {
25 | log.Fatalf("Failed connecting to the database: %v\n", err)
26 | }
27 | defer func() {
28 | if err := db.Close(); err != nil {
29 | log.Fatal(err)
30 | }
31 | }()
32 |
33 | driver, err := mysql.WithInstance(db, &mysql.Config{})
34 | if err != nil {
35 | log.Fatalf("Failed to create MySQL driver instance: %v\n", err)
36 | }
37 |
38 | migration, err := migrate.NewWithDatabaseInstance(
39 | "file://migrations",
40 | "mysql",
41 | driver,
42 | )
43 | if err != nil {
44 | log.Fatalf("Failed to create migration instance: %v\n", err)
45 | }
46 |
47 | err = migration.Up()
48 | if err != nil {
49 | log.Fatalf("Failed on running migrations up: %v\n", err)
50 | return
51 | }
52 | }
53 |
54 | func Down() {
55 | env, err := config.LoadEnv("../")
56 | if err != nil {
57 | log.Fatalf("Failed to load .env file: %v\n", err)
58 | }
59 | databaseUrl := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", env.DB_USER, env.DB_PASSWORD, env.MIGRATION_HOST, env.DB_PORT, env.DB_DATABASE)
60 | db, err := sql.Open("mysql", databaseUrl)
61 | if err != nil {
62 | log.Fatalf("Failed connecting to the database: %v\n", err)
63 | }
64 | defer func() {
65 | if err := db.Close(); err != nil {
66 | log.Fatal(err)
67 | }
68 | }()
69 |
70 | driver, err := mysql.WithInstance(db, &mysql.Config{})
71 | if err != nil {
72 | log.Fatalf("Failed to create MySQL driver instance: %v\n", err)
73 | }
74 |
75 | migration, err := migrate.NewWithDatabaseInstance(
76 | "file://migrations",
77 | "mysql",
78 | driver,
79 | )
80 | if err != nil {
81 | panic(err)
82 | }
83 | err = migration.Down()
84 | if err != nil {
85 | log.Fatalf("Failed on running migrations down: %v\n", err)
86 | return
87 | }
88 |
89 | }
90 |
91 | func Create(name string) {
92 | path, err := config.LoadEnv("../../")
93 | if err != nil {
94 | fmt.Println("Error loading the .env file:", err)
95 | }
96 | data := time.Now()
97 | timestamp := data.Format("20060102150405")
98 | fmt.Println("Current date and time: ", timestamp)
99 | fileNameDown := fmt.Sprintf("%s/%s_%s.down.sql", path.MIGRATIONS_PATH, timestamp, name)
100 | fileNameUp := fmt.Sprintf("%s/%s_%s.up.sql", path.MIGRATIONS_PATH, timestamp, name)
101 | // Create the file
102 | fileDown, err := os.Create(fileNameDown)
103 | if err != nil {
104 | fmt.Println("Error creating down file:", err)
105 | return
106 | }
107 | defer fileDown.Close()
108 |
109 | fileUp, err := os.Create(fileNameUp)
110 | if err != nil {
111 | fmt.Println("Error creating up file:", err)
112 | return
113 | }
114 | defer fileUp.Close()
115 | }
116 |
--------------------------------------------------------------------------------
/pkg/sso/facebook.go:
--------------------------------------------------------------------------------
1 | package sso
2 |
3 | import (
4 | "errors"
5 | "pet-dex-backend/v2/entity/dto"
6 | "pet-dex-backend/v2/infra/config"
7 |
8 | fb "github.com/huandu/facebook/v2"
9 | )
10 |
11 | type FacebookSSO struct {
12 | name string
13 | id string
14 | secret string
15 | }
16 |
17 | func NewFacebookGateway(env *config.Envconfig) *FacebookSSO {
18 | return &FacebookSSO{
19 | name: "facebook",
20 | id: env.FACEBOOK_APP_ID,
21 | secret: env.FACEBOOK_APP_SECRET,
22 | }
23 | }
24 |
25 | func (f *FacebookSSO) GetUserDetails(accessToken string) (*dto.UserSSODto, error) {
26 | if f.id == "" || f.secret == "" {
27 | return nil, errors.New("facebook app id or secret missing")
28 | }
29 |
30 | var globalApp = fb.New(f.id, f.secret)
31 | session := globalApp.Session(accessToken)
32 | err := session.Validate()
33 |
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | res, err := session.Get("/me?fields=name,email", nil)
39 |
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | var userDetails dto.UserSSODto
45 |
46 | err = res.Decode(&userDetails)
47 |
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | if userDetails.Email == "" {
53 | return nil, errors.New("email scope not authorized at facebook app")
54 | }
55 |
56 | return &userDetails, nil
57 | }
58 |
59 | func (f *FacebookSSO) Name() string {
60 | return f.name
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/sso/google.go:
--------------------------------------------------------------------------------
1 | package sso
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "io"
8 | "pet-dex-backend/v2/entity/dto"
9 | "pet-dex-backend/v2/infra/config"
10 |
11 | "golang.org/x/oauth2"
12 | "golang.org/x/oauth2/google"
13 | )
14 |
15 | type GoogleUserDetails struct {
16 | Id string `json:"id"`
17 | Email string `json:"email"`
18 | VerifiedEmail bool `json:"verified_email"`
19 | Name string `json:"name"`
20 | GivenName string `json:"given_name"`
21 | FamilyName string `json:"family_name"`
22 | Picture string `json:"picture"`
23 | }
24 |
25 | type GoogleSSO struct {
26 | name string
27 | client string
28 | secret string
29 | redirect string
30 | }
31 |
32 | func NewGoogleGateway(env *config.Envconfig) *GoogleSSO {
33 | return &GoogleSSO{
34 | name: "google",
35 | client: env.GOOGLE_OAUTH_CLIENT_ID,
36 | secret: env.GOOGLE_OAUTH_CLIENT_SECRET,
37 | redirect: env.GOOGLE_REDIRECT_URL,
38 | }
39 | }
40 |
41 | func (g *GoogleSSO) GetUserDetails(accessToken string) (*dto.UserSSODto, error) {
42 | if g.client == "" || g.secret == "" {
43 | return nil, errors.New("google client id or secret missing")
44 | }
45 | if g.redirect == "" {
46 | return nil, errors.New("google redirect url missing")
47 | }
48 | conf := oauth2.Config{
49 | ClientID: g.client,
50 | ClientSecret: g.secret,
51 | Scopes: []string{
52 | "https://www.googleapis.com/auth/userinfo.email",
53 | "https://www.googleapis.com/auth/userinfo.profile",
54 | },
55 | Endpoint: google.Endpoint,
56 | RedirectURL: g.redirect,
57 | }
58 |
59 | ctx := context.Background()
60 |
61 | token, err := conf.Exchange(ctx, accessToken)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | response, err := conf.Client(ctx, token).Get("https://www.googleapis.com/oauth2/v2/userinfo")
67 | if err != nil {
68 | return nil, err
69 | }
70 | defer response.Body.Close()
71 | client, err := io.ReadAll(response.Body)
72 | if err != nil {
73 | return nil, err
74 | }
75 |
76 | var googleUserDetails GoogleUserDetails
77 | err = json.Unmarshal(client, &googleUserDetails)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | userDetails := dto.UserSSODto{
83 | Name: googleUserDetails.Name,
84 | Email: googleUserDetails.Email,
85 | }
86 | return &userDetails, nil
87 | }
88 |
89 | func (g *GoogleSSO) Name() string {
90 | return g.name
91 | }
92 |
--------------------------------------------------------------------------------
/pkg/sso/provider.go:
--------------------------------------------------------------------------------
1 | package sso
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity/dto"
6 | "pet-dex-backend/v2/interfaces"
7 | )
8 |
9 | type Provider struct {
10 | gateways map[string]interfaces.SingleSignOnGateway
11 | }
12 |
13 | func NewProvider(gateways ...interfaces.SingleSignOnGateway) *Provider {
14 | p := &Provider{
15 | gateways: make(map[string]interfaces.SingleSignOnGateway),
16 | }
17 |
18 | for _, g := range gateways {
19 | p.gateways[g.Name()] = g
20 | }
21 |
22 | return p
23 | }
24 |
25 | func (p *Provider) GetUserDetails(provider, accessToken string) (*dto.UserSSODto, error) {
26 |
27 | g, ok := p.gateways[provider]
28 | if !ok {
29 | return nil, fmt.Errorf("Provider %s not found", provider)
30 | }
31 |
32 | return g.GetUserDetails(accessToken)
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/sso/provider_test.go:
--------------------------------------------------------------------------------
1 | package sso
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "pet-dex-backend/v2/entity/dto"
8 | "pet-dex-backend/v2/interfaces"
9 | mockInterfaces "pet-dex-backend/v2/mocks/pet-dex-backend/v2/interfaces"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | func TestNewProvider(t *testing.T) {
16 | tcases := map[string]struct {
17 | signInGateways []interfaces.SingleSignOnGateway
18 | mock func(*testing.T, []interfaces.SingleSignOnGateway)
19 | expectedCompareReturn []string
20 | }{
21 | "New Providers": {
22 | signInGateways: []interfaces.SingleSignOnGateway{
23 | mockInterfaces.NewMockSingleSignOnGateway(t),
24 | mockInterfaces.NewMockSingleSignOnGateway(t),
25 | },
26 | mock: func(t *testing.T, m []interfaces.SingleSignOnGateway) {
27 | m0, ok := m[0].(*mockInterfaces.MockSingleSignOnGateway)
28 | require.True(t, ok)
29 | m0.On("Name").Return("gateway1")
30 | m1, ok := m[1].(*mockInterfaces.MockSingleSignOnGateway)
31 | require.True(t, ok)
32 | m1.On("Name").Return("gateway2")
33 | },
34 | expectedCompareReturn: []string{
35 | "gateway1",
36 | "gateway2",
37 | },
38 | },
39 | "No Providers": {
40 | signInGateways: nil,
41 | mock: func(t *testing.T, m []interfaces.SingleSignOnGateway) {},
42 | expectedCompareReturn: []string{},
43 | },
44 | }
45 |
46 | for name, tcase := range tcases {
47 | t.Run(name, func(t *testing.T) {
48 | tcase.mock(t, tcase.signInGateways)
49 |
50 | provider := NewProvider(tcase.signInGateways...)
51 |
52 | for _, gateway := range tcase.expectedCompareReturn {
53 | _, ok := provider.gateways[gateway]
54 | assert.True(t, ok, fmt.Sprintf("expected gateway \"%s\" not found", gateway))
55 | }
56 |
57 | expectedSize := len(tcase.expectedCompareReturn)
58 | returnedSize := len(provider.gateways)
59 |
60 | assert.Equal(t, expectedSize, returnedSize, fmt.Sprintf("unexpected gateway len. expected %d, received %d", expectedSize, returnedSize))
61 | })
62 | }
63 | }
64 |
65 | func TestGetUserDetails(t *testing.T) {
66 | tcases := map[string]struct {
67 | signInGateway interfaces.SingleSignOnGateway
68 | providerName string
69 | mock func(*testing.T, interfaces.SingleSignOnGateway, *dto.UserSSODto)
70 | expectedUser *dto.UserSSODto
71 | expectedErr error
72 | }{
73 | "Success": {
74 | signInGateway: mockInterfaces.NewMockSingleSignOnGateway(t),
75 | providerName: "gateway1",
76 | mock: func(t *testing.T, m interfaces.SingleSignOnGateway, getUserDetailsReturn *dto.UserSSODto) {
77 | m0, ok := m.(*mockInterfaces.MockSingleSignOnGateway)
78 | require.True(t, ok)
79 | m0.On("Name").Return("gateway1")
80 | m0.On("GetUserDetails", "").Return(getUserDetailsReturn, nil)
81 | },
82 | expectedUser: &dto.UserSSODto{
83 | Name: "g1",
84 | Email: "abc@gmail.com",
85 | },
86 | expectedErr: nil,
87 | },
88 | "Error": {
89 | signInGateway: mockInterfaces.NewMockSingleSignOnGateway(t),
90 | providerName: "gateway1",
91 | mock: func(t *testing.T, m interfaces.SingleSignOnGateway, getUserDetailsReturn *dto.UserSSODto) {
92 | m0, ok := m.(*mockInterfaces.MockSingleSignOnGateway)
93 | require.True(t, ok)
94 | m0.On("Name").Return("gateway2")
95 | },
96 | expectedUser: nil,
97 | expectedErr: fmt.Errorf("Provider %s not found", "gateway1"),
98 | },
99 | }
100 |
101 | for name, tcase := range tcases {
102 | t.Run(name, func(t *testing.T) {
103 | tcase.mock(t, tcase.signInGateway, tcase.expectedUser)
104 |
105 | provider := NewProvider(tcase.signInGateway)
106 |
107 | user, err := provider.GetUserDetails(tcase.providerName, "")
108 |
109 | assert.Equal(t, err, tcase.expectedErr, fmt.Sprintf("unexpected Error expected %v, received %v", tcase.expectedErr, err))
110 |
111 | assert.EqualValues(t, user, tcase.expectedUser, fmt.Sprintf("unexpected User expected %v, received %v", tcase.expectedUser, err))
112 | })
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/uniqueEntityId/id.go:
--------------------------------------------------------------------------------
1 | package uniqueEntityId
2 |
3 | import "github.com/google/uuid"
4 |
5 | type ID = uuid.UUID
6 |
7 | func NewID() ID {
8 | return ID(uuid.New())
9 | }
10 |
11 | func ParseID(s string) (ID, error) {
12 | id, err := uuid.Parse(s)
13 | return ID(id), err
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/uniqueEntityId/id_test.go:
--------------------------------------------------------------------------------
1 | package uniqueEntityId
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCreateEntityUuid(t *testing.T) {
10 | id := NewID()
11 |
12 | assert.NotNil(t, id)
13 | }
14 |
15 | func TestParseId(t *testing.T) {
16 | idString := "cf8010b6-faef-4911-88ab-2117a14deed9"
17 |
18 | id, err := ParseID(idString)
19 |
20 | assert.Nil(t, err)
21 | assert.NotNil(t, id)
22 | assert.Equal(t, id.String(), idString)
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/utils/user.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "regexp"
4 |
5 | func IsValidPassword(password string) bool {
6 | lengthRegex := regexp.MustCompile(`^.{6,}$`)
7 | uppercaseRegex := regexp.MustCompile(`[A-Z]`)
8 | lowercaseRegex := regexp.MustCompile(`[a-z]`)
9 | digitRegex := regexp.MustCompile(`[0-9]`)
10 | specialCharRegex := regexp.MustCompile(`[!@#$%^&*()_+{}":;'?/.,<>]`)
11 |
12 | return lengthRegex.MatchString(password) &&
13 | uppercaseRegex.MatchString(password) &&
14 | specialCharRegex.MatchString(password) &&
15 | lowercaseRegex.MatchString(password) &&
16 | digitRegex.MatchString(password)
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/utils/user_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestISValidPassword(t *testing.T) {
10 | cases := map[string]struct {
11 | pass string
12 | errorMessage string
13 | expected bool
14 | }{
15 | "Valid Password": {
16 | pass: "Patrick123!",
17 | errorMessage: "Valid Password",
18 | expected: true,
19 | },
20 | "Short Password": {
21 | pass: "Paa1!",
22 | errorMessage: "Password must be at least 6 characters",
23 | expected: false,
24 | },
25 | "No Upper Letter Password": {
26 | pass: "patrick123!",
27 | errorMessage: "Password must have at least one uppercase letter",
28 | expected: false,
29 | },
30 | "No Special Character Password": {
31 | pass: "Patrick123",
32 | errorMessage: "Password must have at least one special character",
33 | expected: false,
34 | },
35 | "No lower Letter Character Password": {
36 | pass: "PATRICK123!",
37 | errorMessage: "Password must have at least one lowercase letter",
38 | expected: false,
39 | },
40 | "No digit Password": {
41 | pass: "Patrick!",
42 | errorMessage: "Password must have at least one number",
43 | expected: false,
44 | },
45 | }
46 |
47 | for name, test := range cases {
48 | t.Run(name, func(t *testing.T) {
49 | result := IsValidPassword(test.pass)
50 | assert.Equal(t, test.expected, result, test.errorMessage)
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/swagger/details.md:
--------------------------------------------------------------------------------
1 | ## Samples requests
2 |
3 | ```
4 | GET /api/ongs/
5 | GET /api/ongs?limite=10&offset=2
6 | GET /api/ongs?sortBy=name&order=asc
7 |
8 | ```
--------------------------------------------------------------------------------
/usecase/adopt.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/interfaces"
6 | )
7 |
8 | type AdoptUseCase struct {
9 | petRepository interfaces.PetRepository
10 | }
11 |
12 | func NewAdoptUseCase (p interfaces.PetRepository) *AdoptUseCase {
13 | return &AdoptUseCase{
14 | petRepository: p,
15 | }
16 | }
17 |
18 | func (auc *AdoptUseCase) Do () {
19 | fmt.Println("Hello, World!")
20 | }
21 |
--------------------------------------------------------------------------------
/usecase/adopt_test.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import "testing"
4 |
5 | func TestNewAdoptUseCase (t *testing.T) {
6 |
7 | }
--------------------------------------------------------------------------------
/usecase/breed.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity"
6 | "pet-dex-backend/v2/entity/dto"
7 | "pet-dex-backend/v2/infra/config"
8 | "pet-dex-backend/v2/interfaces"
9 | "pet-dex-backend/v2/pkg/uniqueEntityId"
10 | )
11 |
12 | var loggerBreed = config.GetLogger("breed-usecase")
13 |
14 | type BreedUseCase struct {
15 | repo interfaces.BreedRepository
16 | }
17 |
18 | func NewBreedUseCase(repo interfaces.BreedRepository) *BreedUseCase {
19 | return &BreedUseCase{
20 | repo: repo,
21 | }
22 | }
23 |
24 | func (useCase *BreedUseCase) List() ([]*dto.BreedList, error) {
25 | breed, err := useCase.repo.List()
26 | if err != nil {
27 | loggerBreed.Error("error listing breeds", err)
28 | err = fmt.Errorf("error listing breeds: %s", err)
29 | return nil, err
30 | }
31 | return breed, nil
32 | }
33 |
34 | func (useCase *BreedUseCase) FindByID(ID uniqueEntityId.ID) (*entity.Breed, error) {
35 | breed, err := useCase.repo.FindByID(ID)
36 | if err != nil {
37 | err = fmt.Errorf("failed to retrieve breed: %s", err)
38 | return nil, err
39 | }
40 | return breed, nil
41 | }
--------------------------------------------------------------------------------
/usecase/breed_test.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity"
6 | "pet-dex-backend/v2/entity/dto"
7 | mockInterfaces "pet-dex-backend/v2/mocks/pet-dex-backend/v2/interfaces"
8 | "pet-dex-backend/v2/pkg/uniqueEntityId"
9 | "testing"
10 |
11 | "github.com/google/uuid"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestList(t *testing.T) {
16 | uuidList := []uniqueEntityId.ID{
17 | uuid.MustParse("f3768895-d8cc-40d7-b8ae-8b7eb0eac26c"),
18 | uuid.MustParse("db6ba220-19dc-4f6c-845f-0fbf84c275b9"),
19 | uuid.MustParse("eb90009f-dfcc-4568-95b9-3f393ef9a9c2"),
20 | uuid.MustParse("43c7b32a-3e31-4894-8c93-0e8b29415caa"),
21 | }
22 |
23 | tcases := map[string]struct {
24 | repo *mockInterfaces.MockBreedRepository
25 | expectOutput []*dto.BreedList
26 | expectedError error
27 | }{
28 | "success": {
29 | repo: mockInterfaces.NewMockBreedRepository(t),
30 | expectOutput: []*dto.BreedList{
31 | {ID: uuidList[0], Name: "Amarelo", ImgUrl: "image url 1"},
32 | {ID: uuidList[1], Name: "Caramela", ImgUrl: "image url 2"},
33 | {ID: uuidList[2], Name: "Nuvem", ImgUrl: "image url 3"},
34 | {ID: uuidList[3], Name: "Thor", ImgUrl: "image url 4"},
35 | },
36 | expectedError: nil,
37 | },
38 | }
39 |
40 | for name, tcase := range tcases {
41 | t.Run(name, func(t *testing.T) {
42 | tcase.repo.On("List").Return(tcase.expectOutput, nil)
43 |
44 | usecase := NewBreedUseCase(tcase.repo)
45 | list, err := usecase.List()
46 |
47 | assert.Equal(t, tcase.expectOutput, list, "expected output mismatch")
48 | assert.Equal(t, tcase.expectedError, err, "expected error mismatch")
49 | })
50 | }
51 | }
52 |
53 | func TestListErrorOnRepo(t *testing.T) {
54 |
55 | tcases := map[string]struct {
56 | repo *mockInterfaces.MockBreedRepository
57 | expectOutput []*dto.BreedList
58 | mockError error
59 | expectedError error
60 | }{
61 | "errorList": {
62 | repo: mockInterfaces.NewMockBreedRepository(t),
63 | expectOutput: nil,
64 | mockError: fmt.Errorf("error listing breeds"),
65 | expectedError: fmt.Errorf("error listing breeds: error listing breeds"),
66 | },
67 | }
68 |
69 | for name, tcase := range tcases {
70 | t.Run(name, func(t *testing.T) {
71 | tcase.repo.On("List").Return(tcase.expectOutput, tcase.mockError)
72 |
73 | usecase := NewBreedUseCase(tcase.repo)
74 | _, err := usecase.List()
75 |
76 | assert.Equal(t, tcase.expectedError, err, "expected error mismatch")
77 | })
78 | }
79 | }
80 |
81 | func TestBreedFindByID(t *testing.T) {
82 | ID := uniqueEntityId.NewID()
83 |
84 | expectedBreed := &entity.Breed{ID: ID, Name: "Pastor Alemão", Specie: "Dog"}
85 |
86 | tcases := map[string]struct {
87 | repo *mockInterfaces.MockBreedRepository
88 | expectOutput *entity.Breed
89 | expectedError error
90 | }{
91 | "success": {
92 | repo: mockInterfaces.NewMockBreedRepository(t),
93 | expectOutput: expectedBreed,
94 | expectedError: nil,
95 | },
96 | }
97 |
98 | for name, tcase := range tcases {
99 | t.Run(name, func(t *testing.T) {
100 | tcase.repo.On("FindByID", expectedBreed.ID).Return(tcase.expectOutput, tcase.expectedError)
101 |
102 | usecase := NewBreedUseCase(tcase.repo)
103 | list, err := usecase.FindByID(expectedBreed.ID)
104 |
105 | assert.Equal(t, tcase.expectOutput, list, "expected output mismatch")
106 | assert.Equal(t, tcase.expectedError, err, "expected error mismatch")
107 | })
108 | }
109 | }
110 |
111 | func TestBreedFindByIDErrorOnRepo(t *testing.T) {
112 | ID := uniqueEntityId.NewID()
113 |
114 | tcases := map[string]struct {
115 | repo *mockInterfaces.MockBreedRepository
116 | expectOutput *entity.Breed
117 | mockInput error
118 | expectedError error
119 | }{
120 | "error": {
121 | repo: mockInterfaces.NewMockBreedRepository(t),
122 | expectOutput: nil,
123 | mockInput: fmt.Errorf("error retrieving breed"),
124 | expectedError: fmt.Errorf("failed to retrieve breed:"),
125 | },
126 | }
127 |
128 | for name, tcase := range tcases {
129 | t.Run(name, func(t *testing.T) {
130 | tcase.repo.On("FindByID", ID).Return(tcase.expectOutput, tcase.mockInput)
131 |
132 | usecase := NewBreedUseCase(tcase.repo)
133 | list, err := usecase.FindByID(ID)
134 |
135 | assert.EqualError(t, err, "failed to retrieve breed: error retrieving breed")
136 | assert.Equal(t, list, tcase.expectOutput)
137 | assert.Error(t, err, tcase.expectedError)
138 | })
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/usecase/ong.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "fmt"
5 | "pet-dex-backend/v2/entity"
6 | "pet-dex-backend/v2/entity/dto"
7 | "pet-dex-backend/v2/infra/config"
8 | "pet-dex-backend/v2/interfaces"
9 | "pet-dex-backend/v2/pkg/uniqueEntityId"
10 | )
11 |
12 | type OngUsecase struct {
13 | repo interfaces.OngRepository
14 | userRepo interfaces.UserRepository
15 | hasher interfaces.Hasher
16 | logger config.Logger
17 | }
18 |
19 | func NewOngUseCase(repo interfaces.OngRepository, userRepo interfaces.UserRepository, hasher interfaces.Hasher) *OngUsecase {
20 | return &OngUsecase{
21 | repo: repo,
22 | userRepo: userRepo,
23 | hasher: hasher,
24 | logger: *config.NewLogger("ong-usecase"),
25 | }
26 | }
27 |
28 | func (o *OngUsecase) Save(ongDto *dto.OngInsertDto) error {
29 | ong := entity.NewOng(*ongDto)
30 | hashedPass, err := o.hasher.Hash(ong.User.Pass)
31 |
32 | if err != nil {
33 | o.logger.Error("error on ong usecase: ", err)
34 | return err
35 | }
36 |
37 | ong.User.Pass = hashedPass
38 |
39 | err = o.userRepo.Save(&ong.User)
40 |
41 | if err != nil {
42 | fmt.Println(fmt.Errorf("#OngUseCase.SaveUser error: %w", err))
43 | return err
44 | }
45 |
46 | err = o.userRepo.SaveAddress(&ong.User.Adresses)
47 |
48 | if err != nil {
49 | fmt.Println(fmt.Errorf("#OngUseCase.SaveAddress error: %w", err))
50 | return err
51 | }
52 |
53 | err = o.repo.Save(ong)
54 |
55 | if err != nil {
56 | o.logger.Error("error on ong Save: ", err)
57 | return err
58 | }
59 |
60 | return nil
61 |
62 | }
63 |
64 | func (o *OngUsecase) List(limit, offset int, sortBy, order string) ([]*dto.OngListMapper, error) {
65 | ong, err := o.repo.List(limit, offset, sortBy, order)
66 |
67 | if err != nil {
68 | err = fmt.Errorf("error listing ongs: %w", err)
69 | return nil, err
70 | }
71 | return ong, nil
72 | }
73 |
74 | func (c *OngUsecase) FindByID(ID uniqueEntityId.ID) (*dto.OngListMapper, error) {
75 |
76 | ong, err := c.repo.FindByID(ID)
77 |
78 | if err != nil {
79 | c.logger.Error("error on ong repository: ", err)
80 | err = fmt.Errorf("failed to retrieve ong: %w", err)
81 | return nil, err
82 | }
83 |
84 | return ong, nil
85 | }
86 |
87 | func (o *OngUsecase) Update(ongId uniqueEntityId.ID, ongDto *dto.OngUpdateDto) error {
88 | ongToUpdate, _ := entity.OngToUpdate(*ongDto)
89 |
90 | ong, err := o.repo.FindByID(ongId)
91 | if err != nil {
92 | o.logger.Error("error on ong usecase: ", err)
93 | return err
94 | }
95 |
96 | err = o.userRepo.Update(ong.UserID, ongToUpdate.User)
97 | if err != nil {
98 | o.logger.Error("error on ong usecase: ", err)
99 | return err
100 | }
101 |
102 | err = o.repo.Update(ongId, *ongToUpdate)
103 | if err != nil {
104 | o.logger.Error("error on ong usecase: ", err)
105 | return err
106 | }
107 |
108 | return nil
109 |
110 | }
111 |
112 | func (o *OngUsecase) Delete(ongId uniqueEntityId.ID) error {
113 | _, err := o.repo.FindByID(ongId)
114 | if err != nil {
115 | o.logger.Error("error on ong usecase: ", err)
116 | return err
117 | }
118 | err = o.repo.Delete(ongId)
119 | if err != nil {
120 | o.logger.Error("error on ong usecase: ", err)
121 | return err
122 | }
123 | return nil
124 | }
125 |
--------------------------------------------------------------------------------
/usecase/ong_test.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "pet-dex-backend/v2/entity/dto"
5 | mockInterfaces "pet-dex-backend/v2/mocks/pet-dex-backend/v2/interfaces"
6 | "pet-dex-backend/v2/pkg/uniqueEntityId"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestOngDelete(t *testing.T) {
13 | tcases := map[string]struct {
14 | repo *mockInterfaces.MockOngRepository
15 | inputID uniqueEntityId.ID
16 | findByIDResp *dto.OngListMapper
17 | findByIDErr error
18 | deleteResp error
19 | expectOutput error
20 | }{
21 | "success": {
22 | repo: mockInterfaces.NewMockOngRepository(t),
23 | inputID: uniqueEntityId.NewID(),
24 | findByIDResp: &dto.OngListMapper{},
25 | findByIDErr: nil,
26 | deleteResp: nil,
27 | expectOutput: nil,
28 | },
29 | }
30 |
31 | for name, tcase := range tcases {
32 | t.Run(name, func(t *testing.T) {
33 | tcase.repo.On("FindByID", tcase.inputID).Return(nil, tcase.expectOutput)
34 | tcase.repo.On("Delete", tcase.inputID).Return(tcase.expectOutput)
35 |
36 | usecase := NewOngUseCase(tcase.repo, nil, nil)
37 | err := usecase.Delete(tcase.inputID)
38 |
39 | assert.Equal(t, tcase.expectOutput, err, "expected error mismatch")
40 | tcase.repo.AssertExpectations(t)
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/usecase/pet.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "pet-dex-backend/v2/entity"
7 | "pet-dex-backend/v2/entity/dto"
8 | "pet-dex-backend/v2/infra/config"
9 | "pet-dex-backend/v2/interfaces"
10 | "pet-dex-backend/v2/pkg/uniqueEntityId"
11 |
12 | "github.com/google/uuid"
13 | )
14 |
15 | var loggerUpdate = config.GetLogger("update-usecase")
16 |
17 | type PetUseCase struct {
18 | repo interfaces.PetRepository
19 | }
20 |
21 | func NewPetUseCase(repo interfaces.PetRepository) *PetUseCase {
22 | return &PetUseCase{repo: repo}
23 | }
24 |
25 | func (c *PetUseCase) FindByID(ID uniqueEntityId.ID) (*entity.Pet, error) {
26 | pet, err := c.repo.FindByID(ID)
27 | if err != nil {
28 | err = fmt.Errorf("failed to retrieve pet: %w", err)
29 | return nil, err
30 | }
31 | return pet, nil
32 | }
33 |
34 | func (c *PetUseCase) Update(petID string, userID string, petUpdateDto dto.PetUpdateDto) (err error) {
35 | petToUpdate := entity.PetToEntity(&petUpdateDto)
36 |
37 | if !c.isValidPetSize(petToUpdate) {
38 | return errors.New("the animal size is invalid")
39 | }
40 |
41 | if !c.isValidSpecialCare(petToUpdate) {
42 | return errors.New("failed to update special care")
43 | }
44 |
45 | if !c.isValidWeight(petToUpdate) {
46 | return errors.New("the animal weight is invalid")
47 | }
48 |
49 | err = c.repo.Update(petID, userID, petToUpdate)
50 | if err != nil {
51 | loggerUpdate.Error("error updating pet", err)
52 | return fmt.Errorf("failed to update pet with ID %s: %w", petID, err)
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func (c *PetUseCase) isValidPetSize(petToUpdate *entity.Pet) bool {
59 | return (petToUpdate.Size == "small" || petToUpdate.Size == "medium" || petToUpdate.Size == "large" || petToUpdate.Size == "giant")
60 | }
61 |
62 | func (c *PetUseCase) isValidWeight(petToUpdate *entity.Pet) bool {
63 | return (petToUpdate.Weight > 0 &&
64 | (petToUpdate.WeightMeasure == "kg" || petToUpdate.WeightMeasure == "lb"))
65 | }
66 |
67 | func (c *PetUseCase) ListUserPets(userID uniqueEntityId.ID) ([]*entity.Pet, error) {
68 | pets, err := c.repo.ListByUser(userID)
69 | if err != nil {
70 | err = fmt.Errorf("failed to retrieve all user pets: %w", err)
71 | return nil, err
72 | }
73 | return pets, nil
74 | }
75 |
76 | func (c *PetUseCase) ListPetsByPage(page int, isUnauthorized bool) ([]*entity.Pet, error) {
77 | if isUnauthorized {
78 | return c.listPetsUnauthenticated()
79 | }
80 | return c.listPetsAuthenticated(page)
81 | }
82 |
83 | func (c *PetUseCase) listPetsAuthenticated(page int) ([]*entity.Pet, error) {
84 | pets, err := c.repo.ListAllByPage(page)
85 | if err != nil {
86 | err = fmt.Errorf("failed to retrieve pets page: %w", err)
87 | return nil, err
88 | }
89 | return pets, nil
90 | }
91 |
92 | func (c *PetUseCase) listPetsUnauthenticated() ([]*entity.Pet, error) {
93 | pets, err := c.repo.ListAllByPage(1)
94 | if len(pets) > 6 {
95 | pets = pets[:6]
96 | }
97 |
98 | for i, pet := range pets {
99 | tempPet := pet
100 | pet.ID = uuid.Nil
101 | pets[i] = tempPet
102 | }
103 |
104 | if err != nil {
105 | err = fmt.Errorf("failed to retrieve all user pets: %w", err)
106 | return nil, err
107 | }
108 | return pets, nil
109 | }
110 |
111 | func (c *PetUseCase) isValidSpecialCare(petToUpdate *entity.Pet) bool {
112 | var needed = petToUpdate.NeedSpecialCare.Needed
113 | var description = petToUpdate.NeedSpecialCare.Description
114 |
115 | if needed != nil {
116 | if *needed {
117 | return description != ""
118 | }
119 | if !*needed {
120 | return description == ""
121 | }
122 | }
123 | return true
124 | }
125 |
126 | func (c *PetUseCase) Save(petDto dto.PetInsertDto) error {
127 | pet := entity.NewPet(petDto.UserID, petDto.BreedID, petDto.Size, petDto.Name, petDto.Weight, petDto.AdoptionDate, petDto.Birthdate)
128 |
129 | err := c.repo.Save(pet)
130 | if err != nil {
131 | err = fmt.Errorf("failed to save pet: %w", err)
132 | return err
133 | }
134 | return nil
135 | }
136 |
--------------------------------------------------------------------------------
/usecase/user.go:
--------------------------------------------------------------------------------
1 | package usecase
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "pet-dex-backend/v2/entity"
7 | "pet-dex-backend/v2/entity/dto"
8 | "pet-dex-backend/v2/infra/config"
9 | "pet-dex-backend/v2/interfaces"
10 | "pet-dex-backend/v2/pkg/uniqueEntityId"
11 | "time"
12 |
13 | "github.com/golang-jwt/jwt"
14 | )
15 |
16 | type UserUsecase struct {
17 | repo interfaces.UserRepository
18 | hasher interfaces.Hasher
19 | encoder interfaces.Encoder
20 | logger config.Logger
21 | ssoProvider interfaces.SingleSignOnProvider
22 | }
23 |
24 | func NewUserUsecase(repo interfaces.UserRepository, hasher interfaces.Hasher, encoder interfaces.Encoder, ssoProvider interfaces.SingleSignOnProvider) *UserUsecase {
25 | return &UserUsecase{
26 | repo: repo,
27 | hasher: hasher,
28 | encoder: encoder,
29 | logger: *config.GetLogger("user-usecase"),
30 | ssoProvider: ssoProvider,
31 | }
32 | }
33 |
34 | func (uc *UserUsecase) Save(userDto dto.UserInsertDto) error {
35 | user := entity.NewUser(userDto)
36 |
37 | hashedPass, err := uc.hasher.Hash(user.Pass)
38 | if err != nil {
39 | uc.logger.Error("error hashing: ", err)
40 | return err
41 | }
42 |
43 | user.Pass = hashedPass
44 |
45 | err = uc.repo.Save(user)
46 | if err != nil {
47 | uc.logger.Error("error saving user: ", err)
48 | return err
49 | }
50 |
51 | err = uc.repo.SaveAddress(&user.Adresses)
52 | if err != nil {
53 | uc.logger.Error("error saving user adress: ", err)
54 | return err
55 | }
56 |
57 | return nil
58 | }
59 |
60 | func (uc *UserUsecase) Login(loginDto *dto.UserLoginDto) (string, error) {
61 | user, err := uc.FindByEmail(loginDto.Email)
62 | if err != nil {
63 | return "", errors.New("invalid credentials")
64 | }
65 |
66 | if user.Name == "" {
67 | return "", errors.New("invalid credentials")
68 | }
69 | if !uc.hasher.Compare(loginDto.Password, user.Pass) {
70 | return "", errors.New("invalid credentials")
71 | }
72 | token, _ := uc.encoder.NewAccessToken(interfaces.UserClaims{
73 | Id: user.ID.String(),
74 | Name: user.Name,
75 | Email: user.Email,
76 | Role: user.Role,
77 | StandardClaims: jwt.StandardClaims{
78 | ExpiresAt: time.Now().Add(time.Hour).Unix(),
79 | },
80 | })
81 | return token, nil
82 | }
83 |
84 | func (uc *UserUsecase) Update(userID uniqueEntityId.ID, userDto dto.UserUpdateDto) error {
85 | user := entity.UserToUpdate(userDto)
86 |
87 | err := uc.repo.Update(userID, *user)
88 | if err != nil {
89 | uc.logger.Error("error updating user: ", err)
90 | return err
91 | }
92 |
93 | return nil
94 | }
95 |
96 | func (uc *UserUsecase) FindByEmail(email string) (*entity.User, error) {
97 | user, err := uc.repo.FindByEmail(email)
98 |
99 | if err != nil {
100 | uc.logger.Error("error finding user by email:", err)
101 | return nil, err
102 | }
103 |
104 | return user, nil
105 | }
106 |
107 | func (uc *UserUsecase) FindByID(ID uniqueEntityId.ID) (*entity.User, error) {
108 | user, err := uc.repo.FindByID(ID)
109 |
110 | if err != nil {
111 | uc.logger.Error("error finding user by id:", err)
112 | return nil, err
113 | }
114 |
115 | address, err := uc.repo.FindAddressByUserID(user.ID)
116 |
117 | if err != nil {
118 | uc.logger.Error("error finding user address:", err)
119 | return nil, err
120 | }
121 |
122 | user.Adresses = *address
123 |
124 | return user, nil
125 | }
126 |
127 | func (uc *UserUsecase) Delete(userID uniqueEntityId.ID) error {
128 | err := uc.repo.Delete(userID)
129 |
130 | if err != nil {
131 | uc.logger.Error(fmt.Errorf("#UserUsecase.Delete error: %w", err))
132 | return err
133 | }
134 |
135 | return nil
136 | }
137 |
138 | func (uc *UserUsecase) ChangePassword(userChangePasswordDto dto.UserChangePasswordDto, userId uniqueEntityId.ID) error {
139 | user, err := uc.repo.FindByID(userId)
140 | if err != nil {
141 | uc.logger.Error("error finding user by id: ", err)
142 | return errors.New("") // Don't show the user the reason for secure reasons
143 | }
144 |
145 | if !uc.hasher.Compare(userChangePasswordDto.OldPassword, user.Pass) {
146 | uc.logger.Error("old password does not match")
147 | return errors.New("old password does not match")
148 | }
149 |
150 | newPassword, err := uc.hasher.Hash(userChangePasswordDto.NewPassword)
151 | if err != nil {
152 | uc.logger.Error("error hashing: ", err)
153 | return err
154 | }
155 | err = uc.repo.ChangePassword(userId, newPassword)
156 | if err != nil {
157 | uc.logger.Error(err)
158 | return err
159 | }
160 | return nil
161 | }
162 |
163 | func (uc *UserUsecase) UpdatePushNotificationSettings(userID uniqueEntityId.ID, userPushNotificationEnabled dto.UserPushNotificationEnabled) error {
164 | user, err := uc.repo.FindByID(userID)
165 |
166 | if err != nil {
167 | uc.logger.Error("error finding user by id: ", err)
168 | return errors.New("user dont exists")
169 | }
170 |
171 | user.PushNotificationsEnabled = &userPushNotificationEnabled.PushNotificationEnabled
172 |
173 | err = uc.repo.Update(userID, *user)
174 |
175 | if err != nil {
176 | uc.logger.Error("error updating user by id: ", err)
177 | return errors.New("error on updating push notification")
178 | }
179 |
180 | return nil
181 |
182 | }
183 |
184 | func (uc *UserUsecase) ProviderLogin(accessToken string, provider string) (*entity.User, bool, error) {
185 | userInfo, err := uc.ssoProvider.GetUserDetails(provider, accessToken)
186 | if err != nil {
187 | return nil, false, err
188 | }
189 |
190 | user, _ := uc.FindByEmail(userInfo.Email)
191 |
192 | return user, user == nil, nil
193 |
194 | }
195 |
196 | func (uc *UserUsecase) NewAccessToken(id string, name string, email string) (string, error) {
197 | return uc.encoder.NewAccessToken(interfaces.UserClaims{
198 | Id: id,
199 | Name: name,
200 | Email: email,
201 | StandardClaims: jwt.StandardClaims{
202 | ExpiresAt: time.Now().Add(time.Hour).Unix(),
203 | },
204 | })
205 | }
206 |
--------------------------------------------------------------------------------