├── .air.toml ├── .dockerignore ├── .env.example ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── feature-request.md │ └── issue_template.md ├── pull_request_template.md └── workflows │ ├── CD.yml │ ├── base-workflows.yml │ ├── createBranchAssign.yml │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── prepare-commit-msg ├── .mockery.yaml ├── .tool-versions ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── a.out ├── api ├── controllers │ ├── breed.go │ ├── ong.go │ ├── pet.go │ └── user.go ├── errors │ └── errors.go ├── main.go ├── middlewares │ ├── auth.go │ └── cors.go └── routes │ ├── router.go │ └── routes.go ├── cmd └── main.go ├── docker-compose.yml ├── docs ├── database │ └── migration.md └── infra │ └── infra.md ├── entity ├── address.go ├── breed.go ├── dto │ ├── address_insert_dto.go │ ├── breed_list.go │ ├── link_dto.go │ ├── ong_delete_dto.go │ ├── ong_insert_dto.go │ ├── ong_list_dto.go │ ├── ong_update_dto.go │ ├── pet_insert_dto.go │ ├── pet_update.dto.go │ ├── user_change_password.dto.go │ ├── user_change_password.dto_test.go │ ├── user_insert.dto.go │ ├── user_login.dto.go │ ├── user_push_notification_enabled.dto.go │ ├── user_sso.dto.go │ └── user_update.dto.go ├── ong.go ├── pet.go └── user.go ├── go.mod ├── go.sum ├── infra ├── config │ ├── config.go │ ├── env.go │ └── logger.go ├── db │ ├── breed_repository.go │ ├── ong_repository.go │ ├── pet_repository.go │ └── user_repository.go └── mail_repository.go ├── integration-tests └── .keep ├── interfaces ├── address_repository.go ├── breed_repository.go ├── encoder.go ├── hasher.go ├── mail_repository.go ├── ong_repository.go ├── pet_repository.go ├── sso.go └── user_repository.go ├── main ├── migrations ├── 20240508222543_init_db.down.sql ├── 20240508222543_init_db.up.sql ├── 20240508222652_add_needed_care.down.sql ├── 20240508222652_add_needed_care.up.sql ├── 20240610154621_add_push_notification_settings.down.sql ├── 20240610154621_add_push_notification_settings.up.sql ├── 20240613185301_add_user_roles.down.sql ├── 20240613185301_add_user_roles.up.sql ├── 20240722165154_add_deletedAt_legal_persons.down.sql └── 20240722165154_add_deletedAt_legal_persons.up.sql ├── mocks └── pet-dex-backend │ └── v2 │ └── interfaces │ ├── mock_AdressRepo.go │ ├── mock_BreedRepository.go │ ├── mock_Hasher.go │ ├── mock_OngRepository.go │ ├── mock_PetRepository.go │ ├── mock_SingleSignOnGateway.go │ ├── mock_SingleSignOnProvider.go │ └── mock_UserRepository.go ├── pkg ├── encoder │ ├── encoder.go │ └── encoder_test.go ├── hasher │ ├── hasher.go │ └── hasher_test.go ├── mail │ ├── README.md │ ├── config.go │ ├── mail.go │ ├── mail_test.go │ ├── message.go │ └── validate.go ├── migration │ └── migration.go ├── sso │ ├── facebook.go │ ├── google.go │ ├── provider.go │ └── provider_test.go ├── uniqueEntityId │ ├── id.go │ └── id_test.go └── utils │ ├── user.go │ └── user_test.go ├── swagger ├── details.md ├── docs.go ├── swagger.json └── swagger.yaml └── usecase ├── adopt.go ├── adopt_test.go ├── breed.go ├── breed_test.go ├── ong.go ├── ong_test.go ├── pet.go ├── pet_test.go ├── user.go └── user_test.go /.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | tmp_dir = "tmp" 3 | 4 | [build] 5 | cmd = "go build -o ./tmp/main ./api/main.go" 6 | bin = "./tmp/main" 7 | exclude_dir = ["tmp", ".husky", ".github"] 8 | exclude_regex = ["_test\\.go"] 9 | exclude_unchanged = true 10 | include_ext = ["go", "tpl", "tmpl", "html"] 11 | log = "build-errors.log" 12 | stop_on_error = false 13 | 14 | [color] 15 | build = "yellow" 16 | main = "magenta" 17 | runner = "green" 18 | watcher = "cyan" 19 | 20 | [log] 21 | time = true 22 | main_only = false 23 | 24 | [misc] 25 | clean_on_exit = true 26 | 27 | [screen] 28 | clear_on_rebuild = false 29 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .git/ 3 | .husky/ 4 | data/ 5 | data-teste/ 6 | *.yaml 7 | *.yml 8 | *.md 9 | .gitignore 10 | Makefile 11 | Dockerfile -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_PORT="3000" 2 | ENVIRONMENT="DEVELOPMENT" 3 | MIGRATIONS_PATH="migrations" 4 | 5 | DB_USER="maria" 6 | DB_PASSWORD="123" 7 | DB_ROOT_PASSWORD="123" 8 | DB_DATABASE="petdex" 9 | DB_HOST="db" 10 | DB_PORT="3306" 11 | 12 | MIGRATION_HOST="localhost" 13 | 14 | REDIS_HOST="pet-dex-cache" 15 | REDIS_PORT="6379" 16 | REDIS_PASSWORD="123" 17 | 18 | JWT_SECRET=MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHQ5BWxB9NlRB89pOVY320IISVsSCOLKtGy0nVybrHNdzIhQW64XN8af66SvAN4CG9ZuzH73iGOFUXoMTwy1bDKsoxrRgybEoHA8wZxw0yOItYoQ8HY6fzLJTEmHtMTPLKCqhZe70vEBK/N69dJhZWL0MH4UeQwLgibIrCoPQuhbAgMBAAE 19 | 20 | # Get at https://console.cloud.google.com/apis/credentials follow https://developers.google.com/identity/sign-in/web/sign-in instructions 21 | GOOGLE_OAUTH_CLIENT_ID= 22 | GOOGLE_OAUTH_CLIENT_SECRET= 23 | # The redirect URL should be in the Authorized redirect URIs at https://console.cloud.google.com/apis/credentials and this URL should be the login page that will call the fetch to google login 24 | GOOGLE_REDIRECT_URL= 25 | 26 | # Get at https://developers.facebook.com/ creating an app. 27 | FACEBOOK_APP_ID= 28 | FACEBOOK_APP_SECRET= 29 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @devhatt/hatts @devhatt/petdex-backend-administrators @devhatt/revisores 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a new bug report to help us improve Petdex 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A short description of what the bug is. 11 | 12 |
13 | 14 | Description 15 | 16 | [Describe the bug reported] 17 |
18 | 19 |
20 | 21 |
22 | 23 | Steps to Reproduce 24 | 25 | 26 | [If applicable, provide detailed steps to reproduce the bug.] 27 | 28 |
29 | 30 |
31 | 32 |
33 | 34 | Expected Behavior 35 | 36 | [Describe what is expected to happen.] 37 |
38 | 39 |
40 | 41 |
42 | 43 | Current Behavior 44 | 45 | [Describe what is currently happening.] 46 |
47 | 48 |
49 | 50 |
51 | 52 | Visual information 53 | 54 | [If possible, add screenshots to illustrate this bug.] 55 |
56 | 57 |
58 | 59 |
60 | 61 | Additional Information 62 | 63 | [Provide any additional information, such as relevant versions, browser, OS, context, etc.] 64 | 65 |
66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature to be added to Petdex 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the feature** 10 | A clear and short description of the feature. 11 | 12 |
13 | 14 | Description 15 | 16 | [Describe the new feature requested.] 17 |
18 | 19 |
20 | 21 |
22 | 23 | Use Case 24 | 25 | 26 | [Explain the use for this feature and how it might benefits the project.] 27 |
28 | 29 |
30 | 31 |
32 | 33 | Implementation Details 34 | 35 | 36 | [Provide any details or suggestions on how this feature could be implemented.] 37 | 38 |
39 | 40 |
41 | 42 |
43 | 44 | Visual Concepts 45 | 46 | 47 | [Include any visual representations or concepts if those are available and applicable.] 48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 | Additional Information 56 | 57 | 58 | [Provide any additional information, such as context that might be relevant to the implementation of this specific feature.] 59 |
60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a new bug report to help us improve Petdex 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A short description of what the bug is. 11 | 12 |
13 | 14 | Description 15 | 16 | [Describe the bug reported] 17 |
18 | 19 |
20 | 21 |
22 | 23 | Steps to Reproduce 24 | 25 | 26 | [If applicable, provide detailed steps to reproduce the bug.] 27 | 28 |
29 | 30 |
31 | 32 |
33 | 34 | Expected Behavior 35 | 36 | [Describe what is expected to happen.] 37 |
38 | 39 |
40 | 41 |
42 | 43 | Current Behavior 44 | 45 | [Describe what is currently happening.] 46 |
47 | 48 |
49 | 50 |
51 | 52 | Visual information 53 | 54 | [If possible, add screenshots to illustrate this bug.] 55 |
56 | 57 |
58 | 59 |
60 | 61 | Additional Information 62 | 63 | [Provide any additional information, such as relevant versions, browser, OS, context, etc.] 64 | 65 |
66 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes 2 | 3 |
4 | 5 | Feature 6 | 7 | 8 | N/A 9 |
10 | 11 |
12 | 13 | Bugfix 14 | 15 | 16 | - **Description** 17 | N/A 18 | 19 | - **Cause** 20 | N/A 21 | 22 | - **Solution** 23 | N/A 24 |
25 | 26 |
27 | 28 | Changelog 29 | 30 | N/A 31 |
32 | 33 |
34 | 35 | Visual evidences :framed_picture: 36 | 37 | 38 |
39 | 40 |
41 | 42 | Checklist 43 | 44 | 45 | - [ ] Issue linked 46 | - [ ] Build working correctly 47 | - [ ] Tests created 48 |
49 | 50 |
51 | 52 | Additional info 53 | 54 | N/A 55 |
56 | -------------------------------------------------------------------------------- /.github/workflows/CD.yml: -------------------------------------------------------------------------------- 1 | name: Go CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_run: 8 | workflows: ["Go CI"] 9 | types: 10 | - completed 11 | 12 | jobs: 13 | push-image: 14 | runs-on: ubuntu-latest 15 | if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Log into Docker Hub 20 | uses: docker/login-action@v3 21 | with: 22 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 23 | password: ${{ secrets.DOCKER_HUB_PASSWORD }} 24 | 25 | - name: Build and push Docker image 26 | run: | 27 | SHA=${{ github.sha }} 28 | docker build -t devhatt/pet-dex-backend:$SHA . 29 | docker push devhatt/pet-dex-backend:$SHA 30 | docker tag devhatt/pet-dex-backend:$SHA devhatt/pet-dex-backend:latest 31 | docker push devhatt/pet-dex-backend:latest 32 | -------------------------------------------------------------------------------- /.github/workflows/base-workflows.yml: -------------------------------------------------------------------------------- 1 | name: Base Actions 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request: 7 | 8 | jobs: 9 | assignes: 10 | uses: devhatt/workflows/.github/workflows/auto-assign.yml@main 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /.github/workflows/createBranchAssign.yml: -------------------------------------------------------------------------------- 1 | name: Create Branch on Assignment 2 | 3 | on: 4 | issues: 5 | types: 6 | - assigned 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | create-branch: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Git 20 | run: | 21 | git config user.name "${{ github.actor }}" 22 | git config user.email "${{ github.actor }}@users.noreply.github.com" 23 | 24 | - name: Create Branch 25 | run: | 26 | ISSUE_NUMBER=$(echo ${{ github.event.issue.number }}) 27 | BRANCH_NAME="issue-${ISSUE_NUMBER}" 28 | git checkout -b $BRANCH_NAME 29 | git push origin $BRANCH_NAME 30 | 31 | - name: Notify Success 32 | run: echo "Branch created successfully!" 33 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Go CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | concurrency: ${{ github.workflow }}-${{ github.ref }} 12 | 13 | jobs: 14 | golangci-lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup deps 20 | uses: devhatt/workflows/.github/actions/go-setup@main 21 | 22 | - name: run go lint 23 | uses: golangci/golangci-lint-action@v6 24 | 25 | test: 26 | runs-on: ubuntu-latest 27 | needs: 28 | - golangci-lint 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - name: Setup deps 33 | uses: devhatt/workflows/.github/actions/go-setup@main 34 | 35 | - name: Test 36 | run: go test ./... 37 | 38 | build: 39 | runs-on: ubuntu-latest 40 | needs: 41 | - test 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - name: Setup deps 46 | uses: devhatt/workflows/.github/actions/go-setup@main 47 | 48 | - name: Run Docker Compose Prod 49 | run: | 50 | docker compose --profile integration-tests up -d 51 | 52 | - name: Up migrations 53 | run: | 54 | go run cmd/main.go -up 55 | 56 | - name: Check Docker Containers 57 | run: docker compose ps --services | xargs -I {} sh -c 'docker compose ps {} | grep "Up" || exit 1' 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | data 10 | data-teste 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | 133 | #Environment config 134 | .vscode/* 135 | .idea 136 | 137 | # temporary dir 138 | tmp 139 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx cz --hook || true 5 | -------------------------------------------------------------------------------- /.mockery.yaml: -------------------------------------------------------------------------------- 1 | with-expecter: true 2 | packages: 3 | pet-dex-backend/v2/interfaces: 4 | all: true 5 | interfaces: 6 | Hasher: 7 | PetRepository: 8 | UserRepository: 9 | OngRepository: 10 | SingleSignOnProvider: 11 | SingleSignOnGateway: 12 | BreedRepository: 13 | AdressRepo: 14 | SingleSignOnProvider: 15 | SingleSignOnGateway: 16 | BreedRepository: 17 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.21.5 2 | 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 1. Diretrizes de Contribuição para o Repositório da PetDex 2 | 3 | Bem-vindo à PetDex! Agradecemos pelo seu interesse em contribuir para este projeto open source. Suas contribuições são fundamentais para o sucesso e a melhoria contínua deste projeto. Antes de começar, por favor, leia e siga estas diretrizes para garantir um processo de contribuição harmonioso e eficaz. 4 | 5 | ## Como Contribuir 6 | 7 | 1. **Forquilhando o Repositório:** Faça um fork deste repositório para sua própria conta GitHub clicando no botão "Fork". 8 | 2. **Clonando o Repositório:** Clone o fork do repositório para a sua máquina local: 9 | 10 | ```bash 11 | git clone 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 | [![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white)](https://go.dev/) 12 | [![Swagger](https://img.shields.io/badge/Swagger-OpenAPI-85EA2D.svg)](https://swagger.io/) 13 | [![MariaDB](https://img.shields.io/badge/MariaDB-003545?style=for-the-badge&logo=mariadb&logoColor=white)](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 | --------------------------------------------------------------------------------