diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8b126c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.idea/ +.git/ +.github/ +publish/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8d2a1fc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +dotnet_diagnostic.CS8618.severity = none diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index b289cea..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: SonarCloud -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] -jobs: - build: - name: Build and analyze - runs-on: windows-latest - steps: - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: 'zulu' - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Cache SonarCloud packages - uses: actions/cache@v3 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v3 - with: - path: .\.sonar\scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell - run: | - New-Item -Path .\.sonar\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - - name: Install dotnet-coverage - shell: powershell - run: dotnet tool install --global dotnet-coverage - - name: Clone trx2sonar - uses: actions/checkout@v3 - with: - repository: gmarokov/dotnet-trx2sonar - path: dotnet-trx2sonar - - name: Setup trx2sonar - shell: powershell - run: | - dotnet restore dotnet-trx2sonar - dotnet build dotnet-trx2sonar --configuration Release - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell - run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"fga-eps-mds_2023.1-Dnit-Back" /o:"fga-eps-mds-1" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.testExecutionReportPaths=results.xml - dotnet build - dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml" - dotnet test --logger "trx;LogFileName=results.trx" --results-directory ./TestResults/results.xml - ./dotnet-trx2sonar/TrxToSonar/bin/Release/net6.0/TrxToSonar -d ./TestResults -o results.xml - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fa1701..f23bacc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,55 +1,79 @@ -name: Deploy AWS - -on: - workflow_dispatch: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '6.0.x' - - - name: Install dependencies - run: dotnet restore - - - name: Build - run: dotnet build --configuration Release --no-restore - - - name: Test - run: dotnet test --no-restore --verbosity normal - - - name: Publish - run: dotnet publish -c Release -o '${{ github.workspace }}/out' - - - name: Create email service .env - run: | - echo 'EMAIL_SERVICE_ADDRESS=${{ secrets.EMAIL_SERVICE_ADDRESS }}' > '${{ github.workspace }}/out/.env' - echo 'EMAIL_SERVICE_PASSWORD=${{ secrets.EMAIL_SERVICE_PASSWORD }}' >> '${{ github.workspace }}/out/.env' - - - name: Zip Package - run: | - cd ${{ github.workspace }}/out - zip -r ${{ github.workspace }}/out.zip * .env - - - name: Deploy to EB - uses: einaregilsson/beanstalk-deploy@v21 - with: - aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - application_name: back-dnit-usuario - environment_name: back-dnit-usuario-env - region: us-east-1 - version_label: ${{ github.run_id }} - version_description: ${{ github.sha }} - deployment_package: ${{ github.workspace }}/out.zip +name: CI +on: + push: + branches: + - main + - develop + pull_request: + types: [opened, synchronize, reopened] +jobs: + ci-windows: + name: Build, test and analyze Windows + runs-on: windows-latest + steps: + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarCloud scanner + id: cache-sonar-scanner + uses: actions/cache@v3 + with: + path: .\.sonar\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarCloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path .\.sonar\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + - name: Install dotnet-coverage + shell: powershell + run: dotnet tool install --global dotnet-coverage + - name: Clone trx2sonar + uses: actions/checkout@v3 + with: + repository: gmarokov/dotnet-trx2sonar + path: dotnet-trx2sonar + - name: Setup trx2sonar + shell: powershell + run: | + dotnet restore dotnet-trx2sonar + dotnet build dotnet-trx2sonar --configuration Release + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: powershell + run: | + .\.sonar\scanner\dotnet-sonarscanner begin /k:"fga-eps-mds_2023.2-Dnit-UsuarioService" /o:"fga-eps-mds-1" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.testExecutionReportPaths=results.xml + dotnet build + dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml" + dotnet test --logger "trx;LogFileName=results.trx" --results-directory ./TestResults/results.xml + ./dotnet-trx2sonar/TrxToSonar/bin/Release/net6.0/TrxToSonar -d ./TestResults -o results.xml + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + ci-linux: + name: Build and test Linux + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 337ed29..0a43e84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,14 +12,16 @@ jobs: steps: - name: Get file name id: name - run: echo "::set-output name=file_name::fga-eps-mds-2023.1-Dnit-UsuarioService-$(TZ='America/Sao_Paulo' date +'%m-%d-%Y-%H-%M-%S')-${{github.ref_name}}" + run: echo "::set-output name=file_name::fga-eps-mds_2023.2-Dnit-UsuarioService-$(TZ='America/Sao_Paulo' date +'%m-%d-%Y-%H-%M-%S')-${{github.ref_name}}" - - name: Copy repository + - name: Copy repository and download metrics uses: actions/checkout@v2 - run: wget $METRICS_URL -O ${{ steps.name.outputs.file_name }}.json env: METRICS_URL: ${{ secrets.METRICS_URL }} - - uses: actions/upload-artifact@v2 + + - name: Uploads file + uses: actions/upload-artifact@v2 with: name: ${{ steps.name.outputs.file_name }}.json path: ${{ steps.name.outputs.file_name }}.json @@ -27,10 +29,10 @@ jobs: - name: Send metrics to doc repo uses: dmnemec/copy_file_to_another_repo_action@v1.1.1 env: - API_TOKEN_GITHUB: ${{ secrets.TOKEN_GITHUB }} + API_TOKEN_GITHUB: ${{ secrets.GIT_TOKEN }} with: source_file: ${{ steps.name.outputs.file_name }}.json - destination_repo: 'fga-eps-mds/2023.1-Dnit-DOC' + destination_repo: 'fga-eps-mds/2023.2-Dnit-DOC' destination_folder: 'analytics-raw-data' user_email: ${{ secrets.GIT_EMAIL}} user_name: ${{ secrets.GIT_USER }} diff --git a/.gitignore b/.gitignore index c80e612..5aaf4df 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ bld/ [Ll]og/ [Ll]ogs/ +.vscode + # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot @@ -355,10 +357,18 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ - + # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd /dominio/UsuarioDNIT.cs + +.idea/ + +cov.sh +coveragereport/ + +report/ +.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a8e7ab1..bdcf6bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,29 +2,6 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /app -COPY UsuarioService.sln ./ -COPY app/app.csproj ./app/ -COPY dominio/dominio.csproj ./dominio/ -COPY repositorio/repositorio.csproj ./repositorio/ -COPY service/service.csproj ./service/ -COPY test/test.csproj ./test/ +COPY . . -RUN dotnet restore - -COPY . ./ - -RUN dotnet build -c Release - -RUN dotnet publish app/app.csproj -c Release -o /app/out -RUN dotnet publish service/service.csproj -c Release -o /app/out -RUN dotnet publish repositorio/repositorio.csproj -c Release -o /app/out -RUN dotnet publish dominio/dominio.csproj -c Release -o /app/out -RUN dotnet publish test/test.csproj -c Release -o /app/out - -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime - -WORKDIR /app - -COPY --from=build /app/out . - -ENTRYPOINT ["dotnet", "app.dll"] +CMD dotnet watch --project app diff --git a/UsuarioService.sln b/UsuarioService.sln index cb4ada2..fa57c45 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -3,15 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "app", "app\app.csproj", "{A553D130-F52F-4C65-9EFA-DE58FAC021FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api", "api\api.csproj", "{9B9F5E33-2CDA-4F75-8338-801798AD293E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dominio", "dominio\dominio.csproj", "{91F2F3EB-3054-4187-8E04-0E04EC55ED08}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "app", "app\app.csproj", "{08942609-AAE1-4D23-B71D-E39D1A7CF566}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "service", "service\service.csproj", "{7F390C74-6B61-4479-8840-E57D6B9E56C5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test", "test\test.csproj", "{1D4F20EE-9463-4E06-805F-F4CADF503164}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "repositorio", "repositorio\repositorio.csproj", "{456103BA-9130-4604-8AF7-49632FBAB6BB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8B004C85-9684-409C-B8C8-01BD3A8F6DFA}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{4D6B0EB8-A866-4742-AF0F-30706B1D282F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "auth", "auth\auth.csproj", "{1C2F7ED1-31B7-4274-BD2A-388700259656}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,26 +22,22 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A553D130-F52F-4C65-9EFA-DE58FAC021FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A553D130-F52F-4C65-9EFA-DE58FAC021FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A553D130-F52F-4C65-9EFA-DE58FAC021FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A553D130-F52F-4C65-9EFA-DE58FAC021FA}.Release|Any CPU.Build.0 = Release|Any CPU - {91F2F3EB-3054-4187-8E04-0E04EC55ED08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91F2F3EB-3054-4187-8E04-0E04EC55ED08}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91F2F3EB-3054-4187-8E04-0E04EC55ED08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91F2F3EB-3054-4187-8E04-0E04EC55ED08}.Release|Any CPU.Build.0 = Release|Any CPU - {7F390C74-6B61-4479-8840-E57D6B9E56C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F390C74-6B61-4479-8840-E57D6B9E56C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F390C74-6B61-4479-8840-E57D6B9E56C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F390C74-6B61-4479-8840-E57D6B9E56C5}.Release|Any CPU.Build.0 = Release|Any CPU - {456103BA-9130-4604-8AF7-49632FBAB6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {456103BA-9130-4604-8AF7-49632FBAB6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {456103BA-9130-4604-8AF7-49632FBAB6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {456103BA-9130-4604-8AF7-49632FBAB6BB}.Release|Any CPU.Build.0 = Release|Any CPU - {4D6B0EB8-A866-4742-AF0F-30706B1D282F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D6B0EB8-A866-4742-AF0F-30706B1D282F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D6B0EB8-A866-4742-AF0F-30706B1D282F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D6B0EB8-A866-4742-AF0F-30706B1D282F}.Release|Any CPU.Build.0 = Release|Any CPU + {9B9F5E33-2CDA-4F75-8338-801798AD293E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B9F5E33-2CDA-4F75-8338-801798AD293E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B9F5E33-2CDA-4F75-8338-801798AD293E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B9F5E33-2CDA-4F75-8338-801798AD293E}.Release|Any CPU.Build.0 = Release|Any CPU + {08942609-AAE1-4D23-B71D-E39D1A7CF566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08942609-AAE1-4D23-B71D-E39D1A7CF566}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08942609-AAE1-4D23-B71D-E39D1A7CF566}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08942609-AAE1-4D23-B71D-E39D1A7CF566}.Release|Any CPU.Build.0 = Release|Any CPU + {1D4F20EE-9463-4E06-805F-F4CADF503164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D4F20EE-9463-4E06-805F-F4CADF503164}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D4F20EE-9463-4E06-805F-F4CADF503164}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D4F20EE-9463-4E06-805F-F4CADF503164}.Release|Any CPU.Build.0 = Release|Any CPU + {1C2F7ED1-31B7-4274-BD2A-388700259656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C2F7ED1-31B7-4274-BD2A-388700259656}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C2F7ED1-31B7-4274-BD2A-388700259656}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C2F7ED1-31B7-4274-BD2A-388700259656}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/api/Enums.cs b/api/Enums.cs new file mode 100644 index 0000000..9eb2084 --- /dev/null +++ b/api/Enums.cs @@ -0,0 +1,132 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; + +namespace api +{ + public enum UF + { + [Description("Acre")] + AC = 1, + [Description("Alagoas")] + AL, + [Description("Amapá")] + AP, + [Description("Amazonas")] + AM, + [Description("Bahia")] + BA, + [Description("Ceará")] + CE, + [Description("Espírito Santo")] + ES, + [Description("Goiás")] + GO, + [Description("Maranhão")] + MA, + [Description("Mato Grosso")] + MT, + [Description("Mato Grosso do Sul")] + MS, + [Description("Minas Gerais")] + MG, + [Description("Pará")] + PA, + [Description("Paraíba")] + PB, + [Description("Paraná")] + PR, + [Description("Pernambuco")] + PE, + [Description("Piauí")] + PI, + [Description("Rio de Janeiro")] + RJ, + [Description("Rio Grande do Norte")] + RN, + [Description("Rio Grande do Sul")] + RS, + [Description("Rondônia")] + RO, + [Description("Roraima")] + RR, + [Description("Santa Catarina")] + SC, + [Description("São Paulo")] + SP, + [Description("Sergipe")] + SE, + [Description("Tocantins")] + TO, + [Description("Distrito Federal")] + DF + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum Permissao + { + [Description("Cadastrar Escola")] + EscolaCadastrar = 1000, + [Description("Editar Escola")] + EscolaEditar = 1001, + [Description("Remover Escola")] + EscolaRemover = 1002, + [Description("Visualizar Escola")] + EscolaVisualizar = 1003, + + //[Description("Cadastrar Empresa")] + //EmpresaCadastrar = 2000, + //[Description("Editar Empresa")] + //EmpresaEditar = 2001, + //[Description("Remover Empresa")] + //EmpresaRemover = 2002, + + [Description("Cadastrar Perfil de Usuário")] + PerfilCadastrar = 3000, + [Description("Editar Perfil de Usuário")] + PerfilEditar = 3001, + [Description("Remover Perfil de Usuário")] + PerfilRemover = 3002, + [Description("Visualizar perfis")] + PerfilVisualizar = 3003, + + [Description("Calcular UPS de sinistros")] + UpsCalcularSinistro = 5000, + [Description("Calcular UPS de escolas")] + UpsCalcularEscola = 5001, + [Description("Visualizar UPS")] + UpsVisualizar = 5002, + + [Description("Cadastrar rodovia")] + RodoviaCadastrar = 6000, + + [Description("Cadastrar sinistro")] + SinistroCadastrar = 7000, + + [Description("Visualizar Usuário")] + UsuarioVisualizar = 8003, + [Description("Editar Perfil Usuário")] + UsuarioPerfilEditar = 8004, + } + + public enum ErrorCodes + { + Unknown, + [Description("Usuário não possui permissão para realizar ação")] + NaoPermitido, + [Description("Usuário não encontrado")] + UsuarioNaoEncontrado, + [Description("Código UF inválido")] + CodigoUfInvalido, + [Description("Permissao não encontrada")] + PermissaoNaoEncontrada, + } + + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum TipoPerfil + { + Basico = 1, + Administrador, + Customizavel + } +} diff --git a/api/ListaPaginada.cs b/api/ListaPaginada.cs new file mode 100644 index 0000000..1d4dc01 --- /dev/null +++ b/api/ListaPaginada.cs @@ -0,0 +1,20 @@ +namespace api +{ + public class ListaPaginada + { + public int Pagina { get; set; } + public int ItemsPorPagina { get; set; } + public int Total { get; set; } + public int TotalPaginas { get; set; } + public List Items { get; set; } + + public ListaPaginada(List items, int paginaIndex, int itemsPorPagina, int total) + { + Pagina = paginaIndex; + ItemsPorPagina = itemsPorPagina; + Total = total; + TotalPaginas = (int)Math.Ceiling(Total / (double)itemsPorPagina); + Items = items; + } + } +} \ No newline at end of file diff --git a/api/Municipios/MunicipioModel.cs b/api/Municipios/MunicipioModel.cs new file mode 100644 index 0000000..3ed8c33 --- /dev/null +++ b/api/Municipios/MunicipioModel.cs @@ -0,0 +1,8 @@ +namespace api.Municipios +{ + public class MunicipioModel + { + public string Nome { get; set; } + public int Id { get; set; } + } +} diff --git a/api/Perfis/PerfilDTO.cs b/api/Perfis/PerfilDTO.cs new file mode 100644 index 0000000..f92ed0d --- /dev/null +++ b/api/Perfis/PerfilDTO.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace api.Perfis +{ + public class PerfilDTO + { + [MinLength(1)] + public string Nome { get; set; } + public List Permissoes { get; set; } + } +} \ No newline at end of file diff --git a/api/Perfis/PerfilModel.cs b/api/Perfis/PerfilModel.cs new file mode 100644 index 0000000..0eb028f --- /dev/null +++ b/api/Perfis/PerfilModel.cs @@ -0,0 +1,14 @@ +using api.Permissoes; + +namespace api.Perfis +{ + public class PerfilModel + { + public Guid Id { get; set; } + public string Nome { get; set; } + public int QuantidadeUsuarios { get; set; } + public TipoPerfil Tipo { get; set; } + public List Permissoes { get; set; } + public List? CategoriasPermissao { get; set; } + } +} diff --git a/api/Permissoes/CategoriaPermissaoModel.cs b/api/Permissoes/CategoriaPermissaoModel.cs new file mode 100644 index 0000000..f777c86 --- /dev/null +++ b/api/Permissoes/CategoriaPermissaoModel.cs @@ -0,0 +1,10 @@ +using api.Permissoes; + +namespace api +{ + public class CategoriaPermissaoModel + { + public string Categoria { get; set; } + public List Permissoes { get; set; } + } +} \ No newline at end of file diff --git a/api/Permissoes/PermissaoModel.cs b/api/Permissoes/PermissaoModel.cs new file mode 100644 index 0000000..c4ce866 --- /dev/null +++ b/api/Permissoes/PermissaoModel.cs @@ -0,0 +1,8 @@ +namespace api.Permissoes +{ + public class PermissaoModel + { + public Permissao Codigo { get; set; } + public string Descricao { get; set; } + } +} \ No newline at end of file diff --git a/api/Senhas/RedefinicaoSenhaDTO.cs b/api/Senhas/RedefinicaoSenhaDTO.cs new file mode 100644 index 0000000..67f2a69 --- /dev/null +++ b/api/Senhas/RedefinicaoSenhaDTO.cs @@ -0,0 +1,8 @@ +namespace api.Senhas +{ + public class RedefinicaoSenhaDTO + { + public string Senha { get; set; } + public string UuidAutenticacao { get; set; } + } +} \ No newline at end of file diff --git a/api/Senhas/RedefinicaoSenhaModel.cs b/api/Senhas/RedefinicaoSenhaModel.cs new file mode 100644 index 0000000..60f8162 --- /dev/null +++ b/api/Senhas/RedefinicaoSenhaModel.cs @@ -0,0 +1,8 @@ +namespace api.Senhas +{ + public class RedefinicaoSenhaModel + { + public string Senha { get; set; } + public string UuidAutenticacao { get; set; } + } +} diff --git a/api/UfModel.cs b/api/UfModel.cs new file mode 100644 index 0000000..d0be320 --- /dev/null +++ b/api/UfModel.cs @@ -0,0 +1,9 @@ +namespace api +{ + public class UfModel + { + public string Nome { get; set; } + public int Id { get; set; } + public string Sigla { get; set; } + } +} \ No newline at end of file diff --git a/api/Usuarios/AtualizarTokenData.cs b/api/Usuarios/AtualizarTokenData.cs new file mode 100644 index 0000000..8061bfe --- /dev/null +++ b/api/Usuarios/AtualizarTokenData.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace api.Usuarios +{ + public class AtualizarTokenDto + { + [Required] + public string Token { get; set; } + [Required] + public string TokenAtualizacao { get; set; } + } +} diff --git a/api/Usuarios/EditarPerfilUsuarioDTO.cs b/api/Usuarios/EditarPerfilUsuarioDTO.cs new file mode 100644 index 0000000..d9c73d9 --- /dev/null +++ b/api/Usuarios/EditarPerfilUsuarioDTO.cs @@ -0,0 +1,8 @@ +namespace api.Usuarios +{ + public class EditarPerfilUsuarioDTO + { + public string NovoPerfilId { get; set; } + } +} + diff --git a/api/Usuarios/LoginModel.cs b/api/Usuarios/LoginModel.cs new file mode 100644 index 0000000..329f0d0 --- /dev/null +++ b/api/Usuarios/LoginModel.cs @@ -0,0 +1,10 @@ +namespace api.Usuarios +{ + public class LoginModel + { + public string Token { get; set; } + public string TokenAtualizacao { get; set; } + public DateTime ExpiraEm { get; set; } + public List? Permissoes { get; set; } + } +} diff --git a/api/Usuarios/PesquisaUsuarioFiltro.cs b/api/Usuarios/PesquisaUsuarioFiltro.cs new file mode 100644 index 0000000..2bbdc3d --- /dev/null +++ b/api/Usuarios/PesquisaUsuarioFiltro.cs @@ -0,0 +1,12 @@ +namespace api.Usuarios +{ + public class PesquisaUsuarioFiltro + { + public int Pagina { get; set; } = 1; + public int ItemsPorPagina { get; set; } = 50; + public string? Nome { get; set; } + public UF? UfLotacao { get; set; } + public Guid? PerfilId { get; set; } + public int? MunicipioId { get; set; } + } +} \ No newline at end of file diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs new file mode 100644 index 0000000..f0ca2a2 --- /dev/null +++ b/api/Usuarios/UsuarioDTO.cs @@ -0,0 +1,10 @@ +namespace api.Usuarios +{ + public class UsuarioDTO + { + public string Email { get; set; } + public string Senha { get; set; } + public string Nome { get; set; } + public UF UfLotacao { get; set; } + } +} diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs new file mode 100644 index 0000000..cf6fb0d --- /dev/null +++ b/api/Usuarios/UsuarioDnit.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioDnit : UsuarioDTO + { + public UF UfLotacao { get; set; } + } +} \ No newline at end of file diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs new file mode 100644 index 0000000..f209450 --- /dev/null +++ b/api/Usuarios/UsuarioModel.cs @@ -0,0 +1,17 @@ +using api.Municipios; +using api.Perfis; + +namespace api.Usuarios +{ + public class UsuarioModel + { + public int Id { get; set; } + public string Email { get; set; } + public string Nome { get; set; } + public string Cnpj { get; set; } + public Guid? PerfilId { get; set; } + public PerfilModel? Perfil { get; set; } + public UF UfLotacao { get; set; } + public MunicipioModel? Municipio { get; set; } + } +} diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs new file mode 100644 index 0000000..aa56571 --- /dev/null +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioTerceiro : UsuarioDTO + { + public string CNPJ { get; set; } + } +} diff --git a/dominio/dominio.csproj b/api/api.csproj similarity index 72% rename from dominio/dominio.csproj rename to api/api.csproj index 16e62dd..c91ae47 100644 --- a/dominio/dominio.csproj +++ b/api/api.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..41e00b3 --- /dev/null +++ b/app/.env @@ -0,0 +1,2 @@ +EMAIL_SERVICE_ADDRESS= +EMAIL_SERVICE_PASSWORD= \ No newline at end of file diff --git a/app/Configuracoes/SenhaConfig.cs b/app/Configuracoes/SenhaConfig.cs new file mode 100644 index 0000000..a292113 --- /dev/null +++ b/app/Configuracoes/SenhaConfig.cs @@ -0,0 +1,7 @@ +namespace app.Configuracoes +{ + public class SenhaConfig + { + public string RedefinirSenhaUrl { get; set; } = "http://localhost:3000/redefinirSenha"; + } +} diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index bb4638e..d3b427f 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -1,33 +1,53 @@ -using dominio; +using api; using Microsoft.AspNetCore.Mvc; -using repositorio; -using repositorio.Interfaces; -using service; -using service.Interfaces; +using app.Repositorios.Interfaces; +using app.Services.Interfaces; +using app.Services; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using app.Services; namespace app.Controllers { [ApiController] [Route("api/dominio")] - public class DominioController : ControllerBase + public class DominioController : AppController { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; - - public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio) + private readonly IPermissaoService permissaoService; + private readonly IMapper mapper; + private readonly AuthService authService; + + + public DominioController + ( + IUnidadeFederativaRepositorio unidadeFederativaRepositorio, + IMapper mapper, + IPermissaoService permissaoService, + AuthService authService + ) { this.unidadeFederativaRepositorio = unidadeFederativaRepositorio; + this.permissaoService = permissaoService; + this.authService = authService; + this.mapper = mapper; } - - [HttpGet("unidadeFederativa")] public IActionResult ObterLista() { - IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); + IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); return new OkObjectResult(listaUnidadeFederativa); } - + [Authorize] + [HttpGet("permissoes")] + public List ObterListaDePermissoes() + { + authService.Require(Usuario, Permissao.PerfilVisualizar); + + return permissaoService.CategorizarPermissoes(Enum.GetValues().ToList()); + } } } diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs new file mode 100644 index 0000000..cbc44e5 --- /dev/null +++ b/app/Controllers/PerfilController.cs @@ -0,0 +1,146 @@ +using api; +using api.Perfis; +using app.Entidades; +using app.Services; +using app.Services.Interfaces; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace app.Controllers +{ + [ApiController] + [Route("api/perfil")] + public class PerfilController : AppController + { + private readonly AuthService authService; + private readonly IPerfilService perfilService; + private readonly IMapper mapper; + private readonly IPermissaoService permissaoService; + + public PerfilController( + IPerfilService perfilService, + AuthService authService, + IMapper mapper, + IPermissaoService permissaoService + ) + { + this.perfilService = perfilService; + this.authService = authService; + this.mapper = mapper; + this.permissaoService = permissaoService; + } + + [Authorize] + [HttpPost()] + public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) + { + authService.Require(Usuario, Permissao.PerfilCadastrar); + + var perfil = mapper.Map(perfilDTO); + + try{ + Perfil novoPerfil = perfilService.CriarPerfil(perfil, perfilDTO.Permissoes); + return Ok(mapper.Map(novoPerfil)); + } + catch(DbUpdateException) + { + return UnprocessableEntity("Este Perfil já existe"); + } + catch(Exception) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } + } + + [Authorize] + [HttpPut("{id}")] + public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perfilDTO) + { + authService.Require(Usuario, Permissao.PerfilEditar); + + var perfil = mapper.Map(perfilDTO); + perfil.Id = id; + + try{ + var novoPerfil = await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes); + return Ok(mapper.Map(novoPerfil)); + } + catch(KeyNotFoundException) + { + return NotFound("Perfil não encontrado"); + } + catch(DbUpdateException) + { + return UnprocessableEntity("Este Perfil já existe"); + } + catch(Exception ex) + { + return StatusCode(500, $"Houve um erro interno no servidor. {ex.Message}"); + } + } + + [Authorize] + [HttpDelete("{id}")] + public async Task ExcluirPerfil(Guid id) + { + authService.Require(Usuario, Permissao.PerfilRemover); + + try{ + await perfilService.ExcluirPerfil(id); + return Ok("Perfil excluido"); + } + catch(KeyNotFoundException ex){ + return NotFound(ex.Message); + } + catch(InvalidOperationException e) + { + return StatusCode(400, e.Message); + } + catch(Exception) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } + } + + [HttpGet] + [Authorize] + public async Task ListarPerfis(int pageIndex, int pageSize, string? nome = null) + { + authService.Require(Usuario, Permissao.PerfilVisualizar); + + try + { + var pagina = await perfilService.ListarPerfisAsync(pageIndex, pageSize, nome); + + List paginaRetorno = pagina.Select(p => mapper.Map(p)).ToList(); + + return Ok(paginaRetorno); + } + catch(Exception e) + { + return StatusCode(500, e.Message + "\n" + e.StackTrace + "\nHouve um erro interno no servidor."); + } + } + + [HttpGet("{id}")] + [Authorize] + public async Task ObterPorId(Guid id) + { + authService.Require(Usuario, Permissao.PerfilVisualizar); + + var perfil = await perfilService.ObterPorIdAsync(id); + + if (perfil == null) + { + return NotFound("Perfil não encontrado"); + } + + var perfilModel = mapper.Map(perfil); + perfilModel.CategoriasPermissao = permissaoService.CategorizarPermissoes(perfil.Permissoes!.ToList()); + + return Ok(perfilModel); + } + } +} \ No newline at end of file diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 93b954c..e9593b0 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -1,51 +1,81 @@ -using dominio; +using api.Usuarios; +using api.Senhas; using Microsoft.AspNetCore.Mvc; -using service.Interfaces; +using app.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using app.Services; +using api; +using System.Data.Common; namespace app.Controllers { [ApiController] [Route("api/usuario")] - public class UsuarioController : ControllerBase + public class UsuarioController : AppController { private readonly IUsuarioService usuarioService; + private readonly AuthService authService; - public UsuarioController(IUsuarioService usuarioService) + public UsuarioController( + IUsuarioService usuarioService, + AuthService authService + ) { this.usuarioService = usuarioService; + this.authService = authService; } - + [HttpPost("login")] - public IActionResult Logar([FromBody] UsuarioDTO usuarioDTO) + public async Task Logar([FromBody] UsuarioDTO usuarioDTO) { try { - bool verificar = usuarioService.ValidaLogin(usuarioDTO); - return Ok(); + var resultado = await usuarioService.AutenticarUsuarioAsync(usuarioDTO.Email, usuarioDTO.Senha); + return Ok(resultado); } - catch(UnauthorizedAccessException){ + catch (UnauthorizedAccessException) + { return Unauthorized(); } - catch(KeyNotFoundException){ + catch (KeyNotFoundException) + { return NotFound(); } } + [HttpGet("permissoes")] + [Authorize] + public async Task> ListarPermissoes() + { + var userId = authService.GetUserId(Usuario); + return await usuarioService.ListarPermissoesAsync(userId); + } + + [HttpPost("atualizarToken")] + public async Task AtualizarToken([FromBody] AtualizarTokenDto atualizarTokenDto) + { + return await usuarioService.AtualizarTokenAsync(atualizarTokenDto); + } + + [HttpPost("cadastrarUsuarioDnit")] - public IActionResult CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) + public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) { try { - usuarioService.CadastrarUsuarioDnit(usuarioDTO); - + await usuarioService.CadastrarUsuarioDnit(usuarioDTO); return StatusCode(201, new NoContentResult()); } - catch (Npgsql.PostgresException ex) + catch (DbException) + { + return Conflict("Usuário já cadastrado."); + } + catch (ApiException ex) + { + return StatusCode(400, ex.Message); + } + catch (Exception) { - if (ex.SqlState == "23505") { - return Conflict("Usurio j cadastrado."); - } - return StatusCode(500, "Houve um erro interno no servidor."); } } @@ -59,43 +89,59 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) return StatusCode(201, new NoContentResult()); } - catch (Npgsql.PostgresException ex) + catch (DbException) + { + return Conflict("Usuário já cadastrado."); + } + catch (Exception) { - if (ex.SqlState == "23505") - { - return Conflict("Usurio j cadastrado."); - } - return StatusCode(500, "Houve um erro interno no servidor."); } } [HttpPut("recuperarSenha")] - public IActionResult RecuperarSenha([FromBody] UsuarioDTO usuarioDto) + public async Task RecuperarSenhaAsync([FromBody] UsuarioDTO usuarioDto) { try { - usuarioService.RecuperarSenha(usuarioDto); + await usuarioService.RecuperarSenha(usuarioDto); return Ok(); } - catch(KeyNotFoundException) + catch (KeyNotFoundException) { return NotFound(); } } [HttpPut("redefinirSenha")] - public IActionResult RedefinirSenha([FromBody] RedefinicaoSenhaDTO redefinirSenhaDto) + public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenhaDTO redefinirSenhaDto) { try { - usuarioService.TrocaSenha(redefinirSenhaDto); + await usuarioService.TrocaSenha(redefinirSenhaDto); + return Ok(); } - catch(KeyNotFoundException) + catch (KeyNotFoundException) { return NotFound(); } } + + [Authorize] + [HttpGet()] + public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) + { + authService.Require(Usuario, Permissao.UsuarioVisualizar); + return await usuarioService.ObterUsuariosAsync(filtro); + } + + [Authorize] + [HttpPatch("{id}/perfil")] + public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] EditarPerfilUsuarioDTO dto) + { + authService.Require(Usuario, Permissao.UsuarioPerfilEditar); + await usuarioService.EditarUsuarioPerfil(id, dto.NovoPerfilId); + } } } diff --git a/app/DI/ConfiguracaoConfig.cs b/app/DI/ConfiguracaoConfig.cs new file mode 100644 index 0000000..15975ce --- /dev/null +++ b/app/DI/ConfiguracaoConfig.cs @@ -0,0 +1,14 @@ +using app.Repositorios.Interfaces; +using app.Repositorios; +using app.Configuracoes; + +namespace app.DI +{ + public static class ConfiguracaoConfig + { + public static void AddConfiguracoes(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection("Senha")); + } + } +} diff --git a/app/DI/ContextoConfig.cs b/app/DI/ContextoConfig.cs deleted file mode 100644 index f1cea6b..0000000 --- a/app/DI/ContextoConfig.cs +++ /dev/null @@ -1,38 +0,0 @@ -using dominio.Enums; -using repositorio.Contexto; -using static repositorio.Contexto.ResolverContexto; - -namespace app.DI -{ - public static class ContextoConfig - { - public static void AddContexto(this IServiceCollection services, IConfiguration configuration) - { - string connectionPostgres = ObterConnectionString(configuration, ContextoBancoDeDados.Postgresql).Result; - - services.AddScoped(contexto => new ContextoPostgresql(connectionPostgres)); - - services.AddTransient(serviceProvider => contextos => - { - return contextos switch - { - ContextoBancoDeDados.Postgresql => serviceProvider.GetService(), - _ => throw new NotImplementedException() - }; - }); - } - - private static async Task ObterConnectionString(IConfiguration configuration, ContextoBancoDeDados contexto) - { - string conn = contexto switch - { - ContextoBancoDeDados.Postgresql => "Postgresql", - _ => throw new NotImplementedException(), - }; - - string connection = configuration.GetConnectionString(conn); - - return connection; - } - } -} diff --git a/app/DI/HandleExceptionFilter.cs b/app/DI/HandleExceptionFilter.cs new file mode 100644 index 0000000..2b64bc7 --- /dev/null +++ b/app/DI/HandleExceptionFilter.cs @@ -0,0 +1,57 @@ +using app.Services; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using System.Net; +using System.Security.Cryptography; + +namespace app.DI +{ + public class HandleExceptionFilter : IExceptionFilter + { + private readonly ILogger logger; + + public HandleExceptionFilter(ILogger logger) + { + this.logger = logger; + } + + public void OnException(ExceptionContext context) + { + if (context.Exception is ApiException apiException) + { + logger.LogWarning(apiException, "An API Exception was caught"); + + context.Result = new JsonResult(apiException.Error, JsonConvert.DefaultSettings) + { + StatusCode = (int)HttpStatusCode.UnprocessableEntity, + }; + context.ExceptionHandled = true; + } + else + { + var rawCode = new byte[3]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(rawCode); + } + var code = BitConverter.ToString(rawCode).Replace("-", ""); + + logger.LogError(context.Exception, "Unhandled exception -- code: {ExceptionCode}", code); + + var error = new + { + Message = $"Um erro inesperado aconteceu. Contate o administrador do sistema e informe o código: {code}", + ExceptionCode = code, + }; + + context.Result = new JsonResult(error, JsonConvert.DefaultSettings) + { + StatusCode = (int)HttpStatusCode.InternalServerError, + }; + + context.ExceptionHandled = true; + } + } + } +} diff --git a/app/DI/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index eedac06..f5c7623 100644 --- a/app/DI/RepositoriosConfig.cs +++ b/app/DI/RepositoriosConfig.cs @@ -1,5 +1,5 @@ -using repositorio; -using repositorio.Interfaces; +using app.Repositorios; +using app.Repositorios.Interfaces; namespace app.DI { @@ -9,6 +9,7 @@ public static void AddConfigRepositorios(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } } diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index f78d753..41d3736 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,14 +1,28 @@ -using service; -using service.Interfaces; +using app.Entidades; +using app.Services; +using app.Services.Interfaces; +using auth; +using Microsoft.EntityFrameworkCore; namespace app.DI { public static class ServicesConfig { public static void AddConfigServices(this IServiceCollection services, IConfiguration configuration) - { + { + var mode = Environment.GetEnvironmentVariable("MODE"); + var connectionString = mode == "container" ? "PostgreSqlDocker" : "PostgreSql"; + + services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString(connectionString))); services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddControllers(o => o.Filters.Add(typeof(HandleExceptionFilter))); + + services.AddAuth(configuration); } } } diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs new file mode 100644 index 0000000..22cce9d --- /dev/null +++ b/app/Entidades/AppDbContext.cs @@ -0,0 +1,113 @@ +using api; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualBasic.FileIO; + +namespace app.Entidades +{ + public class AppDbContext : DbContext + { + public DbSet Municipio { get; set; } + public DbSet Usuario { get; set; } + public DbSet RedefinicaoSenha { get; set; } + public DbSet Empresa { get; set; } + + public DbSet Perfis { get; set; } + public DbSet PerfilPermissoes { get; set; } + + public AppDbContext(DbContextOptions options) : base(options) + { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(u => u.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasIndex(u => u.Email) + .IsUnique(); + + modelBuilder.Entity() + .Property(r => r.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasOne(r => r.Usuario) + .WithMany(u => u.RedefinicaoSenha) + .HasForeignKey(r => r.IdUsuario); + + modelBuilder.Entity() + .HasIndex(e => e.Cnpj) + .IsUnique(); + + modelBuilder.Entity() + .HasMany(e => e.Usuarios) + .WithMany(u => u.Empresas) + .UsingEntity(em => + { + em.Property("UsuariosId").HasColumnName("IdUsuario"); + em.Property("EmpresasCnpj").HasColumnName("CnpjEmpresa"); + em.ToTable("UsuarioEmpresa"); + }); + + modelBuilder.Entity() + .HasIndex(p => p.Nome) + .IsUnique(); + + modelBuilder.Entity() + .HasMany(p => p.PerfilPermissoes) + .WithOne(pp => pp.Perfil) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(u => u.Perfil) + .WithMany(p => p.Usuarios) + .OnDelete(DeleteBehavior.Restrict); + } + + public void Popula() + { + PopulaMunicipiosPorArquivo(null, Path.Join(".", "Migrations", "Data", "municipios.csv")); + } + + public List? PopulaMunicipiosPorArquivo(int? limit, string caminho) + { + var hasMunicipio = Municipio.Any(); + var municipios = new List(); + + if (hasMunicipio) + { + return null; + } + + using (var fs = File.OpenRead(caminho)) + using (var parser = new TextFieldParser(fs)) + { + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + + var columns = new Dictionary { { "id", 0 }, { "name", 1 }, { "uf", 2 } }; + + while (!parser.EndOfData) + { + var row = parser.ReadFields()!; + var municipio = new Municipio + { + Id = int.Parse(row[columns["id"]]), + Nome = row[columns["name"]], + Uf = (UF)int.Parse(row[columns["uf"]]), + }; + + municipios.Add(municipio); + if (limit.HasValue && municipios.Count >= limit.Value) + { + break; + } + } + } + AddRange(municipios); + SaveChanges(); + return municipios; + } + } +} \ No newline at end of file diff --git a/app/Entidades/Empresa.cs b/app/Entidades/Empresa.cs new file mode 100644 index 0000000..b5e3141 --- /dev/null +++ b/app/Entidades/Empresa.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace app.Entidades +{ + public class Empresa + { + [Key, MaxLength(14)] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Cnpj { get; set; } + + [Required, MaxLength(200)] + public string RazaoSocial { get; set; } + + public List Usuarios { get; set; } + } +} \ No newline at end of file diff --git a/app/Entidades/Municipio.cs b/app/Entidades/Municipio.cs new file mode 100644 index 0000000..c702105 --- /dev/null +++ b/app/Entidades/Municipio.cs @@ -0,0 +1,15 @@ +using api; +using System.ComponentModel.DataAnnotations; + +namespace app.Entidades +{ + public class Municipio + { + [Key] + public int Id { get; set; } + [Required, MaxLength(50)] + public string Nome { get; set; } + [Required] + public UF Uf { get; set; } + } +} \ No newline at end of file diff --git a/app/Entidades/Perfil.cs b/app/Entidades/Perfil.cs new file mode 100644 index 0000000..a0c1cf3 --- /dev/null +++ b/app/Entidades/Perfil.cs @@ -0,0 +1,28 @@ +using api; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace app.Entidades +{ + public class Perfil + { + [Key] + public Guid Id { get; set; } + + [Required, MaxLength(200)] + public string Nome { get; set; } + + [Required] + public TipoPerfil Tipo { get; set; } = TipoPerfil.Customizavel; + + public List? PerfilPermissoes { get; set; } + + [NotMapped] + public List? PermissoesSessao { get; set; } + + [NotMapped] + public IEnumerable? Permissoes => PermissoesSessao ?? PerfilPermissoes?.Select(p => p.Permissao); + + public List? Usuarios { get; set; } + } +} diff --git a/app/Entidades/PerfilPermissao.cs b/app/Entidades/PerfilPermissao.cs new file mode 100644 index 0000000..9b06a8d --- /dev/null +++ b/app/Entidades/PerfilPermissao.cs @@ -0,0 +1,18 @@ +using api; +using System.ComponentModel.DataAnnotations; + +namespace app.Entidades +{ + public class PerfilPermissao + { + [Key] + public Guid Id { get; set; } + + [Required] + public Guid PerfilId { get; set; } + public Perfil Perfil { get; set; } + + [Required] + public Permissao Permissao { get; set; } + } +} diff --git a/app/Entidades/RedefinicaoSenha.cs b/app/Entidades/RedefinicaoSenha.cs new file mode 100644 index 0000000..b289fd7 --- /dev/null +++ b/app/Entidades/RedefinicaoSenha.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace app.Entidades +{ + public class RedefinicaoSenha + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required, MaxLength(150)] + public string Uuid { get; set; } + + [Required] + public int IdUsuario { get; set; } + public Usuario Usuario { get; set; } + } +} \ No newline at end of file diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs new file mode 100644 index 0000000..d5081b5 --- /dev/null +++ b/app/Entidades/Usuario.cs @@ -0,0 +1,37 @@ +using api; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace app.Entidades +{ + public class Usuario + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public UF UfLotacao { get; set; } + + [Required, MaxLength(150)] + public string Nome { get; set; } + + [Required, MaxLength(50)] + public string Email { get; set; } + + [Required, MaxLength(200)] + public string Senha { get; set; } + + public List RedefinicaoSenha { get; set; } + + public List? Empresas { get; set; } + + public Guid? PerfilId { get; set; } + public Perfil? Perfil { get; set; } + + public string? TokenAtualizacao { get; set; } + public DateTime? TokenAtualizacaoExpiracao { get; set; } + public int? MunicipioId { get; set; } + public Municipio? Municipio { get; set; } + } +} \ No newline at end of file diff --git a/app/Migrations/20231012122128_Initial.Designer.cs b/app/Migrations/20231012122128_Initial.Designer.cs new file mode 100644 index 0000000..fb0004f --- /dev/null +++ b/app/Migrations/20231012122128_Initial.Designer.cs @@ -0,0 +1,29 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012122128_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012122128_Initial.cs b/app/Migrations/20231012122128_Initial.cs new file mode 100644 index 0000000..3b26d49 --- /dev/null +++ b/app/Migrations/20231012122128_Initial.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/app/Migrations/20231012123152_Usuario.Designer.cs b/app/Migrations/20231012123152_Usuario.Designer.cs new file mode 100644 index 0000000..da5f104 --- /dev/null +++ b/app/Migrations/20231012123152_Usuario.Designer.cs @@ -0,0 +1,57 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012123152_Usuario")] + partial class Usuario + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Usuario"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012123152_Usuario.cs b/app/Migrations/20231012123152_Usuario.cs new file mode 100644 index 0000000..a03f785 --- /dev/null +++ b/app/Migrations/20231012123152_Usuario.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class Usuario : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Usuario", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + nome = table.Column(type: "character varying(150)", maxLength: 150, nullable: false), + email = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + senha = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Usuario", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Usuario"); + } + } +} diff --git a/app/Migrations/20231012142410_RedefinicaoSenha.Designer.cs b/app/Migrations/20231012142410_RedefinicaoSenha.Designer.cs new file mode 100644 index 0000000..9cf9014 --- /dev/null +++ b/app/Migrations/20231012142410_RedefinicaoSenha.Designer.cs @@ -0,0 +1,96 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012142410_RedefinicaoSenha")] + partial class RedefinicaoSenha + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012142410_RedefinicaoSenha.cs b/app/Migrations/20231012142410_RedefinicaoSenha.cs new file mode 100644 index 0000000..b031e1f --- /dev/null +++ b/app/Migrations/20231012142410_RedefinicaoSenha.cs @@ -0,0 +1,77 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class RedefinicaoSenha : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "senha", + table: "Usuario", + newName: "Senha"); + + migrationBuilder.RenameColumn( + name: "nome", + table: "Usuario", + newName: "Nome"); + + migrationBuilder.RenameColumn( + name: "email", + table: "Usuario", + newName: "Email"); + + migrationBuilder.CreateTable( + name: "RedefinicaoSenha", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Uuid = table.Column(type: "character varying(150)", maxLength: 150, nullable: false), + IdUsuario = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RedefinicaoSenha", x => x.Id); + table.ForeignKey( + name: "FK_RedefinicaoSenha_Usuario_IdUsuario", + column: x => x.IdUsuario, + principalTable: "Usuario", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RedefinicaoSenha_IdUsuario", + table: "RedefinicaoSenha", + column: "IdUsuario"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RedefinicaoSenha"); + + migrationBuilder.RenameColumn( + name: "Senha", + table: "Usuario", + newName: "senha"); + + migrationBuilder.RenameColumn( + name: "Nome", + table: "Usuario", + newName: "nome"); + + migrationBuilder.RenameColumn( + name: "Email", + table: "Usuario", + newName: "email"); + } + } +} diff --git a/app/Migrations/20231012145354_Empresa.Designer.cs b/app/Migrations/20231012145354_Empresa.Designer.cs new file mode 100644 index 0000000..4dfca08 --- /dev/null +++ b/app/Migrations/20231012145354_Empresa.Designer.cs @@ -0,0 +1,113 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012145354_Empresa")] + partial class Empresa + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012145354_Empresa.cs b/app/Migrations/20231012145354_Empresa.cs new file mode 100644 index 0000000..ffc3c6d --- /dev/null +++ b/app/Migrations/20231012145354_Empresa.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class Empresa : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Empresa", + columns: table => new + { + Cnpj = table.Column(type: "character varying(14)", maxLength: 14, nullable: false), + RazaoSocial = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Empresa", x => x.Cnpj); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Empresa"); + } + } +} diff --git a/app/Migrations/20231012200221_UsuarioEmpresa.Designer.cs b/app/Migrations/20231012200221_UsuarioEmpresa.Designer.cs new file mode 100644 index 0000000..b257cbf --- /dev/null +++ b/app/Migrations/20231012200221_UsuarioEmpresa.Designer.cs @@ -0,0 +1,145 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012200221_UsuarioEmpresa")] + partial class UsuarioEmpresa + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012200221_UsuarioEmpresa.cs b/app/Migrations/20231012200221_UsuarioEmpresa.cs new file mode 100644 index 0000000..98ec41e --- /dev/null +++ b/app/Migrations/20231012200221_UsuarioEmpresa.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class UsuarioEmpresa : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UsuarioEmpresa", + columns: table => new + { + CnpjEmpresa = table.Column(type: "character varying(14)", nullable: false), + IdUsuario = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UsuarioEmpresa", x => new { x.CnpjEmpresa, x.IdUsuario }); + table.ForeignKey( + name: "FK_UsuarioEmpresa_Empresa_CnpjEmpresa", + column: x => x.CnpjEmpresa, + principalTable: "Empresa", + principalColumn: "Cnpj", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UsuarioEmpresa_Usuario_IdUsuario", + column: x => x.IdUsuario, + principalTable: "Usuario", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UsuarioEmpresa_IdUsuario", + table: "UsuarioEmpresa", + column: "IdUsuario"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UsuarioEmpresa"); + } + } +} diff --git a/app/Migrations/20231012202644_UfEnum.Designer.cs b/app/Migrations/20231012202644_UfEnum.Designer.cs new file mode 100644 index 0000000..2c0b7c4 --- /dev/null +++ b/app/Migrations/20231012202644_UfEnum.Designer.cs @@ -0,0 +1,148 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231012202644_UfEnum")] + partial class UfEnum + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231012202644_UfEnum.cs b/app/Migrations/20231012202644_UfEnum.cs new file mode 100644 index 0000000..455a774 --- /dev/null +++ b/app/Migrations/20231012202644_UfEnum.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class UfEnum : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UfLotacao", + table: "Usuario", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UfLotacao", + table: "Usuario"); + } + } +} diff --git a/app/Migrations/20231019025721_Perfil.Designer.cs b/app/Migrations/20231019025721_Perfil.Designer.cs new file mode 100644 index 0000000..3831ca9 --- /dev/null +++ b/app/Migrations/20231019025721_Perfil.Designer.cs @@ -0,0 +1,223 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231019025721_Perfil")] + partial class Perfil + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231019025721_Perfil.cs b/app/Migrations/20231019025721_Perfil.cs new file mode 100644 index 0000000..4fc3b97 --- /dev/null +++ b/app/Migrations/20231019025721_Perfil.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class Perfil : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PerfilId", + table: "Usuario", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "Perfis", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Nome = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Perfis", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PerfilPermissoes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + PerfilId = table.Column(type: "uuid", nullable: false), + Permissao = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PerfilPermissoes", x => x.Id); + table.ForeignKey( + name: "FK_PerfilPermissoes_Perfis_PerfilId", + column: x => x.PerfilId, + principalTable: "Perfis", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Usuario_Email", + table: "Usuario", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Usuario_PerfilId", + table: "Usuario", + column: "PerfilId"); + + migrationBuilder.CreateIndex( + name: "IX_Empresa_Cnpj", + table: "Empresa", + column: "Cnpj", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PerfilPermissoes_PerfilId", + table: "PerfilPermissoes", + column: "PerfilId"); + + migrationBuilder.AddForeignKey( + name: "FK_Usuario_Perfis_PerfilId", + table: "Usuario", + column: "PerfilId", + principalTable: "Perfis", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Usuario_Perfis_PerfilId", + table: "Usuario"); + + migrationBuilder.DropTable( + name: "PerfilPermissoes"); + + migrationBuilder.DropTable( + name: "Perfis"); + + migrationBuilder.DropIndex( + name: "IX_Usuario_Email", + table: "Usuario"); + + migrationBuilder.DropIndex( + name: "IX_Usuario_PerfilId", + table: "Usuario"); + + migrationBuilder.DropIndex( + name: "IX_Empresa_Cnpj", + table: "Empresa"); + + migrationBuilder.DropColumn( + name: "PerfilId", + table: "Usuario"); + } + } +} diff --git a/app/Migrations/20231021000639_TokenAtualizacao.Designer.cs b/app/Migrations/20231021000639_TokenAtualizacao.Designer.cs new file mode 100644 index 0000000..10979c1 --- /dev/null +++ b/app/Migrations/20231021000639_TokenAtualizacao.Designer.cs @@ -0,0 +1,229 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231021000639_TokenAtualizacao")] + partial class TokenAtualizacao + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231021000639_TokenAtualizacao.cs b/app/Migrations/20231021000639_TokenAtualizacao.cs new file mode 100644 index 0000000..e8f8cd9 --- /dev/null +++ b/app/Migrations/20231021000639_TokenAtualizacao.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class TokenAtualizacao : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TokenAtualizacao", + table: "Usuario", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "TokenAtualizacaoExpiracao", + table: "Usuario", + type: "timestamp with time zone", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TokenAtualizacao", + table: "Usuario"); + + migrationBuilder.DropColumn( + name: "TokenAtualizacaoExpiracao", + table: "Usuario"); + } + } +} diff --git a/app/Migrations/20231021183203_PerfilNomeUnico.Designer.cs b/app/Migrations/20231021183203_PerfilNomeUnico.Designer.cs new file mode 100644 index 0000000..a4e7957 --- /dev/null +++ b/app/Migrations/20231021183203_PerfilNomeUnico.Designer.cs @@ -0,0 +1,232 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231021183203_PerfilNomeUnico")] + partial class PerfilNomeUnico + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231021183203_PerfilNomeUnico.cs b/app/Migrations/20231021183203_PerfilNomeUnico.cs new file mode 100644 index 0000000..f6aa178 --- /dev/null +++ b/app/Migrations/20231021183203_PerfilNomeUnico.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class PerfilNomeUnico : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Perfis_Nome", + table: "Perfis", + column: "Nome", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Perfis_Nome", + table: "Perfis"); + } + } +} diff --git a/app/Migrations/20231022000223_PerfilTipo.Designer.cs b/app/Migrations/20231022000223_PerfilTipo.Designer.cs new file mode 100644 index 0000000..9e9c9ca --- /dev/null +++ b/app/Migrations/20231022000223_PerfilTipo.Designer.cs @@ -0,0 +1,235 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231022000223_PerfilTipo")] + partial class PerfilTipo + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Tipo") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231022000223_PerfilTipo.cs b/app/Migrations/20231022000223_PerfilTipo.cs new file mode 100644 index 0000000..09d7dca --- /dev/null +++ b/app/Migrations/20231022000223_PerfilTipo.cs @@ -0,0 +1,83 @@ +using api; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class PerfilTipo : Migration + { + /// + /// Warning: this migration has custom code + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Tipo", + table: "Perfis", + type: "integer", + nullable: false, + defaultValue: 0); + + // ############# + // ## Begin custom code + // ############# + + var idPerfilBasico = Guid.NewGuid(); + + migrationBuilder.InsertData + ( + table: "Perfis", + columns: new[] { "Id", "Nome", "Tipo" }, + values: new object[] { idPerfilBasico, "Básico", (int) TipoPerfil.Basico } + ); + + migrationBuilder.InsertData + ( + table: "PerfilPermissoes", + columns: new[] { "Id", "PerfilId", "Permissao" }, + values: new object[] { Guid.NewGuid(), idPerfilBasico, (int) Permissao.PerfilCadastrar } + ); + + migrationBuilder.InsertData + ( + table: "PerfilPermissoes", + columns: new[] { "Id", "PerfilId", "Permissao" }, + values: new object[] { Guid.NewGuid(), idPerfilBasico, (int) Permissao.PerfilRemover } + ); + + migrationBuilder.InsertData + ( + table: "PerfilPermissoes", + columns: new[] { "Id", "PerfilId", "Permissao" }, + values: new object[] { Guid.NewGuid(), idPerfilBasico, (int) Permissao.PerfilVisualizar } + ); + + migrationBuilder.InsertData + ( + table: "PerfilPermissoes", + columns: new[] { "Id", "PerfilId", "Permissao" }, + values: new object[] { Guid.NewGuid(), idPerfilBasico, (int) Permissao.PerfilEditar } + ); + + migrationBuilder.InsertData + ( + table: "Perfis", + columns: new[] { "Id", "Nome", "Tipo" }, + values: new object[] { Guid.NewGuid(), "Administrador", (int) TipoPerfil.Administrador } + ); + + // ############# + // ## End custom code + // ############# + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Tipo", + table: "Perfis"); + } + } +} diff --git a/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.Designer.cs b/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.Designer.cs new file mode 100644 index 0000000..2c3e0c7 --- /dev/null +++ b/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.Designer.cs @@ -0,0 +1,267 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231028163722_AdicionaMunicipioERelacionamentoComUsuario")] + partial class AdicionaMunicipioERelacionamentoComUsuario + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Municipio", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Uf") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Municipio"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Tipo") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MunicipioId") + .HasColumnType("integer"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("MunicipioId"); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Municipio", "Municipio") + .WithMany() + .HasForeignKey("MunicipioId"); + + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Municipio"); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.cs b/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.cs new file mode 100644 index 0000000..71e118c --- /dev/null +++ b/app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class AdicionaMunicipioERelacionamentoComUsuario : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MunicipioId", + table: "Usuario", + type: "integer", + nullable: true); + + migrationBuilder.CreateTable( + name: "Municipio", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Nome = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Uf = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Municipio", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Usuario_MunicipioId", + table: "Usuario", + column: "MunicipioId"); + + migrationBuilder.AddForeignKey( + name: "FK_Usuario_Municipio_MunicipioId", + table: "Usuario", + column: "MunicipioId", + principalTable: "Municipio", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Usuario_Municipio_MunicipioId", + table: "Usuario"); + + migrationBuilder.DropTable( + name: "Municipio"); + + migrationBuilder.DropIndex( + name: "IX_Usuario_MunicipioId", + table: "Usuario"); + + migrationBuilder.DropColumn( + name: "MunicipioId", + table: "Usuario"); + } + } +} diff --git a/app/Migrations/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..2ee0404 --- /dev/null +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,264 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using app.Entidades; + +#nullable disable + +namespace app.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.Property("EmpresasCnpj") + .HasColumnType("character varying(14)") + .HasColumnName("CnpjEmpresa"); + + b.Property("UsuariosId") + .HasColumnType("integer") + .HasColumnName("IdUsuario"); + + b.HasKey("EmpresasCnpj", "UsuariosId"); + + b.HasIndex("UsuariosId"); + + b.ToTable("UsuarioEmpresa", (string)null); + }); + + modelBuilder.Entity("app.Entidades.Empresa", b => + { + b.Property("Cnpj") + .ValueGeneratedOnAdd() + .HasMaxLength(14) + .HasColumnType("character varying(14)"); + + b.Property("RazaoSocial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Cnpj"); + + b.HasIndex("Cnpj") + .IsUnique(); + + b.ToTable("Empresa"); + }); + + modelBuilder.Entity("app.Entidades.Municipio", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Uf") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Municipio"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Tipo") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis"); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Permissao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PerfilId"); + + b.ToTable("PerfilPermissoes"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IdUsuario") + .HasColumnType("integer"); + + b.Property("Uuid") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("IdUsuario"); + + b.ToTable("RedefinicaoSenha"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MunicipioId") + .HasColumnType("integer"); + + b.Property("Nome") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("PerfilId") + .HasColumnType("uuid"); + + b.Property("Senha") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + + b.Property("UfLotacao") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("MunicipioId"); + + b.HasIndex("PerfilId"); + + b.ToTable("Usuario"); + }); + + modelBuilder.Entity("EmpresaUsuario", b => + { + b.HasOne("app.Entidades.Empresa", null) + .WithMany() + .HasForeignKey("EmpresasCnpj") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("app.Entidades.Usuario", null) + .WithMany() + .HasForeignKey("UsuariosId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("app.Entidades.PerfilPermissao", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("PerfilPermissoes") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => + { + b.HasOne("app.Entidades.Usuario", "Usuario") + .WithMany("RedefinicaoSenha") + .HasForeignKey("IdUsuario") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Usuario"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Municipio", "Municipio") + .WithMany() + .HasForeignKey("MunicipioId"); + + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Municipio"); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.Navigation("RedefinicaoSenha"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/Migrations/Data/municipios.csv b/app/Migrations/Data/municipios.csv new file mode 100644 index 0000000..194b588 --- /dev/null +++ b/app/Migrations/Data/municipios.csv @@ -0,0 +1,5570 @@ +5200050,"Abadia de Goiás",8 +3100104,"Abadia dos Dourados",12 +5200100,"Abadiânia",8 +3100203,"Abaeté",12 +1500107,"Abaetetuba",13 +2300101,"Abaiara",6 +2900108,"Abaíra",5 +2900207,"Abaré",5 +4100103,"Abatiá",15 +4200051,"Abdon Batista",23 +1500131,"Abel Figueiredo",13 +4200101,"Abelardo Luz",23 +3100302,"Abre Campo",12 +2600054,"Abreu e Lima",16 +1700251,"Abreulândia",26 +3100401,"Acaiaca",12 +2100055,"Açailândia",9 +2900306,"Acajutiba",5 +1500206,"Acará",13 +2300150,"Acarape",6 +2300200,"Acaraú",6 +2400109,"Acari",19 +2200053,"Acauã",17 +4300034,"Aceguá",20 +2300309,"Acopiara",6 +5100102,"Acorizal",10 +5200134,"Acreúna",8 +2400208,"Açu",19 +3100500,"Açucena",12 +3500105,"Adamantina",24 +5200159,"Adelândia",8 +3500204,"Adolfo",24 +4100202,"Adrianópolis",15 +2900355,"Adustina",5 +2600104,"Afogados da Ingazeira",16 +2400307,"Afonso Bezerra",19 +3200102,"Afonso Cláudio",7 +2100105,"Afonso Cunha",9 +2600203,"Afrânio",16 +1500305,"Afuá",13 +2600302,"Agrestina",16 +2200103,"Agricolândia",17 +4200200,"Agrolândia",23 +4200309,"Agronômica",23 +1500347,"Água Azul do Norte",13 +3100609,"Água Boa",12 +5100201,"Água Boa",10 +2200202,"Água Branca",17 +2500106,"Água Branca",14 +2700102,"Água Branca",2 +5000203,"Água Clara",11 +3100708,"Água Comprida",12 +4200408,"Água Doce",23 +2100154,"Água Doce do Maranhão",9 +3200169,"Água Doce do Norte",7 +2900405,"Água Fria",5 +5200175,"Água Fria de Goiás",8 +5200209,"Água Limpa",8 +2400406,"Água Nova",19 +2600401,"Água Preta",16 +4300059,"Água Santa",20 +3500303,"Aguaí",24 +3100807,"Aguanil",12 +2600500,"Águas Belas",16 +3500402,"Águas da Prata",24 +4200507,"Águas de Chapecó",23 +3500501,"Águas de Lindóia",24 +3500550,"Águas de Santa Bárbara",24 +3500600,"Águas de São Pedro",24 +3100906,"Águas Formosas",12 +4200556,"Águas Frias",23 +5200258,"Águas Lindas de Goiás",8 +4200606,"Águas Mornas",23 +3101003,"Águas Vermelhas",12 +4300109,"Agudo",20 +3500709,"Agudos",24 +4100301,"Agudos do Sul",15 +3200136,"Águia Branca",7 +2500205,"Aguiar",14 +1700301,"Aguiarnópolis",26 +3101102,"Aimorés",12 +2900603,"Aiquara",5 +2300408,"Aiuaba",6 +3101201,"Aiuruoca",12 +4300208,"Ajuricaba",20 +3101300,"Alagoa",12 +2500304,"Alagoa Grande",14 +2500403,"Alagoa Nova",14 +2500502,"Alagoinha",14 +2600609,"Alagoinha",16 +2200251,"Alagoinha do Piauí",17 +2900702,"Alagoinhas",5 +3500758,"Alambari",24 +3101409,"Albertina",12 +2100204,"Alcântara",9 +2300507,"Alcântaras",6 +2500536,"Alcantil",14 +5000252,"Alcinópolis",11 +2900801,"Alcobaça",5 +2100303,"Aldeias Altas",9 +4300307,"Alecrim",20 +3200201,"Alegre",7 +4300406,"Alegrete",20 +2200277,"Alegrete do Piauí",17 +4300455,"Alegria",20 +3101508,"Além Paraíba",12 +1500404,"Alenquer",13 +2400505,"Alexandria",19 +5200308,"Alexânia",8 +3101607,"Alfenas",12 +3200300,"Alfredo Chaves",7 +3500808,"Alfredo Marcondes",24 +3101631,"Alfredo Vasconcelos",12 +4200705,"Alfredo Wagner",23 +2500577,"Algodão de Jandaíra",14 +2500601,"Alhandra",14 +2600708,"Aliança",16 +1700350,"Aliança do Tocantins",26 +2900900,"Almadina",5 +1700400,"Almas",26 +1500503,"Almeirim",13 +3101706,"Almenara",12 +2400604,"Almino Afonso",19 +4100400,"Almirante Tamandaré",15 +4300471,"Almirante Tamandaré do Sul",20 +5200506,"Aloândia",8 +3101805,"Alpercata",12 +4300505,"Alpestre",20 +3101904,"Alpinópolis",12 +5100250,"Alta Floresta",10 +3500907,"Altair",24 +1500602,"Altamira",13 +2100402,"Altamira do Maranhão",9 +4100459,"Altamira do Paraná",15 +2300606,"Altaneira",6 +3102001,"Alterosa",12 +2600807,"Altinho",16 +3501004,"Altinópolis",24 +3501103,"Alto Alegre",24 +1400050,"Alto Alegre",22 +4300554,"Alto Alegre",20 +2100436,"Alto Alegre do Maranhão",9 +2100477,"Alto Alegre do Pindaré",9 +5100300,"Alto Araguaia",10 +4200754,"Alto Bela Vista",23 +5100359,"Alto Boa Vista",10 +3102050,"Alto Caparaó",12 +2400703,"Alto do Rodrigues",19 +4300570,"Alto Feliz",20 +5100409,"Alto Garças",10 +5200555,"Alto Horizonte",8 +3153509,"Alto Jequitibá",12 +2200301,"Alto Longá",17 +1100015,"Alta Floresta D'Oeste",21 +1100379,"Alto Alegre dos Parecis",21 +5100508,"Alto Paraguai",10 +4128625,"Alto Paraíso",15 +5200605,"Alto Paraíso de Goiás",8 +4100608,"Alto Paraná",15 +2100501,"Alto Parnaíba",9 +4100707,"Alto Piquiri",15 +3102100,"Alto Rio Doce",12 +3200359,"Alto Rio Novo",7 +2300705,"Alto Santo",6 +5100607,"Alto Taquari",10 +4100509,"Altônia",15 +2200400,"Altos",17 +3501152,"Alumínio",24 +1300029,"Alvarães",4 +3102209,"Alvarenga",12 +3501202,"Álvares Florence",24 +3501301,"Álvares Machado",24 +3501400,"Álvaro de Carvalho",24 +3501509,"Alvinlândia",24 +3102308,"Alvinópolis",12 +1700707,"Alvorada",26 +4300604,"Alvorada",20 +3102407,"Alvorada de Minas",12 +2200459,"Alvorada do Gurguéia",17 +5200803,"Alvorada do Norte",8 +4100806,"Alvorada do Sul",15 +1400027,"Amajari",22 +5000609,"Amambai",11 +1600105,"Amapá",3 +2100550,"Amapá do Maranhão",9 +4100905,"Amaporã",15 +2600906,"Amaraji",16 +4300638,"Amaral Ferrador",20 +5200829,"Amaralina",8 +2200509,"Amarante",17 +2100600,"Amarante do Maranhão",9 +2901007,"Amargosa",5 +1300060,"Amaturá",4 +2901106,"Amélia Rodrigues",5 +2901155,"América Dourada",5 +3501608,"Americana",24 +5200852,"Americano do Brasil",8 +3501707,"Américo Brasiliense",24 +3501806,"Américo de Campos",24 +4300646,"Ametista do Sul",20 +2300754,"Amontada",6 +5200902,"Amorinópolis",8 +2500734,"Amparo",14 +3501905,"Amparo",24 +2800100,"Amparo de São Francisco",25 +3102506,"Amparo do Serra",12 +4101002,"Ampére",15 +2700201,"Anadia",2 +2901205,"Anagé",5 +4101051,"Anahy",15 +1500701,"Anajás",13 +2100709,"Anajatuba",9 +3502002,"Analândia",24 +1300086,"Anamã",4 +1701002,"Ananás",26 +1500800,"Ananindeua",13 +5201108,"Anápolis",8 +1500859,"Anapu",13 +2100808,"Anapurus",9 +5000708,"Anastácio",11 +5000807,"Anaurilândia",11 +4200804,"Anchieta",23 +3200409,"Anchieta",7 +2901304,"Andaraí",5 +4101101,"Andirá",15 +2901353,"Andorinha",5 +3102605,"Andradas",12 +3502101,"Andradina",24 +4300661,"André da Rocha",20 +3102803,"Andrelândia",12 +3502200,"Angatuba",24 +3102852,"Angelândia",12 +5000856,"Angélica",11 +2601003,"Angelim",16 +4200903,"Angelina",23 +2901403,"Angical",5 +2200608,"Angical do Piauí",17 +1701051,"Angico",26 +2400802,"Angicos",19 +3300100,"Angra dos Reis",18 +2901502,"Anguera",5 +4101150,"Ângulo",15 +5201207,"Anhanguera",8 +3502309,"Anhembi",24 +3502408,"Anhumas",24 +5201306,"Anicuns",8 +2200707,"Anísio de Abreu",17 +4201000,"Anita Garibaldi",23 +4201109,"Anitápolis",23 +1300102,"Anori",4 +4300703,"Anta Gorda",20 +2901601,"Antas",5 +4101200,"Antonina",15 +2300804,"Antonina do Norte",6 +2200806,"Antônio Almeida",17 +2901700,"Antônio Cardoso",5 +4201208,"Antônio Carlos",23 +3102902,"Antônio Carlos",12 +3103009,"Antônio Dias",12 +2901809,"Antônio Gonçalves",5 +5000906,"Antônio João",11 +2400901,"Antônio Martins",19 +4101309,"Antônio Olinto",15 +4300802,"Antônio Prado",20 +3103108,"Antônio Prado de Minas",12 +2500775,"Aparecida",14 +3502507,"Aparecida",24 +3502606,"Aparecida d'Oeste",24 +5201405,"Aparecida de Goiânia",8 +5201454,"Aparecida do Rio Doce",8 +1701101,"Aparecida do Rio Negro",26 +5001003,"Aparecida do Taboado",11 +3300159,"Aperibé",18 +3200508,"Apiacá",7 +5100805,"Apiacás",10 +3502705,"Apiaí",24 +2100832,"Apicum-Açu",9 +4201257,"Apiúna",23 +2401008,"Apodi",19 +2901908,"Aporá",5 +5201504,"Aporé",8 +2901957,"Apuarema",5 +4101408,"Apucarana",15 +1300144,"Apuí",4 +2300903,"Apuiarés",6 +2800209,"Aquidabã",25 +5001102,"Aquidauana",11 +2301000,"Aquiraz",6 +4201273,"Arabutã",23 +2500809,"Araçagi",14 +3103207,"Araçaí",12 +2800308,"Aracaju",28 +3502754,"Araçariguama",24 +2902054,"Araças",5 +2301109,"Aracati",6 +2902005,"Aracatu",5 +3502804,"Araçatuba",24 +2902104,"Araci",5 +3103306,"Aracitaba",12 +2601052,"Araçoiaba",16 +2301208,"Aracoiaba",6 +3502903,"Araçoiaba da Serra",24 +3200607,"Aracruz",7 +5201603,"Araçu",8 +3103405,"Araçuaí",12 +5201702,"Aragarças",8 +5201801,"Aragoiânia",8 +1701309,"Aragominas",26 +1701903,"Araguacema",26 +1702000,"Araguaçu",26 +5101001,"Araguaiana",10 +1100346,"Alvorada D'Oeste",21 +1702109,"Araguaína",26 +5101209,"Araguainha",10 +1702158,"Araguanã",26 +2100873,"Araguanã",9 +5202155,"Araguapaz",8 +3103504,"Araguari",12 +1702208,"Araguatins",26 +2100907,"Araioses",9 +5001243,"Aral Moreira",11 +2902203,"Aramari",5 +4300851,"Arambaré",20 +2100956,"Arame",9 +3503000,"Aramina",24 +3503109,"Arandu",24 +3103603,"Arantina",12 +3503158,"Arapeí",24 +2700300,"Arapiraca",2 +1702307,"Arapoema",26 +3103702,"Araponga",12 +4101507,"Arapongas",15 +3103751,"Araporã",12 +4101606,"Arapoti",15 +4101655,"Arapuã",15 +3103801,"Arapuá",12 +5101258,"Araputanga",10 +4201307,"Araquari",23 +2500908,"Arara",14 +4201406,"Araranguá",23 +3503208,"Araraquara",24 +3503307,"Araras",24 +2301257,"Ararendá",6 +2101004,"Arari",9 +4300877,"Araricá",20 +2301307,"Araripe",6 +2601102,"Araripina",16 +3300209,"Araruama",18 +4101705,"Araruna",15 +2501005,"Araruna",14 +2902252,"Arataca",5 +4300901,"Aratiba",20 +2301406,"Aratuba",6 +2902302,"Aratuípe",5 +2800407,"Arauá",25 +4101804,"Araucária",15 +3103900,"Araújos",12 +3104007,"Araxá",12 +3104106,"Arceburgo",12 +3503356,"Arco-Íris",24 +3104205,"Arcos",12 +2601201,"Arcoverde",16 +3104304,"Areado",12 +3300225,"Areal",18 +3503406,"Arealva",24 +2501104,"Areia",14 +2401107,"Areia Branca",19 +2800506,"Areia Branca",25 +2501153,"Areia de Baraúnas",14 +2501203,"Areial",14 +3503505,"Areias",24 +3503604,"Areiópolis",24 +5101308,"Arenápolis",10 +5202353,"Arenópolis",8 +2401206,"Arês",19 +3104403,"Argirita",12 +3104452,"Aricanduva",12 +3104502,"Arinos",12 +5101407,"Aripuanã",10 +3503703,"Ariranha",24 +4101853,"Ariranha do Ivaí",15 +3300233,"Armação dos Búzios",18 +4201505,"Armazém",23 +2301505,"Arneiroz",6 +2200905,"Aroazes",17 +2501302,"Aroeiras",14 +2200954,"Aroeiras do Itaim",17 +2201002,"Arraial",17 +3300258,"Arraial do Cabo",18 +1702406,"Arraias",26 +4301008,"Arroio do Meio",20 +4301073,"Arroio do Padre",20 +4301057,"Arroio do Sal",20 +4301206,"Arroio do Tigre",20 +4301107,"Arroio dos Ratos",20 +4301305,"Arroio Grande",20 +4201604,"Arroio Trinta",23 +3503802,"Artur Nogueira",24 +5202502,"Aruanã",8 +3503901,"Arujá",24 +4201653,"Arvoredo",23 +4301404,"Arvorezinha",20 +4201703,"Ascurra",23 +3503950,"Aspásia",24 +4101903,"Assaí",15 +2301604,"Assaré",6 +3504008,"Assis",24 +4102000,"Assis Chateaubriand",15 +2501351,"Assunção",14 +2201051,"Assunção do Piauí",17 +3104601,"Astolfo Dutra",12 +4102109,"Astorga",15 +4102208,"Atalaia",15 +2700409,"Atalaia",2 +1300201,"Atalaia do Norte",4 +4201802,"Atalanta",23 +3104700,"Ataléia",12 +3504107,"Atibaia",24 +3200706,"Atilio Vivacqua",7 +1702554,"Augustinópolis",26 +1500909,"Augusto Corrêa",13 +3104809,"Augusto de Lima",12 +4301503,"Augusto Pestana",20 +2401305,"Augusto Severo (Campo Grande)",19 +4301552,"Áurea",20 +2902401,"Aurelino Leal",5 +3504206,"Auriflama",24 +5202601,"Aurilândia",8 +2301703,"Aurora",6 +4201901,"Aurora",23 +1500958,"Aurora do Pará",13 +1702703,"Aurora do Tocantins",26 +1300300,"Autazes",4 +3504305,"Avaí",24 +3504404,"Avanhandava",24 +3504503,"Avaré",24 +1501006,"Aveiro",13 +2201101,"Avelino Lopes",17 +5202809,"Avelinópolis",8 +2101103,"Axixá",9 +1702901,"Axixá do Tocantins",26 +1703008,"Babaçulândia",26 +2101202,"Bacabal",9 +2101251,"Bacabeira",9 +2101301,"Bacuri",9 +2101350,"Bacurituba",9 +3504602,"Bady Bassitt",24 +3104908,"Baependi",12 +4301602,"Bagé",20 +1501105,"Bagre",13 +2501401,"Baía da Traição",14 +2401404,"Baía Formosa",19 +2902500,"Baianópolis",5 +1501204,"Baião",13 +2902609,"Baixa Grande",5 +2201150,"Baixa Grande do Ribeiro",17 +2301802,"Baixio",6 +3200805,"Baixo Guandu",7 +3504701,"Balbinos",24 +3105004,"Baldim",12 +5203104,"Baliza",8 +4201950,"Balneário Arroio do Silva",23 +4202057,"Balneário Barra do Sul",23 +4202008,"Balneário Camboriú",23 +4202073,"Balneário Gaivota",23 +4212809,"Balneário Piçarras",23 +4301636,"Balneário Pinhal",20 +4220000,"Balneário Rincão",23 +4102307,"Balsa Nova",15 +3504800,"Bálsamo",24 +2101400,"Balsas",9 +3105103,"Bambuí",12 +1100023,"Ariquemes",21 +2301851,"Banabuiú",6 +3504909,"Bananal",24 +2501500,"Bananeiras",14 +3105202,"Bandeira",12 +3105301,"Bandeira do Sul",12 +4202081,"Bandeirante",23 +5001508,"Bandeirantes",11 +4102406,"Bandeirantes",15 +1703057,"Bandeirantes do Tocantins",26 +1501253,"Bannach",13 +2902658,"Banzaê",5 +4301651,"Barão",20 +3505005,"Barão de Antonina",24 +3105400,"Barão de Cocais",12 +4301701,"Barão de Cotegipe",20 +2101509,"Barão de Grajaú",9 +5101605,"Barão de Melgaço",10 +3105509,"Barão de Monte Alto",12 +4301750,"Barão do Triunfo",20 +2401453,"Baraúna",19 +2501534,"Baraúna",14 +3105608,"Barbacena",12 +2301901,"Barbalha",6 +3505104,"Barbosa",24 +4102505,"Barbosa Ferraz",15 +1501303,"Barcarena",13 +2401503,"Barcelona",19 +1300409,"Barcelos",4 +3505203,"Bariri",24 +2902708,"Barra",5 +4202099,"Barra Bonita",23 +3505302,"Barra Bonita",24 +2201176,"Barra D'Alcântara",17 +2902807,"Barra da Estiva",5 +2601300,"Barra de Guabiraba",16 +2501609,"Barra de Santa Rosa",14 +2501575,"Barra de Santana",14 +2700508,"Barra de Santo Antônio",2 +3200904,"Barra de São Francisco",7 +2501708,"Barra de São Miguel",14 +2700607,"Barra de São Miguel",2 +5101704,"Barra do Bugres",10 +3505351,"Barra do Chapéu",24 +2902906,"Barra do Choça",5 +2101608,"Barra do Corda",9 +5101803,"Barra do Garças",10 +4301859,"Barra do Guarita",20 +4102703,"Barra do Jacaré",15 +2903003,"Barra do Mendes",5 +1703073,"Barra do Ouro",26 +3300308,"Barra do Piraí",18 +4301875,"Barra do Quaraí",20 +4301909,"Barra do Ribeiro",20 +4301925,"Barra do Rio Azul",20 +2903102,"Barra do Rocha",5 +3505401,"Barra do Turvo",24 +2800605,"Barra dos Coqueiros",25 +4301958,"Barra Funda",20 +3105707,"Barra Longa",12 +3300407,"Barra Mansa",18 +4202107,"Barra Velha",23 +4301800,"Barracão",20 +4102604,"Barracão",15 +2201200,"Barras",17 +2301950,"Barreira",6 +2903201,"Barreiras",5 +2201309,"Barreiras do Piauí",17 +1300508,"Barreirinha",4 +2101707,"Barreirinhas",9 +2601409,"Barreiros",16 +3505500,"Barretos",24 +3505609,"Barrinha",24 +2302008,"Barro",6 +2903235,"Barro Alto",5 +5203203,"Barro Alto",8 +2201408,"Barro Duro",17 +2903300,"Barro Preto",5 +2903276,"Barrocas",5 +1703107,"Barrolândia",26 +2302057,"Barroquinha",6 +4302006,"Barros Cassal",20 +3105905,"Barroso",12 +3505708,"Barueri",24 +3505807,"Bastos",24 +5001904,"Bataguassu",11 +2201507,"Batalha",17 +2700706,"Batalha",2 +3505906,"Batatais",24 +5002001,"Batayporã",11 +2302107,"Baturité",6 +3506003,"Bauru",24 +2501807,"Bayeux",14 +3506102,"Bebedouro",24 +2302206,"Beberibe",6 +2302305,"Bela Cruz",6 +5002100,"Bela Vista",11 +4102752,"Bela Vista da Caroba",15 +5203302,"Bela Vista de Goiás",8 +3106002,"Bela Vista de Minas",12 +2101772,"Bela Vista do Maranhão",9 +4102802,"Bela Vista do Paraíso",15 +2201556,"Bela Vista do Piauí",17 +4202131,"Bela Vista do Toldo",23 +2101731,"Belágua",9 +2501906,"Belém",14 +2700805,"Belém",2 +2601508,"Belém de Maria",16 +2502003,"Belém do Brejo do Cruz",14 +2201572,"Belém do Piauí",17 +2601607,"Belém do São Francisco",16 +3300456,"Belford Roxo",18 +3106101,"Belmiro Braga",12 +4202156,"Belmonte",23 +2903409,"Belmonte",5 +2903508,"Belo Campo",5 +2601706,"Belo Jardim",16 +2700904,"Belo Monte",2 +3106309,"Belo Oriente",12 +3106408,"Belo Vale",12 +1501451,"Belterra",13 +2201606,"Beneditinos",17 +2101806,"Benedito Leite",9 +4202206,"Benedito Novo",23 +1501501,"Benevides",13 +1300607,"Benjamin Constant",4 +4302055,"Benjamin Constant do Sul",20 +3506201,"Bento de Abreu",24 +2401602,"Bento Fernandes",19 +4302105,"Bento Gonçalves",20 +2101905,"Bequimão",9 +3106507,"Berilo",12 +3106655,"Berizal",12 +2502052,"Bernardino Batista",14 +3506300,"Bernardino de Campos",24 +2101939,"Bernardo do Mearim",9 +1703206,"Bernardo Sayão",26 +3506359,"Bertioga",24 +2201705,"Bertolínia",17 +3106606,"Bertópolis",12 +1300631,"Beruri",4 +2601805,"Betânia",16 +2201739,"Betânia do Piauí",17 +3106705,"Betim",12 +2601904,"Bezerros",16 +3106804,"Bias Fortes",12 +3106903,"Bicas",12 +4202305,"Biguaçu",23 +3506409,"Bilac",24 +3107000,"Biquinhas",12 +3506508,"Birigui",24 +3506607,"Biritiba-Mirim",24 +2903607,"Biritinga",5 +1501402,"Belém",13 +4102901,"Bituruna",15 +4202404,"Blumenau",23 +4103008,"Boa Esperança",15 +3107109,"Boa Esperança",12 +3201001,"Boa Esperança",7 +4103024,"Boa Esperança do Iguaçu",15 +3506706,"Boa Esperança do Sul",24 +2201770,"Boa Hora",17 +2903706,"Boa Nova",5 +2502102,"Boa Ventura",14 +4103040,"Boa Ventura de São Roque",15 +2302404,"Boa Viagem",6 +2502151,"Boa Vista",14 +4103057,"Boa Vista da Aparecida",15 +4302154,"Boa Vista das Missões",20 +4302204,"Boa Vista do Buricá",20 +4302220,"Boa Vista do Cadeado",20 +2101970,"Boa Vista do Gurupi",9 +4302238,"Boa Vista do Incra",20 +1300680,"Boa Vista do Ramos",4 +4302253,"Boa Vista do Sul",20 +2903805,"Boa Vista do Tupim",5 +2701001,"Boca da Mata",2 +1300706,"Boca do Acre",4 +2201804,"Bocaina",17 +3506805,"Bocaina",24 +3107208,"Bocaina de Minas",12 +4202438,"Bocaina do Sul",23 +3107307,"Bocaiúva",12 +4103107,"Bocaiúva do Sul",15 +2401651,"Bodó",19 +2602001,"Bodocó",16 +5002159,"Bodoquena",11 +3506904,"Bofete",24 +3507001,"Boituva",24 +2602100,"Bom Conselho",16 +3107406,"Bom Despacho",12 +3300506,"Bom Jardim",18 +2602209,"Bom Jardim",16 +2102002,"Bom Jardim",9 +4202503,"Bom Jardim da Serra",23 +5203401,"Bom Jardim de Goiás",8 +3107505,"Bom Jardim de Minas",12 +4202537,"Bom Jesus",23 +4302303,"Bom Jesus",20 +2201903,"Bom Jesus",17 +2401701,"Bom Jesus",19 +2502201,"Bom Jesus",14 +2903904,"Bom Jesus da Lapa",5 +3107604,"Bom Jesus da Penha",12 +2903953,"Bom Jesus da Serra",5 +2102036,"Bom Jesus das Selvas",9 +5203500,"Bom Jesus de Goiás",8 +3107703,"Bom Jesus do Amparo",12 +5101852,"Bom Jesus do Araguaia",10 +3107802,"Bom Jesus do Galho",12 +3300605,"Bom Jesus do Itabapoana",18 +3201100,"Bom Jesus do Norte",7 +4202578,"Bom Jesus do Oeste",23 +4103156,"Bom Jesus do Sul",15 +1501576,"Bom Jesus do Tocantins",13 +1703305,"Bom Jesus do Tocantins",26 +3507100,"Bom Jesus dos Perdões",24 +2102077,"Bom Lugar",9 +4302352,"Bom Princípio",20 +2201919,"Bom Princípio do Piauí",17 +4302378,"Bom Progresso",20 +3107901,"Bom Repouso",12 +4202602,"Bom Retiro",23 +4302402,"Bom Retiro do Sul",20 +3108008,"Bom Sucesso",12 +4103206,"Bom Sucesso",15 +2502300,"Bom Sucesso",14 +3507159,"Bom Sucesso de Itararé",24 +4103222,"Bom Sucesso do Sul",15 +4202453,"Bombinhas",23 +1400159,"Bonfim",22 +3108107,"Bonfim",12 +2201929,"Bonfim do Piauí",17 +5203559,"Bonfinópolis",8 +3108206,"Bonfinópolis de Minas",12 +2904001,"Boninal",5 +2602308,"Bonito",16 +2904050,"Bonito",5 +1501600,"Bonito",13 +5002209,"Bonito",11 +3108255,"Bonito de Minas",12 +2502409,"Bonito de Santa Fé",14 +5203575,"Bonópolis",8 +2502508,"Boqueirão",14 +4302451,"Boqueirão do Leão",20 +2201945,"Boqueirão do Piauí",17 +2800670,"Boquim",25 +2904100,"Boquira",5 +3507209,"Borá",24 +3507308,"Boracéia",24 +1300805,"Borba",4 +2502706,"Borborema",14 +3507407,"Borborema",24 +3108305,"Borda da Mata",12 +3507456,"Borebi",24 +4103305,"Borrazópolis",15 +4302501,"Bossoroca",20 +3108404,"Botelhos",12 +3507506,"Botucatu",24 +3108503,"Botumirim",12 +2904209,"Botuporã",5 +4202701,"Botuverá",23 +4302584,"Bozano",20 +4202800,"Braço do Norte",23 +4202859,"Braço do Trombudo",23 +4302600,"Braga",20 +1501709,"Bragança",13 +3507605,"Bragança Paulista",24 +4103354,"Braganey",15 +2701100,"Branquinha",2 +3108701,"Brás Pires",12 +1501725,"Brasil Novo",13 +5002308,"Brasilândia",11 +3108552,"Brasilândia de Minas",12 +4103370,"Brasilândia do Sul",15 +1703602,"Brasilândia do Tocantins",26 +2201960,"Brasileira",17 +5300108,"Brasília",27 +3108602,"Brasília de Minas",12 +5101902,"Brasnorte",10 +3507704,"Braúna",24 +3108800,"Braúnas",12 +5203609,"Brazabrantes",8 +3108909,"Brazópolis",12 +2602407,"Brejão",16 +3201159,"Brejetuba",7 +2401800,"Brejinho",19 +2602506,"Brejinho",16 +1703701,"Brejinho de Nazaré",26 +2102101,"Brejo",9 +3507753,"Brejo Alegre",24 +2602605,"Brejo da Madre de Deus",16 +2102150,"Brejo de Areia",9 +2502805,"Brejo do Cruz",14 +2201988,"Brejo do Piauí",17 +2502904,"Brejo dos Santos",14 +2800704,"Brejo Grande",25 +1501758,"Brejo Grande do Araguaia",13 +2302503,"Brejo Santo",6 +2904308,"Brejões",5 +2904407,"Brejolândia",5 +1501782,"Breu Branco",13 +1501808,"Breves",13 +5203807,"Britânia",8 +1400100,"Boa Vista",22 +4302659,"Brochier",20 +3507803,"Brodowski",24 +3507902,"Brotas",24 +2904506,"Brotas de Macaúbas",5 +3109006,"Brumadinho",12 +2904605,"Brumado",5 +4202875,"Brunópolis",23 +4202909,"Brusque",23 +3109105,"Bueno Brandão",12 +3109204,"Buenópolis",12 +2602704,"Buenos Aires",16 +2904704,"Buerarema",5 +3109253,"Bugre",12 +2602803,"Buíque",16 +1501907,"Bujaru",13 +3508009,"Buri",24 +3508108,"Buritama",24 +2102200,"Buriti",9 +5203906,"Buriti Alegre",8 +2102309,"Buriti Bravo",9 +5203939,"Buriti de Goiás",8 +1703800,"Buriti do Tocantins",26 +2202000,"Buriti dos Lopes",17 +2202026,"Buriti dos Montes",17 +2102325,"Buriticupu",9 +5203962,"Buritinópolis",8 +2904753,"Buritirama",5 +2102358,"Buritirana",9 +3109303,"Buritis",12 +3508207,"Buritizal",24 +3109402,"Buritizeiro",12 +4302709,"Butiá",20 +1300839,"Caapiranga",4 +2503001,"Caaporã",14 +5002407,"Caarapó",11 +2904803,"Caatiba",5 +2503100,"Cabaceiras",14 +2904852,"Cabaceiras do Paraguaçu",5 +3109451,"Cabeceira Grande",12 +5204003,"Cabeceiras",8 +2202059,"Cabeceiras do Piauí",17 +2503209,"Cabedelo",14 +2602902,"Cabo de Santo Agostinho",16 +3300704,"Cabo Frio",18 +3109501,"Cabo Verde",12 +3508306,"Cabrália Paulista",24 +3508405,"Cabreúva",24 +2603009,"Cabrobó",16 +4203006,"Caçador",23 +3508504,"Caçapava",24 +4302808,"Caçapava do Sul",20 +4302907,"Cacequi",20 +5102504,"Cáceres",10 +2904902,"Cachoeira",5 +5204102,"Cachoeira Alta",8 +3109600,"Cachoeira da Prata",12 +5204201,"Cachoeira de Goiás",8 +3109709,"Cachoeira de Minas",12 +3102704,"Cachoeira de Pajeú",12 +1502004,"Cachoeira do Arari",13 +1501956,"Cachoeira do Piriá",13 +4303004,"Cachoeira do Sul",20 +2503308,"Cachoeira dos Índios",14 +5204250,"Cachoeira Dourada",8 +3109808,"Cachoeira Dourada",12 +2102374,"Cachoeira Grande",9 +3508603,"Cachoeira Paulista",24 +3300803,"Cachoeiras de Macacu",18 +1703826,"Cachoeirinha",26 +2603108,"Cachoeirinha",16 +4303103,"Cachoeirinha",20 +3201209,"Cachoeiro de Itapemirim",7 +2503407,"Cacimba de Areia",14 +2503506,"Cacimba de Dentro",14 +2503555,"Cacimbas",14 +2701209,"Cacimbinhas",2 +4303202,"Cacique Doble",20 +3508702,"Caconde",24 +5204300,"Caçu",8 +2905008,"Caculé",5 +2905107,"Caém",5 +3109907,"Caetanópolis",12 +2905156,"Caetanos",5 +3110004,"Caeté",12 +2603207,"Caetés",16 +2905206,"Caetité",5 +2905305,"Cafarnaum",5 +4103404,"Cafeara",15 +3508801,"Cafelândia",24 +4103453,"Cafelândia",15 +4103479,"Cafezal do Sul",15 +3508900,"Caiabu",24 +3110103,"Caiana",12 +5204409,"Caiapônia",8 +4303301,"Caibaté",20 +4203105,"Caibi",23 +4303400,"Caiçara",20 +2503605,"Caiçara",14 +2401859,"Caiçara do Norte",19 +2401909,"Caiçara do Rio do Vento",19 +2402006,"Caicó",19 +3509007,"Caieiras",24 +2905404,"Cairu",5 +3509106,"Caiuá",24 +3509205,"Cajamar",24 +2102408,"Cajapió",9 +2102507,"Cajari",9 +3509254,"Cajati",24 +2503704,"Cajazeiras",14 +2202075,"Cajazeiras do Piauí",17 +2503753,"Cajazeirinhas",14 +3509304,"Cajobi",24 +2701308,"Cajueiro",2 +2202083,"Cajueiro da Praia",17 +3110202,"Cajuri",12 +3509403,"Cajuru",24 +2603306,"Calçado",16 +1600204,"Calçoene",3 +3110301,"Caldas",12 +2503803,"Caldas Brandão",14 +5204508,"Caldas Novas",8 +5204557,"Caldazinha",8 +2905503,"Caldeirão Grande",5 +2202091,"Caldeirão Grande do Piauí",17 +4103503,"Califórnia",15 +4203154,"Calmon",23 +2603405,"Calumbi",16 +2905602,"Camacan",5 +2905701,"Camaçari",5 +3110400,"Camacho",12 +2503902,"Camalaú",14 +2905800,"Camamu",5 +3110509,"Camanducaia",12 +5002605,"Camapuã",11 +4303509,"Camaquã",20 +2603454,"Camaragibe",16 +4303558,"Camargo",20 +4103602,"Cambará",15 +4303608,"Cambará do Sul",20 +4103701,"Cambé",15 +4103800,"Cambira",15 +4203204,"Camboriú",23 +3300902,"Cambuci",18 +3110608,"Cambuí",12 +3110707,"Cambuquira",12 +1502103,"Cametá",13 +2302602,"Camocim",6 +2603504,"Camocim de São Félix",16 +3110806,"Campanário",12 +3110905,"Campanha",12 +3111002,"Campestre",12 +2701357,"Campestre",2 +4303673,"Campestre da Serra",20 +1100452,"Buritis",21 +1100031,"Cabixi",21 +1100601,"Cacaulândia",21 +1100049,"Cacoal",21 +5204607,"Campestre de Goiás",8 +2102556,"Campestre do Maranhão",9 +4103909,"Campina da Lagoa",15 +4303707,"Campina das Missões",20 +3509452,"Campina do Monte Alegre",24 +4103958,"Campina do Simão",15 +2504009,"Campina Grande",14 +4104006,"Campina Grande do Sul",15 +3111101,"Campina Verde",12 +5204656,"Campinaçu",8 +5102603,"Campinápolis",10 +3509502,"Campinas",24 +2202109,"Campinas do Piauí",17 +4303806,"Campinas do Sul",20 +5204706,"Campinorte",8 +4203303,"Campo Alegre",23 +2701407,"Campo Alegre",2 +5204805,"Campo Alegre de Goiás",8 +2905909,"Campo Alegre de Lourdes",5 +2202117,"Campo Alegre do Fidalgo",17 +3111150,"Campo Azul",12 +3111200,"Campo Belo",12 +4203402,"Campo Belo do Sul",23 +4303905,"Campo Bom",20 +4104055,"Campo Bonito",15 +2801009,"Campo do Brito",25 +3111309,"Campo do Meio",12 +4104105,"Campo do Tenente",15 +4203501,"Campo Erê",23 +3111408,"Campo Florido",12 +2906006,"Campo Formoso",5 +2701506,"Campo Grande",2 +2202133,"Campo Grande do Piauí",17 +4104204,"Campo Largo",15 +2202174,"Campo Largo do Piauí",17 +5204854,"Campo Limpo de Goiás",8 +3509601,"Campo Limpo Paulista",24 +4104253,"Campo Magro",15 +2202208,"Campo Maior",17 +4104303,"Campo Mourão",15 +4304002,"Campo Novo",20 +5102637,"Campo Novo do Parecis",10 +2402105,"Campo Redondo",19 +5102678,"Campo Verde",10 +3111507,"Campos Altos",12 +5204904,"Campos Belos",8 +4304101,"Campos Borges",20 +5102686,"Campos de Júlio",10 +3509700,"Campos do Jordão",24 +3301009,"Campos dos Goytacazes",18 +3111606,"Campos Gerais",12 +1703842,"Campos Lindos",26 +4203600,"Campos Novos",23 +3509809,"Campos Novos Paulista",24 +2302701,"Campos Sales",6 +5204953,"Campos Verdes",8 +2603603,"Camutanga",16 +3111903,"Cana Verde",12 +3111705,"Canaã",12 +1502152,"Canaã dos Carajás",13 +5102694,"Canabrava do Norte",10 +3509908,"Cananéia",24 +2701605,"Canapi",2 +2906105,"Canápolis",5 +3111804,"Canápolis",12 +2906204,"Canarana",5 +5102702,"Canarana",10 +3509957,"Canas",24 +2202251,"Canavieira",17 +2906303,"Canavieiras",5 +2906402,"Candeal",5 +2906501,"Candeias",5 +3112000,"Candeias",12 +4304200,"Candelária",20 +2906600,"Candiba",5 +4104402,"Cândido de Abreu",15 +4304309,"Cândido Godói",20 +2102606,"Cândido Mendes",9 +3510005,"Cândido Mota",24 +3510104,"Cândido Rodrigues",24 +2906709,"Cândido Sales",5 +4304358,"Candiota",20 +4104428,"Candói",15 +4304408,"Canela",20 +4203709,"Canelinha",23 +2402204,"Canguaretama",19 +4304507,"Canguçu",20 +2801108,"Canhoba",25 +2603702,"Canhotinho",16 +2302800,"Canindé",6 +2801207,"Canindé de São Francisco",25 +3510153,"Canitar",24 +4304606,"Canoas",20 +4203808,"Canoinhas",23 +2906808,"Cansanção",5 +1400175,"Cantá",22 +3301108,"Cantagalo",18 +4104451,"Cantagalo",15 +3112059,"Cantagalo",12 +2102705,"Cantanhede",9 +2202307,"Canto do Buriti",17 +2906824,"Canudos",5 +4304614,"Canudos do Vale",20 +1300904,"Canutama",4 +1502202,"Capanema",13 +4104501,"Capanema",15 +4203253,"Capão Alto",23 +3510203,"Capão Bonito",24 +4304622,"Capão Bonito do Sul",20 +4304630,"Capão da Canoa",20 +4304655,"Capão do Cipó",20 +4304663,"Capão do Leão",20 +3112109,"Caparaó",12 +2701704,"Capela",2 +2801306,"Capela",25 +4304689,"Capela de Santana",20 +3510302,"Capela do Alto",24 +2906857,"Capela do Alto Alegre",5 +3112208,"Capela Nova",12 +3112307,"Capelinha",12 +3112406,"Capetinga",12 +2504033,"Capim",14 +3112505,"Capim Branco",12 +2906873,"Capim Grosso",5 +3112604,"Capinópolis",12 +4203907,"Capinzal",23 +2102754,"Capinzal do Norte",9 +2302909,"Capistrano",6 +4304697,"Capitão",20 +3112653,"Capitão Andrade",12 +2202406,"Capitão de Campos",17 +3112703,"Capitão Enéas",12 +2202455,"Capitão Gervásio Oliveira",17 +4104600,"Capitão Leônidas Marques",15 +1502301,"Capitão Poço",13 +3112802,"Capitólio",12 +3510401,"Capivari",24 +4203956,"Capivari de Baixo",23 +4304671,"Capivari do Sul",20 +2603801,"Capoeiras",16 +3112901,"Caputira",12 +4304713,"Caraá",20 +1400209,"Caracaraí",22 +2202505,"Caracol",17 +5002803,"Caracol",11 +3510500,"Caraguatatuba",24 +3113008,"Caraí",12 +2906899,"Caraíbas",5 +4104659,"Carambeí",15 +1100700,"Campo Novo de Rondônia",21 +1100809,"Candeias do Jamari",21 +5002704,"Campo Grande",11 +3113107,"Caranaíba",12 +3113206,"Carandaí",12 +3113305,"Carangola",12 +3300936,"Carapebus",18 +3510609,"Carapicuíba",24 +3113404,"Caratinga",12 +1301001,"Carauari",4 +2402303,"Caraúbas",19 +2504074,"Caraúbas",14 +2202539,"Caraúbas do Piauí",17 +2906907,"Caravelas",5 +4304705,"Carazinho",20 +3113503,"Carbonita",12 +2907004,"Cardeal da Silva",5 +3510708,"Cardoso",24 +3301157,"Cardoso Moreira",18 +3113602,"Careaçu",12 +1301100,"Careiro",4 +1301159,"Careiro da Várzea",4 +3201308,"Cariacica",7 +2303006,"Caridade",6 +2202554,"Caridade do Piauí",17 +2907103,"Carinhanha",5 +2801405,"Carira",25 +2303105,"Cariré",6 +1703867,"Cariri do Tocantins",26 +2303204,"Caririaçu",6 +2303303,"Cariús",6 +5102793,"Carlinda",10 +4104709,"Carlópolis",15 +4304804,"Carlos Barbosa",20 +3113701,"Carlos Chagas",12 +4304853,"Carlos Gomes",20 +3113800,"Carmésia",12 +3301207,"Carmo",18 +3113909,"Carmo da Cachoeira",12 +3114006,"Carmo da Mata",12 +3114105,"Carmo de Minas",12 +3114204,"Carmo do Cajuru",12 +3114303,"Carmo do Paranaíba",12 +3114402,"Carmo do Rio Claro",12 +5205000,"Carmo do Rio Verde",8 +1703883,"Carmolândia",26 +2801504,"Carmópolis",25 +3114501,"Carmópolis de Minas",12 +2603900,"Carnaíba",16 +2402402,"Carnaúba dos Dantas",19 +2402501,"Carnaubais",19 +2303402,"Carnaubal",6 +2603926,"Carnaubeira da Penha",16 +3114550,"Carneirinho",12 +2701803,"Carneiros",2 +1400233,"Caroebe",22 +2102804,"Carolina",9 +2604007,"Carpina",16 +3114600,"Carrancas",12 +2504108,"Carrapateira",14 +1703891,"Carrasco Bonito",26 +2604106,"Caruaru",16 +2102903,"Carutapera",9 +3114709,"Carvalhópolis",12 +3114808,"Carvalhos",12 +3510807,"Casa Branca",24 +3114907,"Casa Grande",12 +2907202,"Casa Nova",5 +4304903,"Casca",20 +3115003,"Cascalho Rico",12 +4104808,"Cascavel",15 +2303501,"Cascavel",6 +1703909,"Caseara",26 +4304952,"Caseiros",20 +3301306,"Casimiro de Abreu",18 +2604155,"Casinhas",16 +2504157,"Casserengue",14 +3115102,"Cássia",12 +3510906,"Cássia dos Coqueiros",24 +5002902,"Cassilândia",11 +1502400,"Castanhal",13 +5102850,"Castanheira",10 +5205059,"Castelândia",8 +3201407,"Castelo",7 +2202604,"Castelo do Piauí",17 +3511003,"Castilho",24 +4104907,"Castro",15 +2907301,"Castro Alves",5 +3115300,"Cataguases",12 +5205109,"Catalão",8 +3511102,"Catanduva",24 +4105003,"Catanduvas",15 +4204004,"Catanduvas",23 +2303600,"Catarina",6 +3115359,"Catas Altas",12 +3115409,"Catas Altas da Noruega",12 +2604205,"Catende",16 +3511201,"Catiguá",24 +2504207,"Catingueira",14 +2907400,"Catolândia",5 +2504306,"Catolé do Rocha",14 +2907509,"Catu",5 +4305009,"Catuípe",20 +3115458,"Catuji",12 +2303659,"Catunda",6 +5205208,"Caturaí",8 +2907558,"Caturama",5 +2504355,"Caturité",14 +3115474,"Catuti",12 +2303709,"Caucaia",6 +5205307,"Cavalcante",8 +3115508,"Caxambu",12 +4204103,"Caxambu do Sul",23 +2103000,"Caxias",9 +4305108,"Caxias do Sul",20 +2202653,"Caxingó",17 +2402600,"Ceará-Mirim",19 +2103109,"Cedral",9 +3511300,"Cedral",24 +2303808,"Cedro",6 +2604304,"Cedro",16 +2801603,"Cedro de São João",25 +3115607,"Cedro do Abaeté",12 +4204152,"Celso Ramos",23 +4305116,"Centenário",20 +1704105,"Centenário",26 +4105102,"Centenário do Sul",15 +2907608,"Central",5 +3115706,"Central de Minas",12 +2103125,"Central do Maranhão",9 +3115805,"Centralina",12 +2103158,"Centro do Guilherme",9 +2103174,"Centro Novo do Maranhão",9 +5205406,"Ceres",8 +3511409,"Cerqueira César",24 +3511508,"Cerquilho",24 +4305124,"Cerrito",20 +4105201,"Cerro Azul",15 +4305132,"Cerro Branco",20 +2402709,"Cerro Corá",19 +4305157,"Cerro Grande",20 +4305173,"Cerro Grande do Sul",20 +4305207,"Cerro Largo",20 +4204178,"Cerro Negro",23 +3511607,"Cesário Lange",24 +4105300,"Céu Azul",15 +5205455,"Cezarina",8 +2604403,"Chã de Alegria",16 +2604502,"Chã Grande",16 +2701902,"Chã Preta",2 +3115904,"Chácara",12 +3116001,"Chalé",12 +4305306,"Chapada",20 +1705102,"Chapada da Natividade",26 +1704600,"Chapada de Areia",26 +3116100,"Chapada do Norte",12 +5103007,"Chapada dos Guimarães",10 +3116159,"Chapada Gaúcha",12 +1100056,"Cerejeiras",21 +5205471,"Chapadão do Céu",8 +4204194,"Chapadão do Lageado",23 +5002951,"Chapadão do Sul",11 +2103208,"Chapadinha",9 +4204202,"Chapecó",23 +3511706,"Charqueada",24 +4305355,"Charqueadas",20 +4305371,"Charrua",20 +2303907,"Chaval",6 +3557204,"Chavantes",24 +1502509,"Chaves",13 +3116209,"Chiador",12 +4305405,"Chiapetta",20 +4105409,"Chopinzinho",15 +2303931,"Choró",6 +2303956,"Chorozinho",6 +2907707,"Chorrochó",5 +4305439,"Chuí",20 +4305447,"Chuvisca",20 +4105508,"Cianorte",15 +2907806,"Cícero Dantas",5 +4105607,"Cidade Gaúcha",15 +5205497,"Cidade Ocidental",8 +2103257,"Cidelândia",9 +4305454,"Cidreira",20 +2907905,"Cipó",5 +3116308,"Cipotânea",12 +4305504,"Ciríaco",20 +3116407,"Claraval",12 +3116506,"Claro dos Poções",12 +5103056,"Cláudia",10 +3116605,"Cláudio",12 +3511904,"Clementina",24 +4105706,"Clevelândia",15 +2908002,"Coaraci",5 +1301209,"Coari",4 +2202703,"Cocal",17 +2202711,"Cocal de Telha",17 +4204251,"Cocal do Sul",23 +2202729,"Cocal dos Alves",17 +5103106,"Cocalinho",10 +5205513,"Cocalzinho de Goiás",8 +2908101,"Cocos",5 +1301308,"Codajás",4 +2103307,"Codó",9 +2103406,"Coelho Neto",9 +3116704,"Coimbra",12 +2702009,"Coité do Nóia",2 +2202737,"Coivaras",17 +1502608,"Colares",13 +3201506,"Colatina",7 +5103205,"Colíder",10 +3512001,"Colina",24 +4305587,"Colinas",20 +2103505,"Colinas",9 +5205521,"Colinas do Sul",8 +1705508,"Colinas do Tocantins",26 +1716703,"Colméia",26 +5103254,"Colniza",10 +3512100,"Colômbia",24 +4105805,"Colombo",15 +2202752,"Colônia do Gurguéia",17 +2202778,"Colônia do Piauí",17 +2702108,"Colônia Leopoldina",2 +4305603,"Colorado",20 +4105904,"Colorado",15 +3116803,"Coluna",12 +1705557,"Combinado",26 +3116902,"Comendador Gomes",12 +3300951,"Comendador Levy Gasparian",18 +3117009,"Comercinho",12 +5103304,"Comodoro",10 +2504405,"Conceição",14 +3117108,"Conceição da Aparecida",12 +3201605,"Conceição da Barra",7 +3115201,"Conceição da Barra de Minas",12 +2908200,"Conceição da Feira",5 +3117306,"Conceição das Alagoas",12 +3117207,"Conceição das Pedras",12 +3117405,"Conceição de Ipanema",12 +3301405,"Conceição de Macabu",18 +2908309,"Conceição do Almeida",5 +1502707,"Conceição do Araguaia",13 +2202802,"Conceição do Canindé",17 +3201704,"Conceição do Castelo",7 +2908408,"Conceição do Coité",5 +2908507,"Conceição do Jacuípe",5 +2103554,"Conceição do Lago-Açu",9 +3117504,"Conceição do Mato Dentro",12 +3117603,"Conceição do Pará",12 +3117702,"Conceição do Rio Verde",12 +1705607,"Conceição do Tocantins",26 +3117801,"Conceição dos Ouros",12 +3512209,"Conchal",24 +3512308,"Conchas",24 +4204301,"Concórdia",23 +1502756,"Concórdia do Pará",13 +2504504,"Condado",14 +2604601,"Condado",16 +2504603,"Conde",14 +2908606,"Conde",5 +2908705,"Condeúba",5 +4305702,"Condor",20 +3117836,"Cônego Marinho",12 +3117876,"Confins",12 +5103353,"Confresa",10 +2504702,"Congo",14 +3117900,"Congonhal",12 +3118007,"Congonhas",12 +3118106,"Congonhas do Norte",12 +4106001,"Congonhinhas",15 +3118205,"Conquista",12 +5103361,"Conquista D'Oeste",10 +3118304,"Conselheiro Lafaiete",12 +4106100,"Conselheiro Mairinck",15 +3118403,"Conselheiro Pena",12 +3118502,"Consolação",12 +4305801,"Constantina",20 +3118601,"Contagem",12 +4106209,"Contenda",15 +2908804,"Contendas do Sincorá",5 +3118700,"Coqueiral",12 +4305835,"Coqueiro Baixo",20 +2702207,"Coqueiro Seco",2 +4305850,"Coqueiros do Sul",20 +3118809,"Coração de Jesus",12 +2908903,"Coração de Maria",5 +4106308,"Corbélia",15 +3301504,"Cordeiro",18 +3512407,"Cordeirópolis",24 +2909000,"Cordeiros",5 +4204350,"Cordilheira Alta",23 +3118908,"Cordisburgo",12 +3119005,"Cordislândia",12 +2304004,"Coreaú",6 +2504801,"Coremas",14 +5003108,"Corguinho",11 +2909109,"Coribe",5 +3119104,"Corinto",12 +4106407,"Cornélio Procópio",15 +3119203,"Coroaci",12 +3512506,"Coroados",24 +2103604,"Coroatá",9 +3119302,"Coromandel",12 +4305871,"Coronel Barros",20 +4305900,"Coronel Bicaco",20 +4106456,"Coronel Domingos Soares",15 +2402808,"Coronel Ezequiel",19 +3119401,"Coronel Fabriciano",12 +4204400,"Coronel Freitas",23 +2402907,"Coronel João Pessoa",19 +1100064,"Colorado do Oeste",21 +2909208,"Coronel João Sá",5 +2202851,"Coronel José Dias",17 +3512605,"Coronel Macedo",24 +4204459,"Coronel Martins",23 +3119500,"Coronel Murta",12 +3119609,"Coronel Pacheco",12 +4305934,"Coronel Pilar",20 +5003157,"Coronel Sapucaia",11 +4106506,"Coronel Vivida",15 +3119708,"Coronel Xavier Chaves",12 +3119807,"Córrego Danta",12 +3119906,"Córrego do Bom Jesus",12 +5205703,"Córrego do Ouro",8 +3119955,"Córrego Fundo",12 +3120003,"Córrego Novo",12 +4204558,"Correia Pinto",23 +2202901,"Corrente",17 +2604700,"Correntes",16 +2909307,"Correntina",5 +2604809,"Cortês",16 +5003207,"Corumbá",11 +5205802,"Corumbá de Goiás",8 +5205901,"Corumbaíba",8 +3512704,"Corumbataí",24 +4106555,"Corumbataí do Sul",15 +4204509,"Corupá",23 +2702306,"Coruripe",2 +3512803,"Cosmópolis",24 +3512902,"Cosmorama",24 +5003256,"Costa Rica",11 +2909406,"Cotegipe",5 +3513009,"Cotia",24 +4305959,"Cotiporã",20 +5103379,"Cotriguaçu",10 +3120102,"Couto de Magalhães de Minas",12 +1706001,"Couto Magalhães",26 +4305975,"Coxilha",20 +5003306,"Coxim",11 +2504850,"Coxixola",14 +2702355,"Craíbas",2 +2304103,"Crateús",6 +2304202,"Crato",6 +3513108,"Cravinhos",24 +2909505,"Cravolândia",5 +4204608,"Criciúma",23 +3120151,"Crisólita",12 +2909604,"Crisópolis",5 +4306007,"Crissiumal",20 +3120201,"Cristais",12 +3513207,"Cristais Paulista",24 +4306056,"Cristal",20 +4306072,"Cristal do Sul",20 +1706100,"Cristalândia",26 +2203008,"Cristalândia do Piauí",17 +3120300,"Cristália",12 +5206206,"Cristalina",8 +3120409,"Cristiano Otoni",12 +5206305,"Cristianópolis",8 +3120508,"Cristina",12 +2801702,"Cristinápolis",25 +2203107,"Cristino Castro",17 +2909703,"Cristópolis",5 +5206404,"Crixás",8 +1706258,"Crixás do Tocantins",26 +2304236,"Croatá",6 +5206503,"Cromínia",8 +3120607,"Crucilândia",12 +2304251,"Cruz",6 +4306106,"Cruz Alta",20 +2909802,"Cruz das Almas",5 +2504900,"Cruz do Espírito Santo",14 +4106803,"Cruz Machado",15 +3513306,"Cruzália",24 +4306130,"Cruzaltense",20 +3513405,"Cruzeiro",24 +3120706,"Cruzeiro da Fortaleza",12 +4106571,"Cruzeiro do Iguaçu",15 +4106605,"Cruzeiro do Oeste",15 +4106704,"Cruzeiro do Sul",15 +4306205,"Cruzeiro do Sul",20 +2403004,"Cruzeta",19 +3120805,"Cruzília",12 +4106852,"Cruzmaltina",15 +3513504,"Cubatão",24 +2505006,"Cubati",14 +2505105,"Cuité",14 +2505238,"Cuité de Mamanguape",14 +2505204,"Cuitegi",14 +5206602,"Cumari",8 +2604908,"Cumaru",16 +1502764,"Cumaru do Norte",13 +2801900,"Cumbe",25 +3513603,"Cunha",24 +4204707,"Cunha Porã",23 +4204756,"Cunhataí",23 +3120839,"Cuparaque",12 +2605004,"Cupira",16 +2909901,"Curaçá",5 +2203206,"Curimatá",17 +1502772,"Curionópolis",13 +4204806,"Curitibanos",23 +4107009,"Curiúva",15 +2203230,"Currais",17 +2403103,"Currais Novos",19 +2505279,"Curral de Cima",14 +3120870,"Curral de Dentro",12 +2203271,"Curral Novo do Piauí",17 +2505303,"Curral Velho",14 +1502806,"Curralinho",13 +2203255,"Curralinhos",17 +1502855,"Curuá",13 +1502905,"Curuçá",13 +2103703,"Cururupu",9 +5103437,"Curvelândia",10 +3120904,"Curvelo",12 +2605103,"Custódia",16 +1600212,"Cutias",3 +5206701,"Damianópolis",8 +2505352,"Damião",14 +5206800,"Damolândia",8 +1706506,"Darcinópolis",26 +2910008,"Dário Meira",5 +3121001,"Datas",12 +4306304,"David Canabarro",20 +2103752,"Davinópolis",9 +5206909,"Davinópolis",8 +3121100,"Delfim Moreira",12 +3121209,"Delfinópolis",12 +2702405,"Delmiro Gouveia",2 +3121258,"Delta",12 +2203305,"Demerval Lobão",17 +5103452,"Denise",10 +5003454,"Deodápolis",11 +2304269,"Deputado Irapuan Pinheiro",6 +4306320,"Derrubadas",20 +3513702,"Descalvado",24 +4204905,"Descanso",23 +3121308,"Descoberto",12 +2505402,"Desterro",14 +3121407,"Desterro de Entre Rios",12 +3121506,"Desterro do Melo",12 +4306353,"Dezesseis de Novembro",20 +3513801,"Diadema",24 +2505600,"Diamante",14 +4107157,"Diamante D'Oeste",15 +4107108,"Diamante do Norte",15 +4107124,"Diamante do Sul",15 +3121605,"Diamantina",12 +5103502,"Diamantino",10 +1707009,"Dianópolis",26 +2910057,"Dias d'Ávila",5 +4106902,"Curitiba",15 +5103403,"Cuiabá",10 +1100072,"Corumbiara",21 +1100080,"Costa Marques",21 +1100940,"Cujubim",21 +4306379,"Dilermando de Aguiar",20 +3121704,"Diogo de Vasconcelos",12 +3121803,"Dionísio",12 +4205001,"Dionísio Cerqueira",23 +5207105,"Diorama",8 +3513850,"Dirce Reis",24 +2203354,"Dirceu Arcoverde",17 +2802007,"Divina Pastora",25 +3121902,"Divinésia",12 +3122009,"Divino",12 +3122108,"Divino das Laranjeiras",12 +3201803,"Divino de São Lourenço",7 +3513900,"Divinolândia",24 +3122207,"Divinolândia de Minas",12 +3122306,"Divinópolis",12 +5208301,"Divinópolis de Goiás",8 +1707108,"Divinópolis do Tocantins",26 +3122355,"Divisa Alegre",12 +3122405,"Divisa Nova",12 +3122454,"Divisópolis",12 +3514007,"Dobrada",24 +3514106,"Dois Córregos",24 +4306403,"Dois Irmãos",20 +4306429,"Dois Irmãos das Missões",20 +5003488,"Dois Irmãos do Buriti",11 +1707207,"Dois Irmãos do Tocantins",26 +4306452,"Dois Lajeados",20 +2702504,"Dois Riachos",2 +4107207,"Dois Vizinhos",15 +3514205,"Dolcinópolis",24 +5103601,"Dom Aquino",10 +2910107,"Dom Basílio",5 +3122470,"Dom Bosco",12 +3122504,"Dom Cavati",12 +1502939,"Dom Eliseu",13 +2203404,"Dom Expedito Lopes",17 +4306502,"Dom Feliciano",20 +2203453,"Dom Inocêncio",17 +3122603,"Dom Joaquim",12 +2910206,"Dom Macedo Costa",5 +4306601,"Dom Pedrito",20 +2103802,"Dom Pedro",9 +4306551,"Dom Pedro de Alcântara",20 +3122702,"Dom Silvério",12 +3122801,"Dom Viçoso",12 +3201902,"Domingos Martins",7 +2203420,"Domingos Mourão",17 +4205100,"Dona Emma",23 +3122900,"Dona Eusébia",12 +4306700,"Dona Francisca",20 +2505709,"Dona Inês",14 +3123007,"Dores de Campos",12 +3123106,"Dores de Guanhães",12 +3123205,"Dores do Indaiá",12 +3202009,"Dores do Rio Preto",7 +3123304,"Dores do Turvo",12 +3123403,"Doresópolis",12 +2605152,"Dormentes",16 +5003504,"Douradina",11 +4107256,"Douradina",15 +3514304,"Dourado",24 +3123502,"Douradoquara",12 +5003702,"Dourados",11 +4107306,"Doutor Camargo",15 +4306734,"Doutor Maurício Cardoso",20 +4205159,"Doutor Pedrinho",23 +4306759,"Doutor Ricardo",20 +2403202,"Doutor Severiano",19 +4128633,"Doutor Ulysses",15 +5207253,"Doverlândia",8 +3514403,"Dracena",24 +3514502,"Duartina",24 +3301603,"Duas Barras",18 +2505808,"Duas Estradas",14 +1707306,"Dueré",26 +3514601,"Dumont",24 +2103901,"Duque Bacelar",9 +3301702,"Duque de Caxias",18 +3123528,"Durandé",12 +3514700,"Echaporã",24 +3202108,"Ecoporanga",7 +5207352,"Edealina",8 +5207402,"Edéia",8 +1301407,"Eirunepé",4 +5003751,"Eldorado",11 +3514809,"Eldorado",24 +1502954,"Eldorado do Carajás",13 +4306767,"Eldorado do Sul",20 +2203503,"Elesbão Veloso",17 +3514908,"Elias Fausto",24 +2203602,"Eliseu Martins",17 +3514924,"Elisiário",24 +2910305,"Elísio Medrado",5 +3123601,"Elói Mendes",12 +2505907,"Emas",14 +3514957,"Embaúba",24 +3515004,"Embu das Artes",24 +3515103,"Embu-Guaçu",24 +3515129,"Emilianópolis",24 +4306809,"Encantado",20 +2403301,"Encanto",19 +2910404,"Encruzilhada",5 +4306908,"Encruzilhada do Sul",20 +4107405,"Enéas Marques",15 +4107504,"Engenheiro Beltrão",15 +3123700,"Engenheiro Caldas",12 +3515152,"Engenheiro Coelho",24 +3123809,"Engenheiro Navarro",12 +3301801,"Engenheiro Paulo de Frontin",18 +4306924,"Engenho Velho",20 +3123858,"Entre Folhas",12 +2910503,"Entre Rios",5 +4205175,"Entre Rios",23 +3123908,"Entre Rios de Minas",12 +4107538,"Entre Rios do Oeste",15 +4306957,"Entre Rios do Sul",20 +4306932,"Entre-Ijuís",20 +1301506,"Envira",4 +2403400,"Equador",19 +4306973,"Erebango",20 +4307005,"Erechim",20 +2304277,"Ererê",6 +2900504,"Érico Cardoso",5 +4205191,"Ermo",23 +4307054,"Ernestina",20 +4307203,"Erval Grande",20 +4307302,"Erval Seco",20 +4205209,"Erval Velho",23 +3124005,"Ervália",12 +2605202,"Escada",16 +4307401,"Esmeralda",20 +3124104,"Esmeraldas",12 +3124203,"Espera Feliz",12 +2506004,"Esperança",14 +4307450,"Esperança do Sul",20 +4107520,"Esperança Nova",15 +1707405,"Esperantina",26 +2203701,"Esperantina",17 +2104008,"Esperantinópolis",9 +4107546,"Espigão Alto do Iguaçu",15 +3124302,"Espinosa",12 +2403509,"Espírito Santo",19 +3124401,"Espírito Santo do Dourado",12 +3515186,"Espírito Santo do Pinhal",24 +3515194,"Espírito Santo do Turvo",24 +2910602,"Esplanada",5 +4307500,"Espumoso",20 +4307559,"Estação",20 +2802106,"Estância",25 +4307609,"Estância Velha",20 +4307708,"Esteio",20 +3124500,"Estiva",12 +3557303,"Estiva Gerbi",24 +2104057,"Estreito",9 +4307807,"Estrela",20 +3515202,"Estrela d'Oeste",24 +3124609,"Estrela Dalva",12 +2702553,"Estrela de Alagoas",2 +3124708,"Estrela do Indaiá",12 +5207501,"Estrela do Norte",8 +3515301,"Estrela do Norte",24 +3124807,"Estrela do Sul",12 +4307815,"Estrela Velha",20 +2910701,"Euclides da Cunha",5 +3515350,"Euclides da Cunha Paulista",24 +4307831,"Eugênio de Castro",20 +3124906,"Eugenópolis",12 +2910727,"Eunápolis",5 +2304285,"Eusébio",6 +3125002,"Ewbank da Câmara",12 +3125101,"Extrema",12 +2403608,"Extremoz",19 +2605301,"Exu",16 +2506103,"Fagundes",14 +4307864,"Fagundes Varela",20 +5207535,"Faina",8 +3125200,"Fama",12 +3125309,"Faria Lemos",12 +2304301,"Farias Brito",6 +1503002,"Faro",13 +4107553,"Farol",15 +4307906,"Farroupilha",20 +3515400,"Fartura",24 +2203750,"Fartura do Piauí",17 +1707553,"Fátima",26 +2910750,"Fátima",5 +5003801,"Fátima do Sul",11 +4107603,"Faxinal",15 +4308003,"Faxinal do Soturno",20 +4205308,"Faxinal dos Guedes",23 +4308052,"Faxinalzinho",20 +5207600,"Fazenda Nova",8 +4107652,"Fazenda Rio Grande",15 +4308078,"Fazenda Vilanova",20 +2910776,"Feira da Mata",5 +2910800,"Feira de Santana",5 +2702603,"Feira Grande",2 +2605400,"Feira Nova",16 +2802205,"Feira Nova",25 +2104073,"Feira Nova do Maranhão",9 +3125408,"Felício dos Santos",12 +2403707,"Felipe Guerra",19 +3125606,"Felisburgo",12 +3125705,"Felixlândia",12 +4308102,"Feliz",20 +2702702,"Feliz Deserto",2 +5103700,"Feliz Natal",10 +4107702,"Fênix",15 +4107736,"Fernandes Pinheiro",15 +3125804,"Fernandes Tourinho",12 +2605459,"Fernando de Noronha",16 +2104081,"Fernando Falcão",9 +2403756,"Fernando Pedroza",19 +3515608,"Fernando Prestes",24 +3515509,"Fernandópolis",24 +3515657,"Fernão",24 +3515707,"Ferraz de Vasconcelos",24 +1600238,"Ferreira Gomes",3 +2605509,"Ferreiros",16 +3125903,"Ferros",12 +3125952,"Fervedouro",12 +4107751,"Figueira",15 +5003900,"Figueirão",11 +1707652,"Figueirópolis",26 +5103809,"Figueirópolis D'Oeste",10 +1707702,"Filadélfia",26 +2910859,"Filadélfia",5 +2910909,"Firmino Alves",5 +5207808,"Firminópolis",8 +2702801,"Flexeiras",2 +4107850,"Flor da Serra do Sul",15 +4205357,"Flor do Sertão",23 +3515806,"Flora Rica",24 +4107801,"Floraí",15 +2403806,"Florânia",19 +3515905,"Floreal",24 +2605608,"Flores",16 +4308201,"Flores da Cunha",20 +5207907,"Flores de Goiás",8 +2203800,"Flores do Piauí",17 +4107900,"Floresta",15 +2605707,"Floresta",16 +2911006,"Floresta Azul",5 +1503044,"Floresta do Araguaia",13 +2203859,"Floresta do Piauí",17 +3126000,"Florestal",12 +4108007,"Florestópolis",15 +2203909,"Floriano",17 +4308250,"Floriano Peixoto",20 +4108106,"Flórida",15 +3516002,"Flórida Paulista",24 +3516101,"Florínia",24 +1301605,"Fonte Boa",4 +4308300,"Fontoura Xavier",20 +3126109,"Formiga",12 +4308409,"Formigueiro",20 +5208004,"Formosa",8 +2104099,"Formosa da Serra Negra",9 +4108205,"Formosa do Oeste",15 +2911105,"Formosa do Rio Preto",5 +4205431,"Formosa do Sul",23 +5208103,"Formoso",8 +3126208,"Formoso",12 +1708205,"Formoso do Araguaia",26 +4308433,"Forquetinha",20 +2304350,"Forquilha",6 +4205456,"Forquilhinha",23 +3126307,"Fortaleza de Minas",12 +1708254,"Fortaleza do Tabocão",26 +2104107,"Fortaleza dos Nogueiras",9 +4308458,"Fortaleza dos Valos",20 +2304459,"Fortim",6 +2104206,"Fortuna",9 +3126406,"Fortuna de Minas",12 +4108304,"Foz do Iguaçu",15 +4108452,"Foz do Jordão",15 +4205506,"Fraiburgo",23 +3516200,"Franca",24 +2204006,"Francinópolis",17 +4108320,"Francisco Alves",15 +2204105,"Francisco Ayres",17 +3126505,"Francisco Badaró",12 +4108403,"Francisco Beltrão",15 +2403905,"Francisco Dantas",19 +3126604,"Francisco Dumont",12 +2204154,"Francisco Macedo",17 +3516309,"Francisco Morato",24 +3126703,"Francisco Sá",12 +2204204,"Francisco Santos",17 +3126752,"Franciscópolis",12 +3516408,"Franco da Rocha",24 +2304509,"Frecheirinha",6 +4308508,"Frederico Westphalen",20 +3126802,"Frei Gaspar",12 +3126901,"Frei Inocêncio",12 +3126950,"Frei Lagonegro",12 +2506202,"Frei Martinho",14 +2605806,"Frei Miguelinho",16 +2802304,"Frei Paulo",25 +4205555,"Frei Rogério",23 +1200302,"Feijó",1 +4205407,"Florianópolis",23 +3127008,"Fronteira",12 +3127057,"Fronteira dos Vales",12 +2204303,"Fronteiras",17 +3127073,"Fruta de Leite",12 +3127107,"Frutal",12 +2404002,"Frutuoso Gomes",19 +3202207,"Fundão",7 +3127206,"Funilândia",12 +3516507,"Gabriel Monteiro",24 +2506251,"Gado Bravo",14 +3516606,"Gália",24 +3127305,"Galiléia",12 +2404101,"Galinhos",19 +4205605,"Galvão",23 +2605905,"Gameleira",16 +5208152,"Gameleira de Goiás",8 +3127339,"Gameleiras",12 +2911204,"Gandu",5 +2606002,"Garanhuns",16 +2802403,"Gararu",25 +3516705,"Garça",24 +4308607,"Garibaldi",20 +4205704,"Garopaba",23 +1503077,"Garrafão do Norte",13 +4308656,"Garruchos",20 +4205803,"Garuva",23 +4205902,"Gaspar",23 +3516804,"Gastão Vidigal",24 +5103858,"Gaúcha do Norte",10 +4308706,"Gaurama",20 +2911253,"Gavião",5 +3516853,"Gavião Peixoto",24 +2204352,"Geminiano",17 +4308805,"General Câmara",20 +5103908,"General Carneiro",10 +4108502,"General Carneiro",15 +2802502,"General Maynard",25 +3516903,"General Salgado",24 +2304608,"General Sampaio",6 +4308854,"Gentil",20 +2911303,"Gentio do Ouro",5 +3517000,"Getulina",24 +4308904,"Getúlio Vargas",20 +2204402,"Gilbués",17 +2702900,"Girau do Ponciano",2 +4309001,"Giruá",20 +3127354,"Glaucilândia",12 +3517109,"Glicério",24 +2911402,"Glória",5 +5103957,"Glória D'Oeste",10 +5004007,"Glória de Dourados",11 +2606101,"Glória do Goitá",16 +4309050,"Glorinha",20 +2104305,"Godofredo Viana",9 +4108551,"Godoy Moreira",15 +3127370,"Goiabeira",12 +3127388,"Goianá",12 +2606200,"Goiana",16 +5208400,"Goianápolis",8 +5208509,"Goiandira",8 +5208608,"Goianésia",8 +1503093,"Goianésia do Pará",13 +2404200,"Goianinha",19 +5208806,"Goianira",8 +1708304,"Goianorte",26 +5208905,"Goiás",8 +1709005,"Goiatins",26 +5209101,"Goiatuba",8 +4108601,"Goioerê",15 +4108650,"Goioxim",15 +3127404,"Gonçalves",12 +2104404,"Gonçalves Dias",9 +2911501,"Gongogi",5 +3127503,"Gonzaga",12 +3127602,"Gouveia",12 +5209150,"Gouvelândia",8 +2104503,"Governador Archer",9 +4206009,"Governador Celso Ramos",23 +2404309,"Governador Dix-Sept Rosado",19 +2104552,"Governador Edison Lobão",9 +2104602,"Governador Eugênio Barros",9 +3202256,"Governador Lindenberg",7 +2104628,"Governador Luiz Rocha",9 +2911600,"Governador Mangabeira",5 +2104651,"Governador Newton Bello",9 +2104677,"Governador Nunes Freire",9 +3127701,"Governador Valadares",12 +2304657,"Graça",6 +2104701,"Graça Aranha",9 +2802601,"Gracho Cardoso",25 +2104800,"Grajaú",9 +4309100,"Gramado",20 +4309126,"Gramado dos Loureiros",20 +4309159,"Gramado Xavier",20 +4108700,"Grandes Rios",15 +2606309,"Granito",16 +2304707,"Granja",6 +2304806,"Granjeiro",6 +3127800,"Grão Mogol",12 +4206108,"Grão Pará",23 +2606408,"Gravatá",16 +4309209,"Gravataí",20 +4206207,"Gravatal",23 +2304905,"Groaíras",6 +2404408,"Grossos",19 +3127909,"Grupiara",12 +4309258,"Guabiju",20 +4206306,"Guabiruba",23 +3202306,"Guaçuí",7 +2204501,"Guadalupe",17 +4309308,"Guaíba",20 +3517208,"Guaiçara",24 +3517307,"Guaimbê",24 +3517406,"Guaíra",24 +4108809,"Guaíra",15 +4108908,"Guairaçá",15 +2304954,"Guaiúba",6 +1301654,"Guajará",4 +2911659,"Guajeru",5 +2404507,"Guamaré",19 +4108957,"Guamiranga",15 +2911709,"Guanambi",5 +3128006,"Guanhães",12 +3128105,"Guapé",12 +3517505,"Guapiaçu",24 +3517604,"Guapiara",24 +3301850,"Guapimirim",18 +4109005,"Guapirama",15 +5209200,"Guapó",8 +4309407,"Guaporé",20 +4109104,"Guaporema",15 +3517703,"Guará",24 +2506301,"Guarabira",14 +3517802,"Guaraçaí",24 +3517901,"Guaraci",24 +4109203,"Guaraci",15 +3128204,"Guaraciaba",12 +4206405,"Guaraciaba",23 +2305001,"Guaraciaba do Norte",6 +3128253,"Guaraciama",12 +1709302,"Guaraí",26 +5209291,"Guaraíta",8 +2305100,"Guaramiranga",6 +4206504,"Guaramirim",23 +3128303,"Guaranésia",12 +3128402,"Guarani",12 +3518008,"Guarani d'Oeste",24 +4309506,"Guarani das Missões",20 +5209408,"Guarani de Goiás",8 +4109302,"Guaraniaçu",15 +3518107,"Guarantã",24 +5104104,"Guarantã do Norte",10 +3202405,"Guarapari",7 +4109401,"Guarapuava",15 +4109500,"Guaraqueçaba",15 +1100106,"Guajará-Mirim",21 +3128501,"Guarará",12 +3518206,"Guararapes",24 +3518305,"Guararema",24 +2911808,"Guaratinga",5 +3518404,"Guaratinguetá",24 +4109609,"Guaratuba",15 +3128600,"Guarda-Mor",12 +3518503,"Guareí",24 +3518602,"Guariba",24 +2204550,"Guaribas",17 +5209457,"Guarinos",8 +3518701,"Guarujá",24 +4206603,"Guarujá do Sul",23 +3518800,"Guarulhos",24 +4206652,"Guatambú",23 +3518859,"Guatapará",24 +3128709,"Guaxupé",12 +5004106,"Guia Lopes da Laguna",11 +3128808,"Guidoval",12 +2104909,"Guimarães",9 +3128907,"Guimarânia",12 +5104203,"Guiratinga",10 +3129004,"Guiricema",12 +3129103,"Gurinhatã",12 +2506400,"Gurinhém",14 +2506509,"Gurjão",14 +1503101,"Gurupá",13 +1709500,"Gurupi",26 +3518909,"Guzolândia",24 +4309555,"Harmonia",20 +5209606,"Heitoraí",8 +3129202,"Heliodora",12 +2911857,"Heliópolis",5 +3519006,"Herculândia",24 +4307104,"Herval",20 +4206702,"Herval d'Oeste",23 +4309571,"Herveiras",20 +5209705,"Hidrolândia",8 +2305209,"Hidrolândia",6 +5209804,"Hidrolina",8 +3519055,"Holambra",24 +4109658,"Honório Serpa",15 +2305233,"Horizonte",6 +4309605,"Horizontina",20 +3519071,"Hortolândia",24 +2204600,"Hugo Napoleão",17 +4309654,"Hulha Negra",20 +4309704,"Humaitá",20 +1301704,"Humaitá",4 +2105005,"Humberto de Campos",9 +3519105,"Iacanga",24 +5209903,"Iaciara",8 +3519204,"Iacri",24 +2911907,"Iaçu",5 +3129301,"Iapu",12 +3519253,"Iaras",24 +2606507,"Iati",16 +4109708,"Ibaiti",15 +4309753,"Ibarama",20 +2305266,"Ibaretama",6 +3519303,"Ibaté",24 +2703007,"Ibateguara",2 +3202454,"Ibatiba",7 +4109757,"Ibema",15 +3129400,"Ibertioga",12 +3129509,"Ibiá",12 +4309803,"Ibiaçá",20 +3129608,"Ibiaí",12 +4206751,"Ibiam",23 +2305308,"Ibiapina",6 +2506608,"Ibiara",14 +2912004,"Ibiassucê",5 +2912103,"Ibicaraí",5 +4206801,"Ibicaré",23 +2912202,"Ibicoara",5 +2912301,"Ibicuí",5 +2305332,"Ibicuitinga",6 +2606606,"Ibimirim",16 +2912400,"Ibipeba",5 +2912509,"Ibipitanga",5 +4109807,"Ibiporã",15 +2912608,"Ibiquera",5 +3519402,"Ibirá",24 +3129657,"Ibiracatu",12 +3129707,"Ibiraci",12 +3202504,"Ibiraçu",7 +4309902,"Ibiraiaras",20 +2606705,"Ibirajuba",16 +4206900,"Ibirama",23 +2912707,"Ibirapitanga",5 +2912806,"Ibirapuã",5 +4309951,"Ibirapuitã",20 +3519501,"Ibirarema",24 +2912905,"Ibirataia",5 +3129806,"Ibirité",12 +4310009,"Ibirubá",20 +2913002,"Ibitiara",5 +3519600,"Ibitinga",24 +3202553,"Ibitirama",7 +2913101,"Ibititá",5 +3129905,"Ibitiúra de Minas",12 +3130002,"Ibituruna",12 +3519709,"Ibiúna",24 +2913200,"Ibotirama",5 +2305357,"Icapuí",6 +4207007,"Içara",23 +3130051,"Icaraí de Minas",12 +4109906,"Icaraíma",15 +2105104,"Icatu",9 +3519808,"Icém",24 +2913309,"Ichu",5 +2305407,"Icó",6 +3202603,"Iconha",7 +2404606,"Ielmo Marinho",19 +3519907,"Iepê",24 +2703106,"Igaci",2 +2913408,"Igaporã",5 +3520004,"Igaraçu do Tietê",24 +2502607,"Igaracy",14 +3520103,"Igarapava",24 +3130101,"Igarapé",12 +2105153,"Igarapé do Meio",9 +2105203,"Igarapé Grande",9 +1503200,"Igarapé-Açu",13 +1503309,"Igarapé-Miri",13 +2606804,"Igarassu",16 +3520202,"Igaratá",24 +3130200,"Igaratinga",12 +2913457,"Igrapiúna",5 +2703205,"Igreja Nova",2 +4310108,"Igrejinha",20 +3301876,"Iguaba Grande",18 +2913507,"Iguaí",5 +3520301,"Iguape",24 +4110003,"Iguaraçu",15 +2606903,"Iguaracy",16 +3130309,"Iguatama",12 +5004304,"Iguatemi",11 +2305506,"Iguatu",6 +4110052,"Iguatu",15 +3130408,"Ijaci",12 +4310207,"Ijuí",20 +3520426,"Ilha Comprida",24 +2802700,"Ilha das Flores",25 +2607604,"Ilha de Itamaracá",16 +2204659,"Ilha Grande",17 +3520442,"Ilha Solteira",24 +3520400,"Ilhabela",24 +2913606,"Ilhéus",5 +4207106,"Ilhota",23 +3130507,"Ilicínea",12 +4310306,"Ilópolis",20 +2506707,"Imaculada",14 +4207205,"Imaruí",23 +4110078,"Imbaú",15 +4310330,"Imbé",20 +3130556,"Imbé de Minas",12 +4207304,"Imbituba",23 +4110102,"Imbituva",15 +4207403,"Imbuia",23 +4310363,"Imigrante",20 +2105302,"Imperatriz",9 +4110201,"Inácio Martins",15 +5209937,"Inaciolândia",8 +2607000,"Inajá",16 +4110300,"Inajá",15 +3130606,"Inconfidentes",12 +3130655,"Indaiabira",12 +4207502,"Indaial",23 +3520509,"Indaiatuba",24 +4310405,"Independência",20 +2305605,"Independência",6 +3520608,"Indiana",24 +4110409,"Indianópolis",15 +3130705,"Indianópolis",12 +3520707,"Indiaporã",24 +5209952,"Indiara",8 +2802809,"Indiaroba",25 +5104500,"Indiavaí",10 +2506806,"Ingá",14 +3130804,"Ingaí",12 +2607109,"Ingazeira",16 +4310413,"Inhacorá",20 +2913705,"Inhambupe",5 +1503408,"Inhangapi",13 +2703304,"Inhapi",2 +3130903,"Inhapim",12 +3131000,"Inhaúma",12 +2204709,"Inhuma",17 +5210000,"Inhumas",8 +3131109,"Inimutaba",12 +5004403,"Inocência",11 +3520806,"Inúbia Paulista",24 +4207577,"Iomerê",23 +3131158,"Ipaba",12 +5210109,"Ipameri",8 +3131208,"Ipanema",12 +2404705,"Ipanguaçu",19 +2305654,"Ipaporanga",6 +3131307,"Ipatinga",12 +2305704,"Ipaumirim",6 +3520905,"Ipaussu",24 +4310439,"Ipê",20 +2913804,"Ipecaetá",5 +3521002,"Iperó",24 +3521101,"Ipeúna",24 +3131406,"Ipiaçu",12 +2913903,"Ipiaú",5 +3521150,"Ipiguá",24 +2914000,"Ipirá",5 +4207601,"Ipira",23 +4110508,"Ipiranga",15 +5210158,"Ipiranga de Goiás",8 +5104526,"Ipiranga do Norte",10 +2204808,"Ipiranga do Piauí",17 +4310462,"Ipiranga do Sul",20 +1301803,"Ipixuna",4 +1503457,"Ipixuna do Pará",13 +2607208,"Ipojuca",16 +4110607,"Iporã",15 +5210208,"Iporá",8 +4207650,"Iporã do Oeste",23 +3521200,"Iporanga",24 +2305803,"Ipu",6 +3521309,"Ipuã",24 +4207684,"Ipuaçu",23 +2607307,"Ipubi",16 +2404804,"Ipueira",19 +1709807,"Ipueiras",26 +2305902,"Ipueiras",6 +3131505,"Ipuiúna",12 +4207700,"Ipumirim",23 +2914109,"Ipupiara",5 +1400282,"Iracema",22 +2306009,"Iracema",6 +4110656,"Iracema do Oeste",15 +3521408,"Iracemápolis",24 +4207759,"Iraceminha",23 +4310504,"Iraí",20 +3131604,"Iraí de Minas",12 +2914208,"Irajuba",5 +2914307,"Iramaia",5 +1301852,"Iranduba",4 +4207809,"Irani",23 +3521507,"Irapuã",24 +3521606,"Irapuru",24 +2914406,"Iraquara",5 +2914505,"Irará",5 +4110706,"Irati",15 +4207858,"Irati",23 +2306108,"Irauçuba",6 +2914604,"Irecê",5 +4110805,"Iretama",15 +4207908,"Irineópolis",23 +1503507,"Irituia",13 +3202652,"Irupi",7 +2204907,"Isaías Coelho",17 +5210307,"Israelândia",8 +4208005,"Itá",23 +4310538,"Itaara",20 +2506905,"Itabaiana",14 +2802908,"Itabaiana",25 +2803005,"Itabaianinha",25 +2914653,"Itabela",5 +3521705,"Itaberá",24 +2914703,"Itaberaba",5 +5210406,"Itaberaí",8 +2803104,"Itabi",25 +3131703,"Itabira",12 +3131802,"Itabirinha",12 +3131901,"Itabirito",12 +3301900,"Itaboraí",18 +2914802,"Itabuna",5 +1710508,"Itacajá",26 +3132008,"Itacambira",12 +3132107,"Itacarambi",12 +2914901,"Itacaré",5 +1301902,"Itacoatiara",4 +2607406,"Itacuruba",16 +4310553,"Itacurubi",20 +2915007,"Itaeté",5 +2915106,"Itagi",5 +2915205,"Itagibá",5 +2915304,"Itagimirim",5 +3202702,"Itaguaçu",7 +2915353,"Itaguaçu da Bahia",5 +3302007,"Itaguaí",18 +4110904,"Itaguajé",15 +3132206,"Itaguara",12 +5210562,"Itaguari",8 +5210604,"Itaguaru",8 +1710706,"Itaguatins",26 +3521804,"Itaí",24 +2607505,"Itaíba",16 +2306207,"Itaiçaba",6 +2205003,"Itainópolis",17 +4208104,"Itaiópolis",23 +2105351,"Itaipava do Grajaú",9 +3132305,"Itaipé",12 +4110953,"Itaipulândia",15 +2306256,"Itaitinga",6 +1503606,"Itaituba",13 +2404853,"Itajá",19 +5210802,"Itajá",8 +4208203,"Itajaí",23 +3521903,"Itajobi",24 +3522000,"Itaju",24 +2915403,"Itaju do Colônia",5 +3132404,"Itajubá",12 +2915502,"Itajuípe",5 +3302056,"Italva",18 +2915601,"Itamaraju",5 +3132503,"Itamarandiba",12 +1301951,"Itamarati",4 +3132602,"Itamarati de Minas",12 +2915700,"Itamari",5 +3132701,"Itambacuri",12 +4111001,"Itambaracá",15 +4111100,"Itambé",15 +2607653,"Itambé",16 +2915809,"Itambé",5 +3132800,"Itambé do Mato Dentro",12 +3132909,"Itamogi",12 +3133006,"Itamonte",12 +2915908,"Itanagra",5 +3522109,"Itanhaém",24 +3133105,"Itanhandu",12 +5104542,"Itanhangá",10 +2916005,"Itanhém",5 +3133204,"Itanhomi",12 +3133303,"Itaobim",12 +3522158,"Itaóca",24 +3302106,"Itaocara",18 +5210901,"Itapaci",8 +3133402,"Itapagipe",12 +2306306,"Itapajé",6 +2916104,"Itaparica",5 +2916203,"Itapé",5 +2916302,"Itapebi",5 +3133501,"Itapecerica",12 +3522208,"Itapecerica da Serra",24 +2105401,"Itapecuru Mirim",9 +4111209,"Itapejara d'Oeste",15 +4208302,"Itapema",23 +3202801,"Itapemirim",7 +4111258,"Itaperuçu",15 +3302205,"Itaperuna",18 +2607703,"Itapetim",16 +2916401,"Itapetinga",5 +3522307,"Itapetininga",24 +3522406,"Itapeva",24 +3133600,"Itapeva",12 +3522505,"Itapevi",24 +2916500,"Itapicuru",5 +2306405,"Itapipoca",6 +3522604,"Itapira",24 +1302009,"Itapiranga",4 +4208401,"Itapiranga",23 +5211008,"Itapirapuã",8 +3522653,"Itapirapuã Paulista",24 +1710904,"Itapiratins",26 +2607752,"Itapissuma",16 +2916609,"Itapitanga",5 +2306504,"Itapiúna",6 +4208450,"Itapoá",23 +3522703,"Itápolis",24 +5004502,"Itaporã",11 +1711100,"Itaporã do Tocantins",26 +3522802,"Itaporanga",24 +2507002,"Itaporanga",14 +2803203,"Itaporanga d'Ajuda",25 +2507101,"Itapororoca",14 +4310579,"Itapuca",20 +3522901,"Itapuí",24 +3523008,"Itapura",24 +5211206,"Itapuranga",8 +3523107,"Itaquaquecetuba",24 +2916708,"Itaquara",5 +4310603,"Itaqui",20 +5004601,"Itaquiraí",11 +2607802,"Itaquitinga",16 +3202900,"Itarana",7 +2916807,"Itarantim",5 +3523206,"Itararé",24 +2306553,"Itarema",6 +3523305,"Itariri",24 +5211305,"Itarumã",8 +4310652,"Itati",20 +3302254,"Itatiaia",18 +3133709,"Itatiaiuçu",12 +3523404,"Itatiba",24 +4310702,"Itatiba do Sul",20 +2916856,"Itatim",5 +3523503,"Itatinga",24 +2306603,"Itatira",6 +2507200,"Itatuba",14 +2404903,"Itaú",19 +3133758,"Itaú de Minas",12 +5104559,"Itaúba",10 +1600253,"Itaubal",3 +5211404,"Itauçu",8 +2205102,"Itaueira",17 +3133808,"Itaúna",12 +4111308,"Itaúna do Sul",15 +3133907,"Itaverava",12 +3134004,"Itinga",12 +2105427,"Itinga do Maranhão",9 +5104609,"Itiquira",10 +3523602,"Itirapina",24 +3523701,"Itirapuã",24 +2916906,"Itiruçu",5 +2917003,"Itiúba",5 +3523800,"Itobi",24 +2917102,"Itororó",5 +3523909,"Itu",24 +2917201,"Ituaçu",5 +2917300,"Ituberá",5 +3134103,"Itueta",12 +3134202,"Ituiutaba",12 +5211503,"Itumbiara",8 +3134301,"Itumirim",12 +3524006,"Itupeva",24 +1503705,"Itupiranga",13 +4208500,"Ituporanga",23 +3134400,"Iturama",12 +3134509,"Itutinga",12 +3524105,"Ituverava",24 +2917334,"Iuiú",5 +3203007,"Iúna",7 +4111407,"Ivaí",15 +4111506,"Ivaiporã",15 +4111555,"Ivaté",15 +4111605,"Ivatuba",15 +5004700,"Ivinhema",11 +5211602,"Ivolândia",8 +4310751,"Ivorá",20 +4310801,"Ivoti",20 +2607901,"Jaboatão dos Guararapes",16 +4208609,"Jaborá",23 +2917359,"Jaborandi",5 +3524204,"Jaborandi",24 +4111704,"Jaboti",15 +4310850,"Jaboticaba",20 +3524303,"Jaboticabal",24 +3134608,"Jaboticatubas",12 +2405009,"Jaçanã",19 +2917409,"Jacaraci",5 +2507309,"Jacaraú",14 +2703403,"Jacaré dos Homens",2 +1503754,"Jacareacanga",13 +3524402,"Jacareí",24 +4111803,"Jacarezinho",15 +3524501,"Jaci",24 +5104807,"Jaciara",10 +3134707,"Jacinto",12 +4208708,"Jacinto Machado",23 +2917508,"Jacobina",5 +2205151,"Jacobina do Piauí",17 +3134806,"Jacuí",12 +2703502,"Jacuípe",2 +4310876,"Jacuizinho",20 +1503804,"Jacundá",13 +3524600,"Jacupiranga",24 +4310900,"Jacutinga",20 +3134905,"Jacutinga",12 +4111902,"Jaguapitã",15 +2917607,"Jaguaquara",5 +3135001,"Jaguaraçu",12 +4311007,"Jaguarão",20 +2917706,"Jaguarari",5 +3203056,"Jaguaré",7 +2306702,"Jaguaretama",6 +4311106,"Jaguari",20 +4112009,"Jaguariaíva",15 +2306801,"Jaguaribara",6 +2306900,"Jaguaribe",6 +2917805,"Jaguaripe",5 +3524709,"Jaguariúna",24 +2307007,"Jaguaruana",6 +4208807,"Jaguaruna",23 +3135050,"Jaíba",12 +2205201,"Jaicós",17 +3524808,"Jales",24 +3524907,"Jambeiro",24 +3135076,"Jampruca",12 +3135100,"Janaúba",12 +5211701,"Jandaia",8 +4112108,"Jandaia do Sul",15 +2405108,"Jandaíra",19 +2917904,"Jandaíra",5 +3525003,"Jandira",24 +2405207,"Janduís",19 +5104906,"Jangada",10 +4112207,"Janiópolis",15 +3135209,"Januária",12 +2405306,"Januário Cicco (Boa Saúde)",19 +3135308,"Japaraíba",12 +2703601,"Japaratinga",2 +2803302,"Japaratuba",25 +3302270,"Japeri",18 +2405405,"Japi",19 +4112306,"Japira",15 +2803401,"Japoatã",25 +3135357,"Japonvar",12 +5004809,"Japorã",11 +4112405,"Japurá",15 +1302108,"Japurá",4 +2607950,"Jaqueira",16 +4311122,"Jaquirana",20 +5211800,"Jaraguá",8 +4208906,"Jaraguá do Sul",23 +5004908,"Jaraguari",11 +2703700,"Jaramataia",2 +2307106,"Jardim",6 +5005004,"Jardim",11 +4112504,"Jardim Alegre",15 +2405504,"Jardim de Angicos",19 +2405603,"Jardim de Piranhas",19 +2205250,"Jardim do Mulato",17 +2405702,"Jardim do Seridó",19 +4112603,"Jardim Olinda",15 +3525102,"Jardinópolis",24 +4208955,"Jardinópolis",23 +4311130,"Jari",20 +3525201,"Jarinu",24 +5211909,"Jataí",8 +4112702,"Jataizinho",15 +2608008,"Jataúba",16 +5005103,"Jateí",11 +2307205,"Jati",6 +2105450,"Jatobá",9 +2608057,"Jatobá",16 +2205276,"Jatobá do Piauí",17 +3525300,"Jaú",24 +1711506,"Jaú do Tocantins",26 +5212006,"Jaupaci",8 +5105002,"Jauru",10 +3135407,"Jeceaba",12 +3135456,"Jenipapo de Minas",12 +2105476,"Jenipapo dos Vieiras",9 +3135506,"Jequeri",12 +2703759,"Jequiá da Praia",2 +2918001,"Jequié",5 +3135605,"Jequitaí",12 +3135704,"Jequitibá",12 +3135803,"Jequitinhonha",12 +2918100,"Jeremoabo",5 +2507408,"Jericó",14 +3525409,"Jeriquara",24 +3203106,"Jerônimo Monteiro",7 +2205300,"Jerumenha",17 +3135902,"Jesuânia",12 +4112751,"Jesuítas",15 +5212055,"Jesúpolis",8 +2307254,"Jijoca de Jericoacoara",6 +2918209,"Jiquiriçá",5 +2918308,"Jitaúna",5 +4209003,"Joaçaba",23 +3136009,"Joaíma",12 +3136108,"Joanésia",12 +3525508,"Joanópolis",24 +2608107,"João Alfredo",16 +2405801,"João Câmara",19 +2205359,"João Costa",17 +2405900,"João Dias",19 +2918357,"João Dourado",5 +2105500,"João Lisboa",9 +3136207,"João Monlevade",12 +3203130,"João Neiva",7 +2507507,"João Pessoa",25 +3136306,"João Pinheiro",12 +3525607,"João Ramalho",24 +3136405,"Joaquim Felício",12 +2703809,"Joaquim Gomes",2 +2608206,"Joaquim Nabuco",16 +2205409,"Joaquim Pires",17 +4112801,"Joaquim Távora",15 +2513653,"Joca Claudino",14 +2205458,"Joca Marques",17 +4311155,"Jóia",20 +4209102,"Joinville",23 +3136504,"Jordânia",12 +4209151,"José Boiteux",23 +3525706,"José Bonifácio",24 +2406007,"José da Penha",19 +2205508,"José de Freitas",17 +3136520,"José Gonçalves de Minas",12 +3136553,"José Raydan",12 +2105609,"Joselândia",9 +3136579,"Josenópolis",12 +5212105,"Joviânia",8 +5105101,"Juara",10 +2507606,"Juarez Távora",14 +1711803,"Juarina",26 +3136652,"Juatuba",12 +2507705,"Juazeirinho",14 +2918407,"Juazeiro",5 +2307304,"Juazeiro do Norte",6 +2205516,"Juazeiro do Piauí",17 +2307403,"Jucás",6 +2608255,"Jucati",16 +2918456,"Jucuruçu",5 +2406106,"Jucurutu",19 +5105150,"Juína",10 +3136702,"Juiz de Fora",12 +2205524,"Júlio Borges",17 +4311205,"Júlio de Castilhos",20 +3525805,"Júlio Mesquita",24 +3525854,"Jumirim",24 +2105658,"Junco do Maranhão",9 +2507804,"Junco do Seridó",14 +2406155,"Jundiá",19 +2703908,"Jundiá",2 +3525904,"Jundiaí",24 +4112900,"Jundiaí do Sul",15 +2704005,"Junqueiro",2 +3526001,"Junqueirópolis",24 +2608305,"Jupi",16 +4209177,"Jupiá",23 +3526100,"Juquiá",24 +3526209,"Juquitiba",24 +3136801,"Juramento",12 +4112959,"Juranda",15 +2608404,"Jurema",16 +2205532,"Jurema",17 +2507903,"Juripiranga",14 +2508000,"Juru",14 +1302207,"Juruá",4 +3136900,"Juruaia",12 +5105176,"Juruena",10 +1503903,"Juruti",13 +5105200,"Juscimeira",10 +2918506,"Jussara",5 +5212204,"Jussara",8 +4113007,"Jussara",15 +2918555,"Jussari",5 +2918605,"Jussiape",5 +1302306,"Jutaí",4 +5005152,"Juti",11 +3136959,"Juvenília",12 +4113106,"Kaloré",15 +1302405,"Lábrea",4 +4209201,"Lacerdópolis",23 +3137007,"Ladainha",12 +5005202,"Ladário",11 +2918704,"Lafaiete Coutinho",5 +3137106,"Lagamar",12 +2803500,"Lagarto",25 +4209300,"Lages",23 +2105708,"Lago da Pedra",9 +1100114,"Jaru",21 +1100122,"Ji-Paraná",21 +2105807,"Lago do Junco",9 +2105948,"Lago dos Rodrigues",9 +2105906,"Lago Verde",9 +2508109,"Lagoa",14 +2205557,"Lagoa Alegre",17 +4311239,"Lagoa Bonita do Sul",20 +2406205,"Lagoa d'Anta",19 +2704104,"Lagoa da Canoa",2 +1711902,"Lagoa da Confusão",26 +3137205,"Lagoa da Prata",12 +2508208,"Lagoa de Dentro",14 +2608503,"Lagoa de Itaenga",16 +2406304,"Lagoa de Pedras",19 +2205573,"Lagoa de São Francisco",17 +2406403,"Lagoa de Velhos",19 +2205565,"Lagoa do Barro do Piauí",17 +2608453,"Lagoa do Carro",16 +2105922,"Lagoa do Mato",9 +2608602,"Lagoa do Ouro",16 +2205581,"Lagoa do Piauí",17 +2205599,"Lagoa do Sítio",17 +1711951,"Lagoa do Tocantins",26 +2608701,"Lagoa dos Gatos",16 +3137304,"Lagoa dos Patos",12 +4311270,"Lagoa dos Três Cantos",20 +3137403,"Lagoa Dourada",12 +3137502,"Lagoa Formosa",12 +3137536,"Lagoa Grande",12 +2608750,"Lagoa Grande",16 +2105963,"Lagoa Grande do Maranhão",9 +2406502,"Lagoa Nova",19 +2918753,"Lagoa Real",5 +2406601,"Lagoa Salgada",19 +5212253,"Lagoa Santa",8 +3137601,"Lagoa Santa",12 +2508307,"Lagoa Seca",14 +4311304,"Lagoa Vermelha",20 +4311254,"Lagoão",20 +3526308,"Lagoinha",24 +2205540,"Lagoinha do Piauí",17 +4209409,"Laguna",23 +5005251,"Laguna Carapã",11 +2918803,"Laje",5 +3302304,"Laje do Muriaé",18 +1712009,"Lajeado",26 +4311403,"Lajeado",20 +4311429,"Lajeado do Bugre",20 +4209458,"Lajeado Grande",23 +2105989,"Lajeado Novo",9 +2918902,"Lajedão",5 +2919009,"Lajedinho",5 +2608800,"Lajedo",16 +2919058,"Lajedo do Tabocal",5 +2406700,"Lajes",19 +2406809,"Lajes Pintadas",19 +3137700,"Lajinha",12 +2919108,"Lamarão",5 +3137809,"Lambari",12 +5105234,"Lambari D'Oeste",10 +3137908,"Lamim",12 +2205607,"Landri Sales",17 +4113205,"Lapa",15 +2919157,"Lapão",5 +3203163,"Laranja da Terra",7 +3138005,"Laranjal",12 +4113254,"Laranjal",15 +1600279,"Laranjal do Jari",3 +3526407,"Laranjal Paulista",24 +2803609,"Laranjeiras",25 +4113304,"Laranjeiras do Sul",15 +3138104,"Lassance",12 +2508406,"Lastro",14 +4209508,"Laurentino",23 +2919207,"Lauro de Freitas",5 +4209607,"Lauro Muller",23 +1712157,"Lavandeira",26 +3526506,"Lavínia",24 +3138203,"Lavras",12 +2307502,"Lavras da Mangabeira",6 +4311502,"Lavras do Sul",20 +3526605,"Lavrinhas",24 +3138302,"Leandro Ferreira",12 +4209706,"Lebon Régis",23 +3526704,"Leme",24 +3138351,"Leme do Prado",12 +2919306,"Lençóis",5 +3526803,"Lençóis Paulista",24 +4209805,"Leoberto Leal",23 +3138401,"Leopoldina",12 +5212303,"Leopoldo de Bulhões",8 +4113403,"Leópolis",15 +4311601,"Liberato Salzano",20 +3138500,"Liberdade",12 +2919405,"Licínio de Almeida",5 +4113429,"Lidianópolis",15 +2106003,"Lima Campos",9 +3138609,"Lima Duarte",12 +3526902,"Limeira",24 +3138625,"Limeira do Oeste",12 +2608909,"Limoeiro",16 +2704203,"Limoeiro de Anadia",2 +1504000,"Limoeiro do Ajuru",13 +2307601,"Limoeiro do Norte",6 +4113452,"Lindoeste",15 +3527009,"Lindóia",24 +4209854,"Lindóia do Sul",23 +4311627,"Lindolfo Collor",20 +4311643,"Linha Nova",20 +3203205,"Linhares",7 +3527108,"Lins",24 +2508505,"Livramento",14 +2919504,"Livramento de Nossa Senhora",5 +1712405,"Lizarda",26 +4113502,"Loanda",15 +4113601,"Lobato",15 +2508554,"Logradouro",14 +4113700,"Londrina",15 +3138658,"Lontra",12 +4209904,"Lontras",23 +3527207,"Lorena",24 +2106102,"Loreto",9 +3527256,"Lourdes",24 +3527306,"Louveira",24 +5105259,"Lucas do Rio Verde",10 +3527405,"Lucélia",24 +2508604,"Lucena",14 +3527504,"Lucianópolis",24 +5105309,"Luciara",10 +2406908,"Lucrécia",19 +3527603,"Luís Antônio",24 +2205706,"Luís Correia",17 +2106201,"Luís Domingues",9 +2919553,"Luís Eduardo Magalhães",5 +2407005,"Luís Gomes",19 +3138674,"Luisburgo",12 +3138682,"Luislândia",12 +4210001,"Luiz Alves",23 +4113734,"Luiziana",15 +3527702,"Luiziânia",24 +3138708,"Luminárias",12 +4113759,"Lunardelli",15 +3527801,"Lupércio",24 +4113809,"Lupionópolis",15 +3527900,"Lutécia",24 +3138807,"Luz",12 +4210035,"Luzerna",23 +5212501,"Luziânia",8 +2205805,"Luzilândia",17 +1712454,"Luzinópolis",26 +3302403,"Macaé",18 +2407104,"Macaíba",19 +2919603,"Macajuba",5 +4311718,"Maçambará",20 +2803708,"Macambira",25 +2609006,"Macaparana",16 +2919702,"Macarani",5 +3528007,"Macatuba",24 +2407203,"Macau",19 +3528106,"Macaubal",24 +2919801,"Macaúbas",5 +3528205,"Macedônia",24 +3138906,"Machacalis",12 +4311700,"Machadinho",20 +3139003,"Machado",12 +2609105,"Machados",16 +4210050,"Macieira",23 +3302452,"Macuco",18 +2919900,"Macururé",5 +2307635,"Madalena",6 +2205854,"Madeiro",17 +2919926,"Madre de Deus",5 +3139102,"Madre de Deus de Minas",12 +2508703,"Mãe d'Água",14 +1504059,"Mãe do Rio",13 +2919959,"Maetinga",5 +4210100,"Mafra",23 +1504109,"Magalhães Barata",13 +2106300,"Magalhães de Almeida",9 +3528304,"Magda",24 +3302502,"Magé",18 +2920007,"Maiquinique",5 +2920106,"Mairi",5 +3528403,"Mairinque",24 +3528502,"Mairiporã",24 +5212600,"Mairipotaba",8 +4210209,"Major Gercino",23 +2704401,"Major Isidoro",2 +2407252,"Major Sales",19 +4210308,"Major Vieira",23 +3139201,"Malacacheta",12 +2920205,"Malhada",5 +2920304,"Malhada de Pedras",5 +2803807,"Malhada dos Bois",25 +2803906,"Malhador",25 +4113908,"Mallet",15 +2508802,"Malta",14 +2508901,"Mamanguape",14 +5212709,"Mambaí",8 +4114005,"Mamborê",15 +3139250,"Mamonas",12 +4311734,"Mampituba",20 +1302504,"Manacapuru",4 +2509008,"Manaíra",14 +1302553,"Manaquiri",4 +2609154,"Manari",16 +4114104,"Mandaguaçu",15 +4114203,"Mandaguari",15 +4114302,"Mandirituba",15 +3528601,"Manduri",24 +4114351,"Manfrinópolis",15 +3139300,"Manga",12 +3302601,"Mangaratiba",18 +4114401,"Mangueirinha",15 +3139409,"Manhuaçu",12 +3139508,"Manhumirim",12 +1302702,"Manicoré",4 +2205904,"Manoel Emídio",17 +4114500,"Manoel Ribas",15 +4311759,"Manoel Viana",20 +2920403,"Manoel Vitorino",5 +2920452,"Mansidão",5 +3139607,"Mantena",12 +3203304,"Mantenópolis",7 +4311775,"Maquiné",20 +3139805,"Mar de Espanha",12 +2704906,"Mar Vermelho",2 +5212808,"Mara Rosa",8 +1302801,"Maraã",4 +1504208,"Marabá",13 +3528700,"Marabá Paulista",24 +2106326,"Maracaçumé",9 +3528809,"Maracaí",24 +4210407,"Maracajá",23 +5005400,"Maracaju",11 +1504307,"Maracanã",13 +2307650,"Maracanaú",6 +2920502,"Maracás",5 +2704500,"Maragogi",2 +2920601,"Maragogipe",5 +2609204,"Maraial",16 +2106359,"Marajá do Sena",9 +2307700,"Maranguape",6 +2106375,"Maranhãozinho",9 +1504406,"Marapanim",13 +3528858,"Marapoama",24 +4311791,"Maratá",20 +3203320,"Marataízes",7 +4311809,"Marau",20 +2920700,"Maraú",5 +2704609,"Maravilha",2 +4210506,"Maravilha",23 +3139706,"Maravilhas",12 +2509057,"Marcação",14 +5105580,"Marcelândia",10 +4311908,"Marcelino Ramos",20 +2407302,"Marcelino Vieira",19 +2920809,"Marcionílio Souza",5 +2307809,"Marco",6 +2205953,"Marcolândia",17 +2206001,"Marcos Parente",17 +4114609,"Marechal Cândido Rondon",15 +2704708,"Marechal Deodoro",2 +3203346,"Marechal Floriano",7 +4210555,"Marema",23 +2509107,"Mari",14 +3139904,"Maria da Fé",12 +4114708,"Maria Helena",15 +4114807,"Marialva",15 +3140001,"Mariana",12 +4311981,"Mariana Pimentel",20 +4312005,"Mariano Moro",20 +1712504,"Marianópolis do Tocantins",26 +3528908,"Mariápolis",24 +2704807,"Maribondo",2 +3302700,"Maricá",18 +3140100,"Marilac",12 +3203353,"Marilândia",7 +4114906,"Marilândia do Sul",15 +4115002,"Marilena",15 +3529005,"Marília",24 +4115101,"Mariluz",15 +4115200,"Maringá",15 +3529104,"Marinópolis",24 +3140159,"Mário Campos",12 +4115309,"Mariópolis",15 +4115358,"Maripá",15 +3140209,"Maripá de Minas",12 +1504422,"Marituba",13 +2509156,"Marizópolis",14 +3140308,"Marliéria",12 +4115408,"Marmeleiro",15 +3140407,"Marmelópolis",12 +4312054,"Marques de Souza",20 +4115457,"Marquinho",15 +3140506,"Martinho Campos",12 +2307908,"Martinópole",6 +3529203,"Martinópolis",24 +2407401,"Martins",19 +3140530,"Martins Soares",12 +2804003,"Maruim",25 +4115507,"Marumbi",15 +5212907,"Marzagão",8 +2920908,"Mascote",5 +2308005,"Massapê",6 +2206050,"Massapê do Piauí",17 +2509206,"Massaranduba",14 +4210605,"Massaranduba",23 +4312104,"Mata",20 +1200336,"Mâncio Lima",1 +1200344,"Manoel Urbano",1 +1200351,"Marechal Thaumaturgo",1 +1302603,"Manaus",4 +1100130,"Machadinho D'Oeste",21 +2921005,"Mata de São João",5 +2705002,"Mata Grande",2 +2106409,"Mata Roma",9 +3140555,"Mata Verde",12 +3529302,"Matão",24 +2509305,"Mataraca",14 +1712702,"Mateiros",26 +4115606,"Matelândia",15 +3140605,"Materlândia",12 +3140704,"Mateus Leme",12 +3171501,"Mathias Lobato",12 +3140803,"Matias Barbosa",12 +3140852,"Matias Cardoso",12 +2206100,"Matias Olímpio",17 +2921054,"Matina",5 +2106508,"Matinha",9 +2509339,"Matinhas",14 +4115705,"Matinhos",15 +3140902,"Matipó",12 +4312138,"Mato Castelhano",20 +2509370,"Mato Grosso",14 +4312153,"Mato Leitão",20 +4312179,"Mato Queimado",20 +4115739,"Mato Rico",15 +3141009,"Mato Verde",12 +2106607,"Matões",9 +2106631,"Matões do Norte",9 +4210704,"Matos Costa",23 +3141108,"Matozinhos",12 +5212956,"Matrinchã",8 +2705101,"Matriz de Camaragibe",2 +5105606,"Matupá",10 +2509396,"Maturéia",14 +3141207,"Matutina",12 +3529401,"Mauá",24 +4115754,"Mauá da Serra",15 +1302900,"Maués",4 +5213004,"Maurilândia",8 +1712801,"Maurilândia do Tocantins",26 +2308104,"Mauriti",6 +2407500,"Maxaranguape",19 +4312203,"Maximiliano de Almeida",20 +1600402,"Mazagão",3 +3141306,"Medeiros",12 +2921104,"Medeiros Neto",5 +4115804,"Medianeira",15 +1504455,"Medicilândia",13 +3141405,"Medina",12 +4210803,"Meleiro",23 +1504505,"Melgaço",13 +3302809,"Mendes",18 +3141504,"Mendes Pimentel",12 +3529500,"Mendonça",24 +4115853,"Mercedes",15 +3141603,"Mercês",12 +3529609,"Meridiano",24 +2308203,"Meruoca",6 +3529658,"Mesópolis",24 +3302858,"Mesquita",18 +3141702,"Mesquita",12 +2705200,"Messias",2 +2407609,"Messias Targino",19 +2206209,"Miguel Alves",17 +2921203,"Miguel Calmon",5 +2206308,"Miguel Leão",17 +3302908,"Miguel Pereira",18 +3529708,"Miguelópolis",24 +2308302,"Milagres",6 +2921302,"Milagres",5 +2106672,"Milagres do Maranhão",9 +2308351,"Milhã",6 +2206357,"Milton Brandão",17 +5213053,"Mimoso de Goiás",8 +3203403,"Mimoso do Sul",7 +5213087,"Minaçu",8 +2705309,"Minador do Negrão",2 +4312252,"Minas do Leão",20 +3141801,"Minas Novas",12 +3141900,"Minduri",12 +5213103,"Mineiros",8 +3529807,"Mineiros do Tietê",24 +3530003,"Mira Estrela",24 +3142007,"Mirabela",12 +3529906,"Miracatu",24 +3303005,"Miracema",18 +1713205,"Miracema do Tocantins",26 +2106706,"Mirador",9 +4115903,"Mirador",15 +3142106,"Miradouro",12 +4312302,"Miraguaí",20 +3142205,"Miraí",12 +2308377,"Miraíma",6 +5005608,"Miranda",11 +2106755,"Miranda do Norte",9 +2609303,"Mirandiba",16 +3530102,"Mirandópolis",24 +2921401,"Mirangaba",5 +1713304,"Miranorte",26 +2921450,"Mirante",5 +3530201,"Mirante do Paranapanema",24 +4116000,"Miraselva",15 +3530300,"Mirassol",24 +5105622,"Mirassol d'Oeste",10 +3530409,"Mirassolândia",24 +3142254,"Miravânia",12 +4210852,"Mirim Doce",23 +2106805,"Mirinzal",9 +4116059,"Missal",15 +2308401,"Missão Velha",6 +1504604,"Mocajuba",13 +3530508,"Mococa",24 +4210902,"Modelo",23 +3142304,"Moeda",12 +3142403,"Moema",12 +2509404,"Mogeiro",14 +3530607,"Mogi das Cruzes",24 +3530706,"Mogi Guaçu",24 +3530805,"Mogi Mirim",24 +5213400,"Moiporá",8 +2804102,"Moita Bonita",25 +1504703,"Moju",13 +1504752,"Mojuí dos Campos",13 +2308500,"Mombaça",6 +3530904,"Mombuca",24 +2106904,"Monção",9 +3531001,"Monções",24 +4211009,"Mondaí",23 +3531100,"Mongaguá",24 +3142502,"Monjolos",12 +2206407,"Monsenhor Gil",17 +2206506,"Monsenhor Hipólito",17 +3142601,"Monsenhor Paulo",12 +2308609,"Monsenhor Tabosa",6 +2509503,"Montadas",14 +3142700,"Montalvânia",12 +3203502,"Montanha",7 +2407708,"Montanhas",19 +4312351,"Montauri",20 +1504802,"Monte Alegre",13 +2407807,"Monte Alegre",19 +5213509,"Monte Alegre de Goiás",8 +3142809,"Monte Alegre de Minas",12 +2804201,"Monte Alegre de Sergipe",25 +2206605,"Monte Alegre do Piauí",17 +3531209,"Monte Alegre do Sul",24 +4312377,"Monte Alegre dos Campos",20 +3531308,"Monte Alto",24 +3531407,"Monte Aprazível",24 +3142908,"Monte Azul",12 +3531506,"Monte Azul Paulista",24 +3143005,"Monte Belo",12 +4312385,"Monte Belo do Sul",20 +4211058,"Monte Carlo",23 +3143104,"Monte Carmelo",12 +4211108,"Monte Castelo",23 +1101302,"Mirante da Serra",21 +3531605,"Monte Castelo",24 +2407906,"Monte das Gameleiras",19 +1713601,"Monte do Carmo",26 +3143153,"Monte Formoso",12 +2509602,"Monte Horebe",14 +3531803,"Monte Mor",24 +2921500,"Monte Santo",5 +3143203,"Monte Santo de Minas",12 +1713700,"Monte Santo do Tocantins",26 +3143401,"Monte Sião",12 +2509701,"Monteiro",14 +3531704,"Monteiro Lobato",24 +2705408,"Monteirópolis",2 +4312401,"Montenegro",20 +2107001,"Montes Altos",9 +3143302,"Montes Claros",12 +5213707,"Montes Claros de Goiás",8 +3143450,"Montezuma",12 +5213756,"Montividiu",8 +5213772,"Montividiu do Norte",8 +2308708,"Morada Nova",6 +3143500,"Morada Nova de Minas",12 +2308807,"Moraújo",6 +2614303,"Moreilândia",16 +4116109,"Moreira Sales",15 +2609402,"Moreno",16 +4312427,"Mormaço",20 +2921609,"Morpará",5 +4116208,"Morretes",15 +5213806,"Morrinhos",8 +2308906,"Morrinhos",6 +4312443,"Morrinhos do Sul",20 +3531902,"Morro Agudo",24 +5213855,"Morro Agudo de Goiás",8 +2206654,"Morro Cabeça no Tempo",17 +4211207,"Morro da Fumaça",23 +3143609,"Morro da Garça",12 +2921708,"Morro do Chapéu",5 +2206670,"Morro do Chapéu do Piauí",17 +3143708,"Morro do Pilar",12 +4211256,"Morro Grande",23 +4312450,"Morro Redondo",20 +4312476,"Morro Reuter",20 +2107100,"Morros",9 +2921807,"Mortugaba",5 +3532009,"Morungaba",24 +5213905,"Mossâmedes",8 +2408003,"Mossoró",19 +4312500,"Mostardas",20 +3532058,"Motuca",24 +5214002,"Mozarlândia",8 +1504901,"Muaná",13 +1400308,"Mucajaí",22 +2309003,"Mucambo",6 +2921906,"Mucugê",5 +4312609,"Muçum",20 +2922003,"Mucuri",5 +3203601,"Mucurici",7 +4312617,"Muitos Capões",20 +4312625,"Muliterno",20 +2509800,"Mulungu",14 +2309102,"Mulungu",6 +2922052,"Mulungu do Morro",5 +2922102,"Mundo Novo",5 +5005681,"Mundo Novo",11 +5214051,"Mundo Novo",8 +3143807,"Munhoz",12 +4116307,"Munhoz de Melo",15 +2922201,"Muniz Ferreira",5 +3203700,"Muniz Freire",7 +2922250,"Muquém de São Francisco",5 +3203809,"Muqui",7 +3143906,"Muriaé",12 +2804300,"Muribeca",25 +2705507,"Murici",2 +2206696,"Murici dos Portelas",17 +1713957,"Muricilândia",26 +2922300,"Muritiba",5 +3532108,"Murutinga do Sul",24 +2922409,"Mutuípe",5 +3144003,"Mutum",12 +5214101,"Mutunópolis",8 +3144102,"Muzambinho",12 +3144201,"Nacip Raydan",12 +3532157,"Nantes",24 +3144300,"Nanuque",12 +4312658,"Não-Me-Toque",20 +3144359,"Naque",12 +3532207,"Narandiba",24 +3144375,"Natalândia",12 +3144409,"Natércia",12 +1714203,"Natividade",26 +3303104,"Natividade",18 +3532306,"Natividade da Serra",24 +2509909,"Natuba",14 +4211306,"Navegantes",23 +5005707,"Naviraí",11 +2922508,"Nazaré",5 +1714302,"Nazaré",26 +2609501,"Nazaré da Mata",16 +2206704,"Nazaré do Piauí",17 +3532405,"Nazaré Paulista",24 +3144508,"Nazareno",12 +2510006,"Nazarezinho",14 +2206720,"Nazária",17 +5214408,"Nazário",8 +2804409,"Neópolis",25 +3144607,"Nepomuceno",12 +5214507,"Nerópolis",8 +3532504,"Neves Paulista",24 +1303007,"Nhamundá",4 +3532603,"Nhandeara",24 +4312674,"Nicolau Vergueiro",20 +2922607,"Nilo Peçanha",5 +3303203,"Nilópolis",18 +2107209,"Nina Rodrigues",9 +3144656,"Ninheira",12 +5005806,"Nioaque",11 +3532702,"Nipoã",24 +5214606,"Niquelândia",8 +2408201,"Nísia Floresta",19 +3303302,"Niterói",18 +5105903,"Nobres",10 +4312708,"Nonoai",20 +2922656,"Nordestina",5 +1400407,"Normandia",22 +5106000,"Nortelândia",10 +2804458,"Nossa Senhora Aparecida",25 +2804508,"Nossa Senhora da Glória",25 +2804607,"Nossa Senhora das Dores",25 +4116406,"Nossa Senhora das Graças",15 +2804706,"Nossa Senhora de Lourdes",25 +2206753,"Nossa Senhora de Nazaré",17 +5106109,"Nossa Senhora do Livramento",10 +2804805,"Nossa Senhora do Socorro",25 +2206803,"Nossa Senhora dos Remédios",17 +3532801,"Nova Aliança",24 +4116505,"Nova Aliança do Ivaí",15 +4312757,"Nova Alvorada",20 +5006002,"Nova Alvorada do Sul",11 +5214705,"Nova América",8 +4116604,"Nova América da Colina",15 +5006200,"Nova Andradina",11 +4312807,"Nova Araçá",20 +4116703,"Nova Aurora",15 +5214804,"Nova Aurora",8 +5106158,"Nova Bandeirantes",10 +4312906,"Nova Bassano",20 +3144672,"Nova Belém",12 +4312955,"Nova Boa Vista",20 +5106208,"Nova Brasilândia",10 +1100148,"Nova Brasilândia D'Oeste",21 +4313003,"Nova Bréscia",20 +3532827,"Nova Campina",24 +2922706,"Nova Canaã",5 +5106216,"Nova Canaã do Norte",10 +3532843,"Nova Canaã Paulista",24 +4313011,"Nova Candelária",20 +4116802,"Nova Cantu",15 +3532868,"Nova Castilho",24 +2107258,"Nova Colinas",9 +5214838,"Nova Crixás",8 +2408300,"Nova Cruz",19 +3144706,"Nova Era",12 +4211405,"Nova Erechim",23 +4116901,"Nova Esperança",15 +1504950,"Nova Esperança do Piriá",13 +4116950,"Nova Esperança do Sudoeste",15 +4313037,"Nova Esperança do Sul",20 +3532900,"Nova Europa",24 +4117008,"Nova Fátima",15 +2922730,"Nova Fátima",5 +2510105,"Nova Floresta",14 +3303401,"Nova Friburgo",18 +5214861,"Nova Glória",8 +3533007,"Nova Granada",24 +5108808,"Nova Guarita",10 +3533106,"Nova Guataporanga",24 +4313060,"Nova Hartz",20 +2922755,"Nova Ibiá",5 +3303500,"Nova Iguaçu",18 +5214879,"Nova Iguaçu de Goiás",8 +3533205,"Nova Independência",24 +2107308,"Nova Iorque",9 +1504976,"Nova Ipixuna",13 +4211454,"Nova Itaberaba",23 +2922805,"Nova Itarana",5 +5106182,"Nova Lacerda",10 +4117057,"Nova Laranjeiras",15 +3144805,"Nova Lima",12 +4117107,"Nova Londrina",15 +3533304,"Nova Luzitânia",24 +5108857,"Nova Marilândia",10 +5108907,"Nova Maringá",10 +3144904,"Nova Módica",12 +5108956,"Nova Monte Verde",10 +5106224,"Nova Mutum",10 +5106174,"Nova Nazaré",10 +3533403,"Nova Odessa",24 +4117206,"Nova Olímpia",15 +5106232,"Nova Olímpia",10 +1714880,"Nova Olinda",26 +2309201,"Nova Olinda",6 +2510204,"Nova Olinda",14 +2107357,"Nova Olinda do Maranhão",9 +1303106,"Nova Olinda do Norte",4 +4313086,"Nova Pádua",20 +4313102,"Nova Palma",20 +2510303,"Nova Palmeira",14 +4313201,"Nova Petrópolis",20 +3145000,"Nova Ponte",12 +3145059,"Nova Porteirinha",12 +4313300,"Nova Prata",20 +4117255,"Nova Prata do Iguaçu",15 +4313334,"Nova Ramada",20 +2922854,"Nova Redenção",5 +3145109,"Nova Resende",12 +5214903,"Nova Roma",8 +4313359,"Nova Roma do Sul",20 +1715002,"Nova Rosalândia",26 +2309300,"Nova Russas",6 +4117214,"Nova Santa Bárbara",15 +5106190,"Nova Santa Helena",10 +4313375,"Nova Santa Rita",20 +2207959,"Nova Santa Rita",17 +4117222,"Nova Santa Rosa",15 +3145208,"Nova Serrana",12 +2922904,"Nova Soure",5 +4117271,"Nova Tebas",15 +1505007,"Nova Timboteua",13 +4211504,"Nova Trento",23 +5106240,"Nova Ubiratã",10 +3136603,"Nova União",12 +3203908,"Nova Venécia",7 +4211603,"Nova Veneza",23 +5215009,"Nova Veneza",8 +2923001,"Nova Viçosa",5 +5106257,"Nova Xavantina",10 +3533254,"Novais",24 +1715101,"Novo Acordo",26 +1303205,"Novo Airão",4 +1715150,"Novo Alegre",26 +1303304,"Novo Aripuanã",4 +4313490,"Novo Barreiro",20 +5215207,"Novo Brasil",8 +4313391,"Novo Cabrais",20 +3145307,"Novo Cruzeiro",12 +5215231,"Novo Gama",8 +4313409,"Novo Hamburgo",20 +4211652,"Novo Horizonte",23 +3533502,"Novo Horizonte",24 +2923035,"Novo Horizonte",5 +5106273,"Novo Horizonte do Norte",10 +5006259,"Novo Horizonte do Sul",11 +4117297,"Novo Itacolomi",15 +1715259,"Novo Jardim",26 +2705606,"Novo Lino",2 +4313425,"Novo Machado",20 +5106265,"Novo Mundo",10 +2309409,"Novo Oriente",6 +3145356,"Novo Oriente de Minas",12 +2206902,"Novo Oriente do Piauí",17 +5215256,"Novo Planalto",8 +1505031,"Novo Progresso",13 +1505064,"Novo Repartimento",13 +2206951,"Novo Santo Antônio",17 +5106315,"Novo Santo Antônio",10 +5106281,"Novo São Joaquim",10 +4313441,"Novo Tiradentes",20 +2923050,"Novo Triunfo",5 +4313466,"Novo Xingu",20 +3145372,"Novorizonte",12 +3533601,"Nuporanga",24 +1505106,"Óbidos",13 +2309458,"Ocara",6 +3533700,"Ocauçu",24 +2207009,"Oeiras",17 +1505205,"Oeiras do Pará",13 +1600501,"Oiapoque",3 +3145406,"Olaria",12 +3533809,"Óleo",24 +2510402,"Olho d'Água",14 +2107407,"Olho d'Água das Cunhãs",9 +2705705,"Olho d'Água das Flores",2 +2705804,"Olho d'Água do Casado",2 +2207108,"Olho D'Água do Piauí",17 +2705903,"Olho d'Água Grande",2 +2408409,"Olho-d'Água do Borges",19 +3145455,"Olhos d'Água",12 +3533908,"Olímpia",24 +3145505,"Olímpio Noronha",12 +2609600,"Olinda",16 +2107456,"Olinda Nova do Maranhão",9 +2923100,"Olindina",5 +2510501,"Olivedos",14 +3145604,"Oliveira",12 +1715507,"Oliveira de Fátima",26 +2923209,"Oliveira dos Brejinhos",5 +3145703,"Oliveira Fortes",12 +2706000,"Olivença",2 +1101435,"Nova União",21 +1100502,"Novo Horizonte do Oeste",21 +3145802,"Onça de Pitangui",12 +3534005,"Onda Verde",24 +3145851,"Oratórios",12 +3534104,"Oriente",24 +3534203,"Orindiúva",24 +1505304,"Oriximiná",13 +3145877,"Orizânia",12 +5215306,"Orizona",8 +3534302,"Orlândia",24 +4211702,"Orleans",23 +2609709,"Orobó",16 +2609808,"Orocó",16 +2309508,"Orós",6 +4117305,"Ortigueira",15 +3534401,"Osasco",24 +3534500,"Oscar Bressane",24 +4313508,"Osório",20 +3534609,"Osvaldo Cruz",24 +4211751,"Otacílio Costa",23 +1505403,"Ourém",13 +2923308,"Ouriçangas",5 +2609907,"Ouricuri",16 +1505437,"Ourilândia do Norte",13 +3534708,"Ourinhos",24 +4117404,"Ourizona",15 +4211801,"Ouro",23 +3145901,"Ouro Branco",12 +2408508,"Ouro Branco",19 +2706109,"Ouro Branco",2 +3146008,"Ouro Fino",12 +3146107,"Ouro Preto",12 +2510600,"Ouro Velho",14 +4211850,"Ouro Verde",23 +3534807,"Ouro Verde",24 +5215405,"Ouro Verde de Goiás",8 +3146206,"Ouro Verde de Minas",12 +4117453,"Ouro Verde do Oeste",15 +3534757,"Ouroeste",24 +2923357,"Ourolândia",5 +5215504,"Ouvidor",8 +3534906,"Pacaembu",24 +1505486,"Pacajá",13 +2309607,"Pacajus",6 +1400456,"Pacaraima",22 +2309706,"Pacatuba",6 +2804904,"Pacatuba",25 +2107506,"Paço do Lumiar",9 +2309805,"Pacoti",6 +2309904,"Pacujá",6 +5215603,"Padre Bernardo",8 +3146255,"Padre Carvalho",12 +2207207,"Padre Marcos",17 +3146305,"Padre Paraíso",12 +2207306,"Paes Landim",17 +3146552,"Pai Pedro",12 +4211876,"Paial",23 +4117503,"Paiçandu",15 +4313607,"Paim Filho",20 +3146404,"Paineiras",12 +4211892,"Painel",23 +3146503,"Pains",12 +3146602,"Paiva",12 +2207355,"Pajeú do Piauí",17 +2706208,"Palestina",2 +3535002,"Palestina",24 +5215652,"Palestina de Goiás",8 +1505494,"Palestina do Pará",13 +2310001,"Palhano",6 +4211900,"Palhoça",23 +3146701,"Palma",12 +4212007,"Palma Sola",23 +2310100,"Palmácia",6 +2610004,"Palmares",16 +4313656,"Palmares do Sul",20 +3535101,"Palmares Paulista",24 +4117602,"Palmas",15 +2923407,"Palmas de Monte Alto",5 +4117701,"Palmeira",15 +4212056,"Palmeira",23 +3535200,"Palmeira d'Oeste",24 +4313706,"Palmeira das Missões",20 +2207405,"Palmeira do Piauí",17 +2706307,"Palmeira dos Índios",2 +2207504,"Palmeirais",17 +2107605,"Palmeirândia",9 +1715705,"Palmeirante",26 +2923506,"Palmeiras",5 +5215702,"Palmeiras de Goiás",8 +1713809,"Palmeiras do Tocantins",26 +2610103,"Palmeirina",16 +1715754,"Palmeirópolis",26 +5215801,"Palmelo",8 +5215900,"Palminópolis",8 +3535309,"Palmital",24 +4117800,"Palmital",15 +4313805,"Palmitinho",20 +4212106,"Palmitos",23 +3146750,"Palmópolis",12 +4117909,"Palotina",15 +5216007,"Panamá",8 +4313904,"Panambi",20 +3204005,"Pancas",7 +2610202,"Panelas",16 +3535408,"Panorama",24 +4313953,"Pantano Grande",20 +2706406,"Pão de Açúcar",2 +3146909,"Papagaios",12 +4212205,"Papanduva",23 +2207553,"Paquetá",17 +3147105,"Pará de Minas",12 +3303609,"Paracambi",18 +3147006,"Paracatu",12 +2310209,"Paracuru",6 +1505502,"Paragominas",13 +3147204,"Paraguaçu",12 +3535507,"Paraguaçu Paulista",24 +4314001,"Paraí",20 +3303708,"Paraíba do Sul",18 +2107704,"Paraibano",9 +3535606,"Paraibuna",24 +2310258,"Paraipaba",6 +3535705,"Paraíso",24 +4212239,"Paraíso",23 +5006275,"Paraíso das Águas",11 +4118006,"Paraíso do Norte",15 +4314027,"Paraíso do Sul",20 +1716109,"Paraíso do Tocantins",26 +3147303,"Paraisópolis",12 +2310308,"Parambu",6 +2923605,"Paramirim",5 +2310407,"Paramoti",6 +1716208,"Paranã",26 +2408607,"Paraná",19 +4118105,"Paranacity",15 +4118204,"Paranaguá",15 +5006309,"Paranaíba",11 +5216304,"Paranaiguara",8 +5106299,"Paranaíta",10 +3535804,"Paranapanema",24 +4118303,"Paranapoema",15 +3535903,"Paranapuã",24 +2610301,"Paranatama",16 +5106307,"Paranatinga",10 +4118402,"Paranavaí",15 +5006358,"Paranhos",11 +3147402,"Paraopeba",12 +3536000,"Parapuã",24 +2510659,"Parari",14 +2923704,"Paratinga",5 +3303807,"Paraty",18 +2408706,"Paraú",19 +1505536,"Parauapebas",13 +5216403,"Paraúna",8 +2408805,"Parazinho",19 +3536109,"Pardinho",24 +4314035,"Pareci Novo",20 +1101450,"Parecis",21 +2408904,"Parelhas",19 +2706422,"Pariconha",2 +1303403,"Parintins",4 +2923803,"Paripiranga",5 +2706448,"Paripueira",2 +3536208,"Pariquera-Açu",24 +3536257,"Parisi",24 +2207603,"Parnaguá",17 +2207702,"Parnaíba",17 +2403251,"Parnamirim",19 +2610400,"Parnamirim",16 +2107803,"Parnarama",9 +4314050,"Parobé",20 +2409100,"Passa e Fica",19 +3147600,"Passa Quatro",12 +4314068,"Passa Sete",20 +3147709,"Passa Tempo",12 +3147808,"Passa-Vinte",12 +3147501,"Passabém",12 +2409209,"Passagem",19 +2510709,"Passagem",14 +2107902,"Passagem Franca",9 +2207751,"Passagem Franca do Piauí",17 +2610509,"Passira",16 +2706505,"Passo de Camaragibe",2 +4212254,"Passo de Torres",23 +4314076,"Passo do Sobrado",20 +4314100,"Passo Fundo",20 +3147907,"Passos",12 +4212270,"Passos Maia",23 +2108009,"Pastos Bons",9 +3147956,"Patis",12 +4118451,"Pato Bragado",15 +4118501,"Pato Branco",15 +2510808,"Patos",14 +3148004,"Patos de Minas",12 +2207777,"Patos do Piauí",17 +3148103,"Patrocínio",12 +3148202,"Patrocínio do Muriaé",12 +3536307,"Patrocínio Paulista",24 +2409308,"Patu",19 +3303856,"Paty do Alferes",18 +2923902,"Pau Brasil",5 +1505551,"Pau d'Arco",13 +1716307,"Pau D'Arco",26 +2207793,"Pau D'Arco do Piauí",17 +2409407,"Pau dos Ferros",19 +2610608,"Paudalho",16 +1303502,"Pauini",4 +3148301,"Paula Cândido",12 +4118600,"Paula Freitas",15 +3536406,"Paulicéia",24 +3536505,"Paulínia",24 +2108058,"Paulino Neves",9 +2510907,"Paulista",14 +2610707,"Paulista",16 +2207801,"Paulistana",17 +3536570,"Paulistânia",24 +3148400,"Paulistas",12 +2924009,"Paulo Afonso",5 +4314134,"Paulo Bento",20 +3536604,"Paulo de Faria",24 +4118709,"Paulo Frontin",15 +2706604,"Paulo Jacinto",2 +4212304,"Paulo Lopes",23 +2108108,"Paulo Ramos",9 +3148509,"Pavão",12 +4314159,"Paverama",20 +2207850,"Pavussu",17 +2924058,"Pé de Serra",5 +4118808,"Peabiru",15 +3148608,"Peçanha",12 +3536703,"Pederneiras",24 +2610806,"Pedra",16 +3148707,"Pedra Azul",12 +3536802,"Pedra Bela",24 +3148756,"Pedra Bonita",12 +2511004,"Pedra Branca",14 +2310506,"Pedra Branca",6 +1600154,"Pedra Branca do Amapari",3 +3148806,"Pedra do Anta",12 +3148905,"Pedra do Indaiá",12 +3149002,"Pedra Dourada",12 +2409506,"Pedra Grande",19 +2511103,"Pedra Lavrada",14 +2805000,"Pedra Mole",25 +2409605,"Pedra Preta",19 +5106372,"Pedra Preta",10 +3149101,"Pedralva",12 +3536901,"Pedranópolis",24 +2924108,"Pedrão",5 +4314175,"Pedras Altas",20 +2511202,"Pedras de Fogo",14 +3149150,"Pedras de Maria da Cruz",12 +4212403,"Pedras Grandes",23 +3537008,"Pedregulho",24 +3537107,"Pedreira",24 +2108207,"Pedreiras",9 +2805109,"Pedrinhas",25 +3537156,"Pedrinhas Paulista",24 +3149200,"Pedrinópolis",12 +1716505,"Pedro Afonso",26 +2924207,"Pedro Alexandre",5 +2409704,"Pedro Avelino",19 +3204054,"Pedro Canário",7 +3537206,"Pedro de Toledo",24 +2108256,"Pedro do Rosário",9 +5006408,"Pedro Gomes",11 +2207900,"Pedro II",17 +2207934,"Pedro Laurentino",17 +3149309,"Pedro Leopoldo",12 +4314209,"Pedro Osório",20 +2512721,"Pedro Régis",14 +3149408,"Pedro Teixeira",12 +2409803,"Pedro Velho",19 +1716604,"Peixe",26 +1505601,"Peixe-Boi",13 +5106422,"Peixoto de Azevedo",10 +4314308,"Pejuçara",20 +4314407,"Pelotas",20 +2310605,"Penaforte",6 +2108306,"Penalva",9 +3537305,"Penápolis",24 +2409902,"Pendências",19 +2706703,"Penedo",2 +4212502,"Penha",23 +2310704,"Pentecoste",6 +3149507,"Pequeri",12 +3149606,"Pequi",12 +1716653,"Pequizeiro",26 +3149705,"Perdigão",12 +3149804,"Perdizes",12 +3149903,"Perdões",12 +3537404,"Pereira Barreto",24 +3537503,"Pereiras",24 +2310803,"Pereiro",6 +2108405,"Peri Mirim",9 +3149952,"Periquito",12 +4212601,"Peritiba",23 +2108454,"Peritoró",9 +4118857,"Perobal",15 +4118907,"Pérola",15 +4119004,"Pérola d'Oeste",15 +5216452,"Perolândia",8 +3537602,"Peruíbe",24 +3150000,"Pescador",12 +4212650,"Pescaria Brava",23 +2610905,"Pesqueira",16 +2611002,"Petrolândia",16 +4212700,"Petrolândia",23 +2611101,"Petrolina",16 +5216809,"Petrolina de Goiás",8 +3303906,"Petrópolis",18 +2706802,"Piaçabuçu",2 +3537701,"Piacatu",24 +2511301,"Piancó",14 +2924306,"Piatã",5 +3150109,"Piau",12 +4314423,"Picada Café",20 +1505635,"Piçarra",13 +2208007,"Picos",17 +2511400,"Picuí",14 +3537800,"Piedade",24 +3150158,"Piedade de Caratinga",12 +3150208,"Piedade de Ponte Nova",12 +3150307,"Piedade do Rio Grande",12 +3150406,"Piedade dos Gerais",12 +4119103,"Piên",15 +2924405,"Pilão Arcado",5 +2511509,"Pilar",14 +2706901,"Pilar",2 +5216908,"Pilar de Goiás",8 +3537909,"Pilar do Sul",24 +2410009,"Pilões",19 +2511608,"Pilões",14 +2511707,"Pilõezinhos",14 +3150505,"Pimenta",12 +2208106,"Pimenteiras",17 +2924504,"Pindaí",5 +3538006,"Pindamonhangaba",24 +2108504,"Pindaré-Mirim",9 +2707008,"Pindoba",2 +2924603,"Pindobaçu",5 +3538105,"Pindorama",24 +1717008,"Pindorama do Tocantins",26 +2310852,"Pindoretama",6 +3150539,"Pingo-d'Água",12 +4119152,"Pinhais",15 +4314456,"Pinhal",20 +4314464,"Pinhal da Serra",20 +4119251,"Pinhal de São Bento",15 +4314472,"Pinhal Grande",20 +4119202,"Pinhalão",15 +3538204,"Pinhalzinho",24 +4212908,"Pinhalzinho",23 +2805208,"Pinhão",25 +4119301,"Pinhão",15 +3303955,"Pinheiral",18 +4314498,"Pinheirinho do Vale",20 +2108603,"Pinheiro",9 +4314506,"Pinheiro Machado",20 +4213005,"Pinheiro Preto",23 +3204104,"Pinheiros",7 +2924652,"Pintadas",5 +4314548,"Pinto Bandeira",20 +3150570,"Pintópolis",12 +2208205,"Pio IX",17 +2108702,"Pio XII",9 +3538303,"Piquerobi",24 +2310902,"Piquet Carneiro",6 +3538501,"Piquete",24 +3538600,"Piracaia",24 +5217104,"Piracanjuba",8 +3150604,"Piracema",12 +3538709,"Piracicaba",24 +2208304,"Piracuruca",17 +3304003,"Piraí",18 +2924678,"Piraí do Norte",5 +4119400,"Piraí do Sul",15 +3538808,"Piraju",24 +3150703,"Pirajuba",12 +3538907,"Pirajuí",24 +2805307,"Pirambu",25 +3150802,"Piranga",12 +3539004,"Pirangi",24 +3150901,"Piranguçu",12 +3151008,"Piranguinho",12 +2707107,"Piranhas",2 +5217203,"Piranhas",8 +2108801,"Pirapemas",9 +3151107,"Pirapetinga",12 +4314555,"Pirapó",20 +3151206,"Pirapora",12 +3539103,"Pirapora do Bom Jesus",24 +3539202,"Pirapozinho",24 +4119509,"Piraquara",15 +1717206,"Piraquê",26 +3539301,"Pirassununga",24 +4314605,"Piratini",20 +3539400,"Piratininga",24 +4213104,"Piratuba",23 +3151305,"Piraúba",12 +5217302,"Pirenópolis",8 +5217401,"Pires do Rio",8 +2310951,"Pires Ferreira",6 +2924702,"Piripá",5 +2208403,"Piripiri",17 +2924801,"Piritiba",5 +2511806,"Pirpirituba",14 +4119608,"Pitanga",15 +3539509,"Pitangueiras",24 +4119657,"Pitangueiras",15 +3151404,"Pitangui",12 +2511905,"Pitimbu",14 +1717503,"Pium",26 +3204203,"Piúma",7 +3151503,"Piumhi",12 +1505650,"Placas",13 +5217609,"Planaltina",8 +4119707,"Planaltina do Paraná",15 +2924900,"Planaltino",5 +2925006,"Planalto",5 +4314704,"Planalto",20 +3539608,"Planalto",24 +4119806,"Planalto",15 +4213153,"Planalto Alegre",23 +5106455,"Planalto da Serra",10 +3151602,"Planura",12 +3539707,"Platina",24 +3539806,"Poá",24 +2611200,"Poção",16 +2108900,"Poção de Pedras",9 +2512002,"Pocinhos",14 +2410108,"Poço Branco",19 +2512036,"Poço Dantas",14 +4314753,"Poço das Antas",20 +2707206,"Poço das Trincheiras",2 +2512077,"Poço de José de Moura",14 +3151701,"Poço Fundo",12 +2805406,"Poço Redondo",25 +2805505,"Poço Verde",25 +2925105,"Poções",5 +5106505,"Poconé",10 +3151800,"Poços de Caldas",12 +3151909,"Pocrane",12 +2925204,"Pojuca",5 +3539905,"Poloni",24 +2512101,"Pombal",14 +2611309,"Pombos",16 +4213203,"Pomerode",23 +3540002,"Pompéia",24 +3152006,"Pompéu",12 +3540101,"Pongaí",24 +1505700,"Ponta de Pedras",13 +4119905,"Ponta Grossa",15 +5006606,"Ponta Porã",11 +3540200,"Pontal",24 +5106653,"Pontal do Araguaia",10 +4119954,"Pontal do Paraná",15 +5217708,"Pontalina",8 +3540259,"Pontalinda",24 +4314779,"Pontão",20 +4213302,"Ponte Alta",23 +1717800,"Ponte Alta do Bom Jesus",26 +4213351,"Ponte Alta do Norte",23 +1717909,"Ponte Alta do Tocantins",26 +5106703,"Ponte Branca",10 +3152105,"Ponte Nova",12 +4314787,"Ponte Preta",20 +4213401,"Ponte Serrada",23 +5106752,"Pontes e Lacerda",10 +3540309,"Pontes Gestal",24 +3204252,"Ponto Belo",7 +3152131,"Ponto Chique",12 +3152170,"Ponto dos Volantes",12 +1100189,"Pimenta Bueno",21 +1101468,"Pimenteiras do Oeste",21 +2925253,"Ponto Novo",5 +3540408,"Populina",24 +2311009,"Poranga",6 +3540507,"Porangaba",24 +5218003,"Porangatu",8 +3304102,"Porciúncula",18 +4120002,"Porecatu",15 +2410207,"Portalegre",19 +4314803,"Portão",20 +5218052,"Porteirão",8 +2311108,"Porteiras",6 +3152204,"Porteirinha",12 +1505809,"Portel",13 +5218102,"Portelândia",8 +2208502,"Porto",17 +5106778,"Porto Alegre do Norte",10 +2208551,"Porto Alegre do Piauí",17 +1718006,"Porto Alegre do Tocantins",26 +4120101,"Porto Amazonas",15 +4120150,"Porto Barreiro",15 +4213500,"Porto Belo",23 +2707305,"Porto Calvo",2 +2805604,"Porto da Folha",25 +1505908,"Porto de Moz",13 +2707404,"Porto de Pedras",2 +2410256,"Porto do Mangue",19 +5106802,"Porto dos Gaúchos",10 +5106828,"Porto Esperidião",10 +5106851,"Porto Estrela",10 +3540606,"Porto Feliz",24 +3540705,"Porto Ferreira",24 +3152303,"Porto Firme",12 +2109007,"Porto Franco",9 +1600535,"Porto Grande",3 +4315008,"Porto Lucena",20 +4315057,"Porto Mauá",20 +5006903,"Porto Murtinho",11 +1718204,"Porto Nacional",26 +3304110,"Porto Real",18 +2707503,"Porto Real do Colégio",2 +4120200,"Porto Rico",15 +2109056,"Porto Rico do Maranhão",9 +2925303,"Porto Seguro",5 +4213609,"Porto União",23 +4315073,"Porto Vera Cruz",20 +4120309,"Porto Vitória",15 +4315107,"Porto Xavier",20 +5218300,"Posse",8 +3152402,"Poté",12 +2311207,"Potengi",6 +3540754,"Potim",24 +2925402,"Potiraguá",5 +3540804,"Potirendaba",24 +2311231,"Potiretama",6 +3152501,"Pouso Alegre",12 +3152600,"Pouso Alto",12 +4315131,"Pouso Novo",20 +4213708,"Pouso Redondo",23 +5107008,"Poxoréu",10 +3540853,"Pracinha",24 +1600550,"Pracuúba",3 +2925501,"Prado",5 +4120333,"Prado Ferreira",15 +3540903,"Pradópolis",24 +3152709,"Prados",12 +3541000,"Praia Grande",24 +4213807,"Praia Grande",23 +1718303,"Praia Norte",26 +1506005,"Prainha",13 +4120358,"Pranchita",15 +3152808,"Prata",12 +2512200,"Prata",14 +2208601,"Prata do Piauí",17 +3541059,"Pratânia",24 +3152907,"Pratápolis",12 +3153004,"Pratinha",12 +3541109,"Presidente Alves",24 +3541208,"Presidente Bernardes",24 +3153103,"Presidente Bernardes",12 +4213906,"Presidente Castello Branco",23 +4120408,"Presidente Castelo Branco",15 +2925600,"Presidente Dutra",5 +2109106,"Presidente Dutra",9 +3541307,"Presidente Epitácio",24 +1303536,"Presidente Figueiredo",4 +4214003,"Presidente Getúlio",23 +2925709,"Presidente Jânio Quadros",5 +3153202,"Presidente Juscelino",12 +2109205,"Presidente Juscelino",9 +1718402,"Presidente Kennedy",26 +3204302,"Presidente Kennedy",7 +3153301,"Presidente Kubitschek",12 +4315149,"Presidente Lucena",20 +2109239,"Presidente Médici",9 +4214102,"Presidente Nereu",23 +3153400,"Presidente Olegário",12 +3541406,"Presidente Prudente",24 +2109270,"Presidente Sarney",9 +2925758,"Presidente Tancredo Neves",5 +2109304,"Presidente Vargas",9 +3541505,"Presidente Venceslau",24 +2611408,"Primavera",16 +1506104,"Primavera",13 +5107040,"Primavera do Leste",10 +2109403,"Primeira Cruz",9 +4120507,"Primeiro de Maio",15 +4214151,"Princesa",23 +2512309,"Princesa Isabel",14 +5218391,"Professor Jamil",8 +4315156,"Progresso",20 +3541604,"Promissão",24 +2805703,"Propriá",25 +4315172,"Protásio Alves",20 +3153608,"Prudente de Morais",12 +4120606,"Prudentópolis",15 +1718451,"Pugmil",26 +2410405,"Pureza",19 +4315206,"Putinga",20 +2512408,"Puxinanã",14 +3541653,"Quadra",24 +4315305,"Quaraí",20 +3153707,"Quartel Geral",12 +4120655,"Quarto Centenário",15 +3541703,"Quatá",24 +4120705,"Quatiguá",15 +1506112,"Quatipuru",13 +3304128,"Quatis",18 +4120804,"Quatro Barras",15 +4315313,"Quatro Irmãos",20 +4120853,"Quatro Pontes",15 +2707602,"Quebrangulo",2 +4120903,"Quedas do Iguaçu",15 +2208650,"Queimada Nova",17 +2512507,"Queimadas",14 +2925808,"Queimadas",5 +3304144,"Queimados",18 +3541802,"Queiroz",24 +3541901,"Queluz",24 +3153806,"Queluzito",12 +5107065,"Querência",10 +4121000,"Querência do Norte",15 +4315321,"Quevedos",20 +2925907,"Quijingue",5 +4214201,"Quilombo",23 +4121109,"Quinta do Sol",15 +3542008,"Quintana",24 +4315354,"Quinze de Novembro",20 +4314902,"Porto Alegre",20 +1100205,"Porto Velho",21 +1100254,"Presidente Médici",21 +1200393,"Porto Walter",1 +2611507,"Quipapá",16 +5218508,"Quirinópolis",8 +3304151,"Quissamã",18 +4121208,"Quitandinha",15 +2311264,"Quiterianópolis",6 +2512606,"Quixabá",14 +2611533,"Quixaba",16 +2925931,"Quixabeira",5 +2311306,"Quixadá",6 +2311355,"Quixelô",6 +2311405,"Quixeramobim",6 +2311504,"Quixeré",6 +2410504,"Rafael Fernandes",19 +2410603,"Rafael Godeiro",19 +2925956,"Rafael Jambeiro",5 +3542107,"Rafard",24 +4121257,"Ramilândia",15 +3542206,"Rancharia",24 +4121307,"Rancho Alegre",15 +4121356,"Rancho Alegre D'Oeste",15 +4214300,"Rancho Queimado",23 +2109452,"Raposa",9 +3153905,"Raposos",12 +3154002,"Raul Soares",12 +4121406,"Realeza",15 +4121505,"Rebouças",15 +3154101,"Recreio",12 +1718501,"Recursolândia",26 +1506138,"Redenção",13 +2311603,"Redenção",6 +3542305,"Redenção da Serra",24 +2208700,"Redenção do Gurguéia",17 +4315404,"Redentora",20 +3154150,"Reduto",12 +2208809,"Regeneração",17 +3542404,"Regente Feijó",24 +3542503,"Reginópolis",24 +3542602,"Registro",24 +4315453,"Relvado",20 +2926004,"Remanso",5 +2512705,"Remígio",14 +4121604,"Renascença",15 +2311702,"Reriutaba",6 +3304201,"Resende",18 +3154200,"Resende Costa",12 +4121703,"Reserva",15 +5107156,"Reserva do Cabaçal",10 +4121752,"Reserva do Iguaçu",15 +3154309,"Resplendor",12 +3154408,"Ressaquinha",12 +3542701,"Restinga",24 +4315503,"Restinga Sêca",20 +2926103,"Retirolândia",5 +2512747,"Riachão",14 +2109502,"Riachão",9 +2926202,"Riachão das Neves",5 +2512754,"Riachão do Bacamarte",14 +2805802,"Riachão do Dantas",25 +2926301,"Riachão do Jacuípe",5 +2512762,"Riachão do Poço",14 +1718550,"Riachinho",26 +3154457,"Riachinho",12 +2410702,"Riacho da Cruz",19 +2611705,"Riacho das Almas",16 +2410801,"Riacho de Santana",19 +2926400,"Riacho de Santana",5 +2512788,"Riacho de Santo Antônio",14 +2512804,"Riacho dos Cavalos",14 +3154507,"Riacho dos Machados",12 +2208858,"Riacho Frio",17 +2410900,"Riachuelo",19 +2805901,"Riachuelo",25 +5218607,"Rialma",8 +5218706,"Rianápolis",8 +2109551,"Ribamar Fiquene",9 +5007109,"Ribas do Rio Pardo",11 +3542800,"Ribeira",24 +2926509,"Ribeira do Amparo",5 +2208874,"Ribeira do Piauí",17 +2926608,"Ribeira do Pombal",5 +2611804,"Ribeirão",16 +3542909,"Ribeirão Bonito",24 +3543006,"Ribeirão Branco",24 +5107180,"Ribeirão Cascalheira",10 +4121802,"Ribeirão Claro",15 +3543105,"Ribeirão Corrente",24 +3154606,"Ribeirão das Neves",12 +2926657,"Ribeirão do Largo",5 +4121901,"Ribeirão do Pinhal",15 +3543204,"Ribeirão do Sul",24 +3543238,"Ribeirão dos Índios",24 +3543253,"Ribeirão Grande",24 +3543303,"Ribeirão Pires",24 +3543402,"Ribeirão Preto",24 +3154705,"Ribeirão Vermelho",12 +5107198,"Ribeirãozinho",10 +2208908,"Ribeiro Gonçalves",17 +2806008,"Ribeirópolis",25 +3543600,"Rifaina",24 +3543709,"Rincão",24 +3543808,"Rinópolis",24 +3154804,"Rio Acima",12 +4122008,"Rio Azul",15 +3204351,"Rio Bananal",7 +4122107,"Rio Bom",15 +3304300,"Rio Bonito",18 +4122156,"Rio Bonito do Iguaçu",15 +5107206,"Rio Branco",10 +4122172,"Rio Branco do Ivaí",15 +4122206,"Rio Branco do Sul",15 +5007208,"Rio Brilhante",11 +3154903,"Rio Casca",12 +3304409,"Rio Claro",18 +3543907,"Rio Claro",24 +1718659,"Rio da Conceição",26 +4214409,"Rio das Antas",23 +3304508,"Rio das Flores",18 +3304524,"Rio das Ostras",18 +3544004,"Rio das Pedras",24 +2926707,"Rio de Contas",5 +2926806,"Rio do Antônio",5 +4214508,"Rio do Campo",23 +2408953,"Rio do Fogo",19 +4214607,"Rio do Oeste",23 +2926905,"Rio do Pires",5 +3155108,"Rio do Prado",12 +4214805,"Rio do Sul",23 +3155009,"Rio Doce",12 +1718709,"Rio dos Bois",26 +4214706,"Rio dos Cedros",23 +4315552,"Rio dos Índios",20 +3155207,"Rio Espera",12 +2611903,"Rio Formoso",16 +4214904,"Rio Fortuna",23 +4315602,"Rio Grande",20 +3544103,"Rio Grande da Serra",24 +2209005,"Rio Grande do Piauí",17 +2707701,"Rio Largo",2 +3155306,"Rio Manso",12 +1506161,"Rio Maria",13 +4215000,"Rio Negrinho",23 +5007307,"Rio Negro",11 +4122305,"Rio Negro",15 +3155405,"Rio Novo",12 +3204401,"Rio Novo do Sul",7 +3155504,"Rio Paranaíba",12 +4315701,"Rio Pardo",20 +3155603,"Rio Pardo de Minas",12 +3155702,"Rio Piracicaba",12 +3304557,"Rio de Janeiro",18 +2611606,"Recife",16 +1100262,"Rio Crespo",21 +3155801,"Rio Pomba",12 +3155900,"Rio Preto",12 +1303569,"Rio Preto da Eva",4 +5218789,"Rio Quente",8 +2927002,"Rio Real",5 +4215059,"Rio Rufino",23 +1718758,"Rio Sono",26 +2512903,"Rio Tinto",14 +5218805,"Rio Verde",8 +5007406,"Rio Verde de Mato Grosso",11 +3156007,"Rio Vermelho",12 +3544202,"Riolândia",24 +4315750,"Riozinho",20 +4215075,"Riqueza",23 +3156106,"Ritápolis",12 +3543501,"Riversul",24 +4315800,"Roca Sales",20 +5007505,"Rochedo",11 +3156205,"Rochedo de Minas",12 +4215109,"Rodeio",23 +4315909,"Rodeio Bonito",20 +3156304,"Rodeiro",12 +2927101,"Rodelas",5 +2411007,"Rodolfo Fernandes",19 +4315958,"Rolador",20 +4122404,"Rolândia",15 +4316006,"Rolante",20 +3156403,"Romaria",12 +4215208,"Romelândia",23 +4122503,"Roncador",15 +4316105,"Ronda Alta",20 +4316204,"Rondinha",20 +5107578,"Rondolândia",10 +4122602,"Rondon",15 +1506187,"Rondon do Pará",13 +5107602,"Rondonópolis",10 +4316303,"Roque Gonzales",20 +1400472,"Rorainópolis",22 +3544251,"Rosana",24 +2109601,"Rosário",9 +3156452,"Rosário da Limeira",12 +2806107,"Rosário do Catete",25 +4122651,"Rosário do Ivaí",15 +4316402,"Rosário do Sul",20 +5107701,"Rosário Oeste",10 +3544301,"Roseira",24 +2707800,"Roteiro",2 +3156502,"Rubelita",12 +3544400,"Rubiácea",24 +5218904,"Rubiataba",8 +3156601,"Rubim",12 +3544509,"Rubinéia",24 +1506195,"Rurópolis",13 +2311801,"Russas",6 +2411106,"Ruy Barbosa",19 +2927200,"Ruy Barbosa",5 +3156700,"Sabará",12 +4122701,"Sabáudia",15 +3544608,"Sabino",24 +3156809,"Sabinópolis",12 +2311900,"Saboeiro",6 +3156908,"Sacramento",12 +4316428,"Sagrada Família",20 +3544707,"Sagres",24 +2612000,"Sairé",16 +4316436,"Saldanha Marinho",20 +3544806,"Sales",24 +3544905,"Sales Oliveira",24 +3545001,"Salesópolis",24 +4215307,"Salete",23 +2513000,"Salgadinho",14 +2612109,"Salgadinho",16 +2806206,"Salgado",25 +2513109,"Salgado de São Félix",14 +4122800,"Salgado Filho",15 +2612208,"Salgueiro",16 +3157005,"Salinas",12 +2927309,"Salinas da Margarida",5 +1506203,"Salinópolis",13 +2311959,"Salitre",6 +3545100,"Salmourão",24 +2612307,"Saloá",16 +4215356,"Saltinho",23 +3545159,"Saltinho",24 +3545209,"Salto",24 +3157104,"Salto da Divisa",12 +3545308,"Salto de Pirapora",24 +5107750,"Salto do Céu",10 +4122909,"Salto do Itararé",15 +4316451,"Salto do Jacuí",20 +4123006,"Salto do Lontra",15 +3545407,"Salto Grande",24 +4215406,"Salto Veloso",23 +4316477,"Salvador das Missões",20 +4316501,"Salvador do Sul",20 +1506302,"Salvaterra",13 +2109700,"Sambaíba",9 +1718808,"Sampaio",26 +4316600,"Sananduva",20 +5219001,"Sanclerlândia",8 +1718840,"Sandolândia",26 +3545506,"Sandovalina",24 +4215455,"Sangão",23 +2612406,"Sanharó",16 +4317103,"Sant'Ana do Livramento",20 +3545605,"Santa Adélia",24 +3545704,"Santa Albertina",24 +4123105,"Santa Amélia",15 +2927507,"Santa Bárbara",5 +3157203,"Santa Bárbara",12 +3545803,"Santa Bárbara d'Oeste",24 +5219100,"Santa Bárbara de Goiás",8 +3157252,"Santa Bárbara do Leste",12 +3157278,"Santa Bárbara do Monte Verde",12 +1506351,"Santa Bárbara do Pará",13 +4316709,"Santa Bárbara do Sul",20 +3157302,"Santa Bárbara do Tugúrio",12 +3546009,"Santa Branca",24 +2927606,"Santa Brígida",5 +5107248,"Santa Carmem",10 +4215505,"Santa Cecília",23 +2513158,"Santa Cecília",14 +4123204,"Santa Cecília do Pavão",15 +4316733,"Santa Cecília do Sul",20 +3546108,"Santa Clara d'Oeste",24 +4316758,"Santa Clara do Sul",20 +2411205,"Santa Cruz",19 +2513208,"Santa Cruz",14 +2612455,"Santa Cruz",16 +2927705,"Santa Cruz Cabrália",5 +2612471,"Santa Cruz da Baixa Verde",16 +3546207,"Santa Cruz da Conceição",24 +3546256,"Santa Cruz da Esperança",24 +2927804,"Santa Cruz da Vitória",5 +3546306,"Santa Cruz das Palmeiras",24 +5219209,"Santa Cruz de Goiás",8 +3157336,"Santa Cruz de Minas",12 +4123303,"Santa Cruz de Monte Castelo",15 +3157377,"Santa Cruz de Salinas",12 +1506401,"Santa Cruz do Arari",13 +2612505,"Santa Cruz do Capibaribe",16 +3157401,"Santa Cruz do Escalvado",12 +2209104,"Santa Cruz do Piauí",17 +3546405,"Santa Cruz do Rio Pardo",24 +4316808,"Santa Cruz do Sul",20 +5107743,"Santa Cruz do Xingu",10 +2209153,"Santa Cruz dos Milagres",17 +3157500,"Santa Efigênia de Minas",12 +3546504,"Santa Ernestina",24 +2927408,"Salvador",5 +1100288,"Rolim de Moura",21 +4123402,"Santa Fé",15 +5219258,"Santa Fé de Goiás",8 +3157609,"Santa Fé de Minas",12 +1718865,"Santa Fé do Araguaia",26 +3546603,"Santa Fé do Sul",24 +2209203,"Santa Filomena",17 +2612554,"Santa Filomena",16 +2109759,"Santa Filomena do Maranhão",9 +3546702,"Santa Gertrudes",24 +4123501,"Santa Helena",15 +4215554,"Santa Helena",23 +2109809,"Santa Helena",9 +2513307,"Santa Helena",14 +5219308,"Santa Helena de Goiás",8 +3157658,"Santa Helena de Minas",12 +2927903,"Santa Inês",5 +4123600,"Santa Inês",15 +2513356,"Santa Inês",14 +2109908,"Santa Inês",9 +3546801,"Santa Isabel",24 +5219357,"Santa Isabel",8 +4123709,"Santa Isabel do Ivaí",15 +1303601,"Santa Isabel do Rio Negro",4 +4123808,"Santa Izabel do Oeste",15 +1506500,"Santa Izabel do Pará",13 +3157708,"Santa Juliana",12 +3204500,"Santa Leopoldina",7 +3546900,"Santa Lúcia",24 +4123824,"Santa Lúcia",15 +2209302,"Santa Luz",17 +2110005,"Santa Luzia",9 +2928059,"Santa Luzia",5 +3157807,"Santa Luzia",12 +2513406,"Santa Luzia",14 +2806305,"Santa Luzia do Itanhy",25 +2707909,"Santa Luzia do Norte",2 +1506559,"Santa Luzia do Pará",13 +2110039,"Santa Luzia do Paruá",9 +3157906,"Santa Margarida",12 +4316972,"Santa Margarida do Sul",20 +4316907,"Santa Maria",20 +2409332,"Santa Maria",19 +2612604,"Santa Maria da Boa Vista",16 +3547007,"Santa Maria da Serra",24 +2928109,"Santa Maria da Vitória",5 +1506583,"Santa Maria das Barreiras",13 +3158003,"Santa Maria de Itabira",12 +3204559,"Santa Maria de Jetibá",7 +2612703,"Santa Maria do Cambucá",16 +4316956,"Santa Maria do Herval",20 +4123857,"Santa Maria do Oeste",15 +1506609,"Santa Maria do Pará",13 +3158102,"Santa Maria do Salto",12 +3158201,"Santa Maria do Suaçuí",12 +1718881,"Santa Maria do Tocantins",26 +3304607,"Santa Maria Madalena",18 +4123907,"Santa Mariana",15 +3547106,"Santa Mercedes",24 +4123956,"Santa Mônica",15 +2312205,"Santa Quitéria",6 +2110104,"Santa Quitéria do Maranhão",9 +2110203,"Santa Rita",9 +2513703,"Santa Rita",14 +3547403,"Santa Rita d'Oeste",24 +3159209,"Santa Rita de Caldas",12 +2928406,"Santa Rita de Cássia",5 +3159407,"Santa Rita de Ibitipoca",12 +3159308,"Santa Rita de Jacutinga",12 +3159357,"Santa Rita de Minas",12 +5219407,"Santa Rita do Araguaia",8 +3159506,"Santa Rita do Itueto",12 +5219456,"Santa Rita do Novo Destino",8 +5007554,"Santa Rita do Pardo",11 +3547502,"Santa Rita do Passa Quatro",24 +3159605,"Santa Rita do Sapucaí",12 +1718899,"Santa Rita do Tocantins",26 +5107768,"Santa Rita do Trivelato",10 +4317202,"Santa Rosa",20 +3159704,"Santa Rosa da Serra",12 +5219506,"Santa Rosa de Goiás",8 +4215604,"Santa Rosa de Lima",23 +2806503,"Santa Rosa de Lima",25 +3547601,"Santa Rosa de Viterbo",24 +2209377,"Santa Rosa do Piauí",17 +4215653,"Santa Rosa do Sul",23 +1718907,"Santa Rosa do Tocantins",26 +3547650,"Santa Salete",24 +3204609,"Santa Teresa",7 +2928505,"Santa Teresinha",5 +2513802,"Santa Teresinha",14 +4317251,"Santa Tereza",20 +5219605,"Santa Tereza de Goiás",8 +4124020,"Santa Tereza do Oeste",15 +1719004,"Santa Tereza do Tocantins",26 +4215679,"Santa Terezinha",23 +5107776,"Santa Terezinha",10 +2612802,"Santa Terezinha",16 +5219704,"Santa Terezinha de Goiás",8 +4124053,"Santa Terezinha de Itaipu",15 +4215687,"Santa Terezinha do Progresso",23 +1720002,"Santa Terezinha do Tocantins",26 +3159803,"Santa Vitória",12 +4317301,"Santa Vitória do Palmar",20 +2928000,"Santaluz",5 +2928208,"Santana",5 +1600600,"Santana",3 +4317004,"Santana da Boa Vista",20 +3547205,"Santana da Ponte Pensa",24 +3158300,"Santana da Vargem",12 +3158409,"Santana de Cataguases",12 +2513505,"Santana de Mangueira",14 +3547304,"Santana de Parnaíba",24 +3158508,"Santana de Pirapama",12 +2312007,"Santana do Acaraú",6 +1506708,"Santana do Araguaia",13 +2312106,"Santana do Cariri",6 +3158607,"Santana do Deserto",12 +3158706,"Santana do Garambéu",12 +2708006,"Santana do Ipanema",2 +4124004,"Santana do Itararé",15 +3158805,"Santana do Jacaré",12 +3158904,"Santana do Manhuaçu",12 +2110237,"Santana do Maranhão",9 +2411403,"Santana do Matos",19 +2708105,"Santana do Mundaú",2 +3158953,"Santana do Paraíso",12 +2209351,"Santana do Piauí",17 +3159001,"Santana do Riacho",12 +2806404,"Santana do São Francisco",25 +2411429,"Santana do Seridó",19 +2513604,"Santana dos Garrotes",14 +3159100,"Santana dos Montes",12 +2928307,"Santanópolis",5 +1506807,"Santarém",13 +1506906,"Santarém Novo",13 +4317400,"Santiago",20 +4215695,"Santiago do Sul",23 +5107263,"Santo Afonso",10 +2928604,"Santo Amaro",5 +1100296,"Santa Luzia D'Oeste",21 +4215703,"Santo Amaro da Imperatriz",23 +2806602,"Santo Amaro das Brotas",25 +2110278,"Santo Amaro do Maranhão",9 +3547700,"Santo Anastácio",24 +3547809,"Santo André",24 +2513851,"Santo André",14 +4317509,"Santo Ângelo",20 +2411502,"Santo Antônio",19 +3547908,"Santo Antônio da Alegria",24 +5219712,"Santo Antônio da Barra",8 +4317608,"Santo Antônio da Patrulha",20 +4124103,"Santo Antônio da Platina",15 +4317707,"Santo Antônio das Missões",20 +5219738,"Santo Antônio de Goiás",8 +2928703,"Santo Antônio de Jesus",5 +2209401,"Santo Antônio de Lisboa",17 +3304706,"Santo Antônio de Pádua",18 +3548005,"Santo Antônio de Posse",24 +3159902,"Santo Antônio do Amparo",12 +3548054,"Santo Antônio do Aracanguá",24 +3160009,"Santo Antônio do Aventureiro",12 +4124202,"Santo Antônio do Caiuá",15 +5219753,"Santo Antônio do Descoberto",8 +3160108,"Santo Antônio do Grama",12 +1303700,"Santo Antônio do Içá",4 +3160207,"Santo Antônio do Itambé",12 +3160306,"Santo Antônio do Jacinto",12 +3548104,"Santo Antônio do Jardim",24 +5107792,"Santo Antônio do Leste",10 +5107800,"Santo Antônio do Leverger",10 +3160405,"Santo Antônio do Monte",12 +4317558,"Santo Antônio do Palma",20 +4124301,"Santo Antônio do Paraíso",15 +3548203,"Santo Antônio do Pinhal",24 +4317756,"Santo Antônio do Planalto",20 +3160454,"Santo Antônio do Retiro",12 +3160504,"Santo Antônio do Rio Abaixo",12 +4124400,"Santo Antônio do Sudoeste",15 +1507003,"Santo Antônio do Tauá",13 +2110302,"Santo Antônio dos Lopes",9 +2209450,"Santo Antônio dos Milagres",17 +4317806,"Santo Augusto",20 +4317905,"Santo Cristo",20 +2928802,"Santo Estêvão",5 +3548302,"Santo Expedito",24 +4317954,"Santo Expedito do Sul",20 +3160603,"Santo Hipólito",12 +4124509,"Santo Inácio",15 +2209500,"Santo Inácio do Piauí",17 +3548401,"Santópolis do Aguapeí",24 +3548500,"Santos",24 +3160702,"Santos Dumont",12 +2312304,"São Benedito",6 +2110401,"São Benedito do Rio Preto",9 +2612901,"São Benedito do Sul",16 +2513927,"São Bentinho",14 +2513901,"São Bento",14 +2110500,"São Bento",9 +3160801,"São Bento Abade",12 +2411601,"São Bento do Norte",19 +3548609,"São Bento do Sapucaí",24 +4215802,"São Bento do Sul",23 +1720101,"São Bento do Tocantins",26 +2411700,"São Bento do Trairí",19 +2613008,"São Bento do Una",16 +4215752,"São Bernardino",23 +2110609,"São Bernardo",9 +3548708,"São Bernardo do Campo",24 +4215901,"São Bonifácio",23 +4318002,"São Borja",20 +2708204,"São Brás",2 +3160900,"São Brás do Suaçuí",12 +2209559,"São Braz do Piauí",17 +2613107,"São Caetano",16 +1507102,"São Caetano de Odivelas",13 +3548807,"São Caetano do Sul",24 +3548906,"São Carlos",24 +4216008,"São Carlos",23 +4124608,"São Carlos do Ivaí",15 +2806701,"São Cristóvão",25 +4216057,"São Cristovão do Sul",23 +2928901,"São Desidério",5 +2928950,"São Domingos",5 +4216107,"São Domingos",23 +2513968,"São Domingos",14 +2806800,"São Domingos",25 +5219803,"São Domingos",8 +3160959,"São Domingos das Dores",12 +1507151,"São Domingos do Araguaia",13 +2110658,"São Domingos do Azeitão",9 +1507201,"São Domingos do Capim",13 +2513943,"São Domingos do Cariri",14 +2110708,"São Domingos do Maranhão",9 +3204658,"São Domingos do Norte",7 +3161007,"São Domingos do Prata",12 +4318051,"São Domingos do Sul",20 +2929107,"São Felipe",5 +2929008,"São Félix",5 +2110807,"São Félix de Balsas",9 +3161056,"São Félix de Minas",12 +5107859,"São Félix do Araguaia",10 +2929057,"São Félix do Coribe",5 +2209609,"São Félix do Piauí",17 +1720150,"São Félix do Tocantins",26 +1507300,"São Félix do Xingu",13 +2411809,"São Fernando",19 +3304805,"São Fidélis",18 +3549003,"São Francisco",24 +2513984,"São Francisco",14 +2806909,"São Francisco",25 +3161106,"São Francisco",12 +4318101,"São Francisco de Assis",20 +2209658,"São Francisco de Assis do Piauí",17 +5219902,"São Francisco de Goiás",8 +3304755,"São Francisco de Itabapoana",18 +4318200,"São Francisco de Paula",20 +3161205,"São Francisco de Paula",12 +3161304,"São Francisco de Sales",12 +2110856,"São Francisco do Brejão",9 +2929206,"São Francisco do Conde",5 +3161403,"São Francisco do Glória",12 +2110906,"São Francisco do Maranhão",9 +2411908,"São Francisco do Oeste",19 +1507409,"São Francisco do Pará",13 +2209708,"São Francisco do Piauí",17 +4216206,"São Francisco do Sul",23 +4318309,"São Gabriel",20 +2929255,"São Gabriel",5 +1303809,"São Gabriel da Cachoeira",4 +3204708,"São Gabriel da Palha",7 +5007695,"São Gabriel do Oeste",11 +3161502,"São Geraldo",12 +3161601,"São Geraldo da Piedade",12 +1507458,"São Geraldo do Araguaia",13 +3161650,"São Geraldo do Baixio",12 +3304904,"São Gonçalo",18 +3161700,"São Gonçalo do Abaeté",12 +2412005,"São Gonçalo do Amarante",19 +2312403,"São Gonçalo do Amarante",6 +2209757,"São Gonçalo do Gurguéia",17 +3161809,"São Gonçalo do Pará",12 +2209807,"São Gonçalo do Piauí",17 +3161908,"São Gonçalo do Rio Abaixo",12 +3125507,"São Gonçalo do Rio Preto",12 +3162005,"São Gonçalo do Sapucaí",12 +2929305,"São Gonçalo dos Campos",5 +3162104,"São Gotardo",12 +4318408,"São Jerônimo",20 +4124707,"São Jerônimo da Serra",15 +4124806,"São João",15 +2613206,"São João",16 +2111003,"São João Batista",9 +4216305,"São João Batista",23 +3162203,"São João Batista do Glória",12 +5220009,"São João d'Aliança",8 +1400506,"São João da Baliza",22 +3305000,"São João da Barra",18 +3549102,"São João da Boa Vista",24 +2209856,"São João da Canabrava",17 +2209872,"São João da Fronteira",17 +3162252,"São João da Lagoa",12 +3162302,"São João da Mata",12 +5220058,"São João da Paraúna",8 +1507466,"São João da Ponta",13 +3162401,"São João da Ponte",12 +2209906,"São João da Serra",17 +4318424,"São João da Urtiga",20 +2209955,"São João da Varjota",17 +3549201,"São João das Duas Pontes",24 +3162450,"São João das Missões",12 +3549250,"São João de Iracema",24 +3305109,"São João de Meriti",18 +1507474,"São João de Pirabas",13 +3162500,"São João del Rei",12 +1507508,"São João do Araguaia",13 +2209971,"São João do Arraial",17 +4124905,"São João do Caiuá",15 +2514008,"São João do Cariri",14 +2111029,"São João do Carú",9 +4216354,"São João do Itaperiú",23 +4125001,"São João do Ivaí",15 +2312502,"São João do Jaguaribe",6 +3162559,"São João do Manhuaçu",12 +3162575,"São João do Manteninha",12 +4216255,"São João do Oeste",23 +3162609,"São João do Oriente",12 +3162658,"São João do Pacuí",12 +3162708,"São João do Paraíso",12 +2111052,"São João do Paraíso",9 +3549300,"São João do Pau d'Alho",24 +2210003,"São João do Piauí",17 +4318432,"São João do Polêsine",20 +2500700,"São João do Rio do Peixe",14 +2412104,"São João do Sabugi",19 +2111078,"São João do Soter",9 +4216404,"São João do Sul",23 +2514107,"São João do Tigre",14 +4125100,"São João do Triunfo",15 +2111102,"São João dos Patos",9 +3162807,"São João Evangelista",12 +3162906,"São João Nepomuceno",12 +4216503,"São Joaquim",23 +3549409,"São Joaquim da Barra",24 +3162922,"São Joaquim de Bicas",12 +2613305,"São Joaquim do Monte",16 +4318440,"São Jorge",20 +4125209,"São Jorge d'Oeste",15 +4125308,"São Jorge do Ivaí",15 +4125357,"São Jorge do Patrocínio",15 +4216602,"São José",23 +3162948,"São José da Barra",12 +3549508,"São José da Bela Vista",24 +4125407,"São José da Boa Vista",15 +2613404,"São José da Coroa Grande",16 +2514206,"São José da Lagoa Tapada",14 +2708303,"São José da Laje",2 +3162955,"São José da Lapa",12 +3163003,"São José da Safira",12 +2708402,"São José da Tapera",2 +3163102,"São José da Varginha",12 +2929354,"São José da Vitória",5 +4318457,"São José das Missões",20 +4125456,"São José das Palmeiras",15 +2514305,"São José de Caiana",14 +2514404,"São José de Espinharas",14 +2412203,"São José de Mipibu",19 +2514503,"São José de Piranhas",14 +2514552,"São José de Princesa",14 +2111201,"São José de Ribamar",9 +3305133,"São José de Ubá",18 +3163201,"São José do Alegre",12 +3549607,"São José do Barreiro",24 +2613503,"São José do Belmonte",16 +2514602,"São José do Bonfim",14 +2514651,"São José do Brejo do Cruz",14 +3204807,"São José do Calçado",7 +2412302,"São José do Campestre",19 +4216701,"São José do Cedro",23 +4216800,"São José do Cerrito",23 +2210052,"São José do Divino",17 +3163300,"São José do Divino",12 +2613602,"São José do Egito",16 +3163409,"São José do Goiabal",12 +4318465,"São José do Herval",20 +4318481,"São José do Hortêncio",20 +4318499,"São José do Inhacorá",20 +2929370,"São José do Jacuípe",5 +3163508,"São José do Jacuri",12 +3163607,"São José do Mantimento",12 +4318507,"São José do Norte",20 +4318606,"São José do Ouro",20 +2210102,"São José do Peixe",17 +2210201,"São José do Piauí",17 +5107297,"São José do Povo",10 +5107305,"São José do Rio Claro",10 +3549706,"São José do Rio Pardo",24 +3549805,"São José do Rio Preto",24 +2514701,"São José do Sabugi",14 +2412401,"São José do Seridó",19 +4318614,"São José do Sul",20 +3305158,"São José do Vale do Rio Preto",18 +5107354,"São José do Xingu",10 +4318622,"São José dos Ausentes",20 +2111250,"São José dos Basílios",9 +3549904,"São José dos Campos",24 +2514800,"São José dos Cordeiros",14 +4125506,"São José dos Pinhais",15 +5107107,"São José dos Quatro Marcos",10 +2514453,"São José dos Ramos",14 +2210300,"São Julião",17 +4318705,"São Leopoldo",20 +3163706,"São Lourenço",12 +2613701,"São Lourenço da Mata",16 +3549953,"São Lourenço da Serra",24 +4216909,"São Lourenço do Oeste",23 +2210359,"São Lourenço do Piauí",17 +4318804,"São Lourenço do Sul",20 +4217006,"São Ludgero",23 +5220108,"São Luís de Montes Belos",8 +2312601,"São Luís do Curu",6 +2210375,"São Luis do Piauí",17 +2708501,"São Luís do Quitunde",2 +2111409,"São Luís Gonzaga do Maranhão",9 +1400605,"São Luiz",22 +5220157,"São Luiz do Norte",8 +3550001,"São Luiz do Paraitinga",24 +4318903,"São Luiz Gonzaga",20 +2514909,"São Mamede",14 +4125555,"São Manoel do Paraná",15 +3550100,"São Manuel",24 +4319000,"São Marcos",20 +4217105,"São Martinho",23 +4319109,"São Martinho",20 +4319125,"São Martinho da Serra",20 +3204906,"São Mateus",7 +2111508,"São Mateus do Maranhão",9 +4125605,"São Mateus do Sul",15 +2412500,"São Miguel",19 +3550209,"São Miguel Arcanjo",24 +2210383,"São Miguel da Baixa Grande",17 +4217154,"São Miguel da Boa Vista",23 +2929404,"São Miguel das Matas",5 +4319158,"São Miguel das Missões",20 +2515005,"São Miguel de Taipu",14 +2807006,"São Miguel do Aleixo",25 +3163805,"São Miguel do Anta",12 +5220207,"São Miguel do Araguaia",8 +2210391,"São Miguel do Fidalgo",17 +2412559,"São Miguel do Gostoso",19 +1507607,"São Miguel do Guamá",13 +4125704,"São Miguel do Iguaçu",15 +4217204,"São Miguel do Oeste",23 +5220264,"São Miguel do Passa Quatro",8 +2210409,"São Miguel do Tapuio",17 +1720200,"São Miguel do Tocantins",26 +2708600,"São Miguel dos Campos",2 +2708709,"São Miguel dos Milagres",2 +4319208,"São Nicolau",20 +5220280,"São Patrício",8 +4319307,"São Paulo das Missões",20 +1303908,"São Paulo de Olivença",4 +2412609,"São Paulo do Potengi",19 +2412708,"São Pedro",19 +3550407,"São Pedro",24 +2111532,"São Pedro da Água Branca",9 +3305208,"São Pedro da Aldeia",18 +5107404,"São Pedro da Cipa",10 +4319356,"São Pedro da Serra",20 +3163904,"São Pedro da União",12 +4319364,"São Pedro das Missões",20 +4217253,"São Pedro de Alcântara",23 +4319372,"São Pedro do Butiá",20 +4125753,"São Pedro do Iguaçu",15 +4125803,"São Pedro do Ivaí",15 +4125902,"São Pedro do Paraná",15 +2210508,"São Pedro do Piauí",17 +3164100,"São Pedro do Suaçuí",12 +4319406,"São Pedro do Sul",20 +3550506,"São Pedro do Turvo",24 +2111573,"São Pedro dos Crentes",9 +3164001,"São Pedro dos Ferros",12 +2412807,"São Rafael",19 +2111607,"São Raimundo das Mangabeiras",9 +2111631,"São Raimundo do Doca Bezerra",9 +2210607,"São Raimundo Nonato",17 +2111672,"São Roberto",9 +3164209,"São Romão",12 +3550605,"São Roque",24 +3164308,"São Roque de Minas",12 +3204955,"São Roque do Canaã",7 +1720259,"São Salvador do Tocantins",26 +3550704,"São Sebastião",24 +2708808,"São Sebastião",2 +4126009,"São Sebastião da Amoreira",15 +3164407,"São Sebastião da Bela Vista",12 +1507706,"São Sebastião da Boa Vista",13 +3550803,"São Sebastião da Grama",24 +3164431,"São Sebastião da Vargem Alegre",12 +2515104,"São Sebastião de Lagoa de Roça",14 +3305307,"São Sebastião do Alto",18 +3164472,"São Sebastião do Anta",12 +4319505,"São Sebastião do Caí",20 +3164506,"São Sebastião do Maranhão",12 +3164605,"São Sebastião do Oeste",12 +3164704,"São Sebastião do Paraíso",12 +2929503,"São Sebastião do Passé",5 +3164803,"São Sebastião do Rio Preto",12 +3164902,"São Sebastião do Rio Verde",12 +1720309,"São Sebastião do Tocantins",26 +1303957,"São Sebastião do Uatumã",4 +2515203,"São Sebastião do Umbuzeiro",14 +4319604,"São Sepé",20 +3550902,"São Simão",24 +5220405,"São Simão",8 +3165206,"São Thomé das Letras",12 +3165008,"São Tiago",12 +3165107,"São Tomás de Aquino",12 +4126108,"São Tomé",15 +2412906,"São Tomé",19 +4319703,"São Valentim",20 +4319711,"São Valentim do Sul",20 +1720499,"São Valério",26 +4319737,"São Valério do Sul",20 +4319752,"São Vendelino",20 +3551009,"São Vicente",24 +2413003,"São Vicente",19 +3165305,"São Vicente de Minas",12 +2515401,"São Vicente do Seridó",14 +4319802,"São Vicente do Sul",20 +2613800,"São Vicente Ferrer",16 +2111706,"São Vicente Ferrer",9 +2515302,"Sapé",14 +2929602,"Sapeaçu",5 +5107875,"Sapezal",10 +4319901,"Sapiranga",20 +4126207,"Sapopema",15 +3165404,"Sapucaí-Mirim",12 +1507755,"Sapucaia",13 +3305406,"Sapucaia",18 +4320008,"Sapucaia do Sul",20 +3305505,"Saquarema",18 +4126256,"Sarandi",15 +2111300,"São Luís",9 +1100320,"São Miguel do Guaporé",21 +4320107,"Sarandi",20 +3551108,"Sarapuí",24 +3165503,"Sardoá",12 +3551207,"Sarutaiá",24 +3165537,"Sarzedo",12 +2929701,"Sátiro Dias",5 +2708907,"Satuba",2 +2111722,"Satubinha",9 +2929750,"Saubara",5 +4126272,"Saudade do Iguaçu",15 +4217303,"Saudades",23 +2929800,"Saúde",5 +4217402,"Schroeder",23 +2929909,"Seabra",5 +4217501,"Seara",23 +3551306,"Sebastianópolis do Sul",24 +2210623,"Sebastião Barros",17 +2930006,"Sebastião Laranjeiras",5 +2210631,"Sebastião Leal",17 +4320206,"Seberi",20 +4320230,"Sede Nova",20 +4320263,"Segredo",20 +4320305,"Selbach",20 +5007802,"Selvíria",11 +3165560,"Sem-Peixe",12 +2111748,"Senador Alexandre Costa",9 +3165578,"Senador Amaral",12 +5220454,"Senador Canedo",8 +3165602,"Senador Cortes",12 +2413102,"Senador Elói de Souza",19 +3165701,"Senador Firmino",12 +2413201,"Senador Georgino Avelino",19 +3165800,"Senador José Bento",12 +1507805,"Senador José Porfírio",13 +2111763,"Senador La Rocque",9 +3165909,"Senador Modestino Gonçalves",12 +2312700,"Senador Pompeu",6 +2708956,"Senador Rui Palmeira",2 +2312809,"Senador Sá",6 +4320321,"Senador Salgado Filho",20 +4126306,"Sengés",15 +2930105,"Senhor do Bonfim",5 +3166006,"Senhora de Oliveira",12 +3166105,"Senhora do Porto",12 +3166204,"Senhora dos Remédios",12 +4320354,"Sentinela do Sul",20 +2930204,"Sento Sé",5 +4320404,"Serafina Corrêa",20 +3166303,"Sericita",12 +4320453,"Sério",20 +3166402,"Seritinga",12 +3305554,"Seropédica",18 +3205002,"Serra",7 +4217550,"Serra Alta",23 +3551405,"Serra Azul",24 +3166501,"Serra Azul de Minas",12 +2515500,"Serra Branca",14 +2410306,"Serra Caiada",19 +2515609,"Serra da Raiz",14 +3166600,"Serra da Saudade",12 +2413300,"Serra de São Bento",19 +2413359,"Serra do Mel",19 +1600055,"Serra do Navio",3 +2930154,"Serra do Ramalho",5 +3166808,"Serra do Salitre",12 +3166709,"Serra dos Aimorés",12 +2930303,"Serra Dourada",5 +2515708,"Serra Grande",14 +3551603,"Serra Negra",24 +2413409,"Serra Negra do Norte",19 +5107883,"Serra Nova Dourada",10 +2930402,"Serra Preta",5 +2515807,"Serra Redonda",14 +2613909,"Serra Talhada",16 +3551504,"Serrana",24 +3166907,"Serrania",12 +2111789,"Serrano do Maranhão",9 +5220504,"Serranópolis",8 +3166956,"Serranópolis de Minas",12 +4126355,"Serranópolis do Iguaçu",15 +3167004,"Serranos",12 +2515906,"Serraria",14 +2413508,"Serrinha",19 +2930501,"Serrinha",5 +2413557,"Serrinha dos Pintos",19 +2614006,"Serrita",16 +3167103,"Serro",12 +2930600,"Serrolândia",5 +4126405,"Sertaneja",15 +2614105,"Sertânia",16 +4126504,"Sertanópolis",15 +4320503,"Sertão",20 +4320552,"Sertão Santana",20 +3551702,"Sertãozinho",24 +2515930,"Sertãozinho",14 +3551801,"Sete Barras",24 +4320578,"Sete de Setembro",20 +3167202,"Sete Lagoas",12 +5007703,"Sete Quedas",11 +3165552,"Setubinha",12 +4320602,"Severiano de Almeida",20 +2413607,"Severiano Melo",19 +3551900,"Severínia",24 +4217600,"Siderópolis",23 +5007901,"Sidrolândia",11 +2210656,"Sigefredo Pacheco",17 +3305604,"Silva Jardim",18 +5220603,"Silvânia",8 +1720655,"Silvanópolis",26 +4320651,"Silveira Martins",20 +3167301,"Silveirânia",12 +3552007,"Silveiras",24 +1304005,"Silves",4 +3167400,"Silvianópolis",12 +2807105,"Simão Dias",25 +3167509,"Simão Pereira",12 +2210706,"Simões",17 +2930709,"Simões Filho",5 +5220686,"Simolândia",8 +3167608,"Simonésia",12 +2210805,"Simplício Mendes",17 +4320677,"Sinimbu",20 +5107909,"Sinop",10 +4126603,"Siqueira Campos",15 +2614204,"Sirinhaém",16 +2807204,"Siriri",25 +5220702,"Sítio d'Abadia",8 +2930758,"Sítio do Mato",5 +2930766,"Sítio do Quinto",5 +2111805,"Sítio Novo",9 +2413706,"Sítio Novo",19 +1720804,"Sítio Novo do Tocantins",26 +2930774,"Sobradinho",5 +4320701,"Sobradinho",20 +2515971,"Sobrado",14 +2312908,"Sobral",6 +3167707,"Sobrália",12 +3552106,"Socorro",24 +2210904,"Socorro do Piauí",17 +2516003,"Solânea",14 +2516102,"Soledade",14 +4320800,"Soledade",20 +3167806,"Soledade de Minas",12 +2614402,"Solidão",16 +2313005,"Solonópole",6 +4217709,"Sombrio",23 +5007935,"Sonora",11 +3205010,"Sooretama",7 +3552205,"Sorocaba",24 +5107925,"Sorriso",10 +2516151,"Sossêgo",14 +1200450,"Senador Guiomard",1 +1101500,"Seringueiras",21 +1507904,"Soure",13 +2516201,"Sousa",14 +2930808,"Souto Soares",5 +1720853,"Sucupira",26 +2111904,"Sucupira do Norte",9 +2111953,"Sucupira do Riachão",9 +3552304,"Sud Mennucci",24 +4217758,"Sul Brasil",23 +4126652,"Sulina",15 +3552403,"Sumaré",24 +2516300,"Sumé",14 +3305703,"Sumidouro",18 +2614501,"Surubim",16 +2210938,"Sussuapara",17 +3552551,"Suzanápolis",24 +3552502,"Suzano",24 +4320859,"Tabaí",20 +5107941,"Tabaporã",10 +3552601,"Tabapuã",24 +3552700,"Tabatinga",24 +1304062,"Tabatinga",4 +2614600,"Tabira",16 +3552809,"Taboão da Serra",24 +2930907,"Tabocas do Brejo Velho",5 +2413805,"Taboleiro Grande",19 +3167905,"Tabuleiro",12 +2313104,"Tabuleiro do Norte",6 +2614709,"Tacaimbó",16 +2614808,"Tacaratu",16 +3552908,"Taciba",24 +2516409,"Tacima",14 +5007950,"Tacuru",11 +3553005,"Taguaí",24 +1720903,"Taguatinga",26 +3553104,"Taiaçu",24 +1507953,"Tailândia",13 +4217808,"Taió",23 +3168002,"Taiobeiras",12 +1720937,"Taipas do Tocantins",26 +2413904,"Taipu",19 +3553203,"Taiúva",24 +1720978,"Talismã",26 +2614857,"Tamandaré",16 +4126678,"Tamarana",15 +3553302,"Tambaú",24 +4126702,"Tamboara",15 +2313203,"Tamboril",6 +2210953,"Tamboril do Piauí",17 +3553401,"Tanabi",24 +2414001,"Tangará",19 +4217907,"Tangará",23 +5107958,"Tangará da Serra",10 +3305752,"Tanguá",18 +2931004,"Tanhaçu",5 +2709004,"Tanque d'Arca",2 +2210979,"Tanque do Piauí",17 +2931053,"Tanque Novo",5 +2931103,"Tanquinho",5 +3168051,"Taparuba",12 +1304104,"Tapauá",4 +4126801,"Tapejara",15 +4320909,"Tapejara",20 +4321006,"Tapera",20 +2931202,"Taperoá",5 +2516508,"Taperoá",14 +4321105,"Tapes",20 +4126900,"Tapira",15 +3168101,"Tapira",12 +3168200,"Tapiraí",12 +3553500,"Tapiraí",24 +2931301,"Tapiramutá",5 +3553609,"Tapiratiba",24 +5108006,"Tapurah",10 +4321204,"Taquara",20 +3168309,"Taquaraçu de Minas",12 +3553658,"Taquaral",24 +5221007,"Taquaral de Goiás",8 +2709103,"Taquarana",2 +4321303,"Taquari",20 +3553708,"Taquaritinga",24 +2615003,"Taquaritinga do Norte",16 +3553807,"Taquarituba",24 +3553856,"Taquarivaí",24 +4321329,"Taquaruçu do Sul",20 +5007976,"Taquarussu",11 +3553906,"Tarabai",24 +2313252,"Tarrafas",6 +1600709,"Tartarugalzinho",3 +3553955,"Tarumã",24 +3168408,"Tarumirim",12 +2112001,"Tasso Fragoso",9 +3554003,"Tatuí",24 +2313302,"Tauá",6 +3554102,"Taubaté",24 +4321352,"Tavares",20 +2516607,"Tavares",14 +1304203,"Tefé",4 +2516706,"Teixeira",14 +2931350,"Teixeira de Freitas",5 +4127007,"Teixeira Soares",15 +3168507,"Teixeiras",12 +2313351,"Tejuçuoca",6 +3554201,"Tejupá",24 +4127106,"Telêmaco Borba",15 +2807303,"Telha",25 +2414100,"Tenente Ananias",19 +2414159,"Tenente Laurentino Cruz",19 +4321402,"Tenente Portela",20 +2516755,"Tenório",14 +2931400,"Teodoro Sampaio",5 +3554300,"Teodoro Sampaio",24 +2931509,"Teofilândia",5 +3168606,"Teófilo Otoni",12 +2931608,"Teolândia",5 +2709152,"Teotônio Vilela",2 +5008008,"Terenos",11 +5221080,"Teresina de Goiás",8 +3305802,"Teresópolis",18 +2615102,"Terezinha",16 +5221197,"Terezópolis de Goiás",8 +1507961,"Terra Alta",13 +4127205,"Terra Boa",15 +4321436,"Terra de Areia",20 +2931707,"Terra Nova",5 +2615201,"Terra Nova",16 +5108055,"Terra Nova do Norte",10 +4127304,"Terra Rica",15 +4127403,"Terra Roxa",15 +3554409,"Terra Roxa",24 +1507979,"Terra Santa",13 +5108105,"Tesouro",10 +4321451,"Teutônia",20 +2313401,"Tianguá",6 +4127502,"Tibagi",15 +2411056,"Tibau",19 +2414209,"Tibau do Sul",19 +3554508,"Tietê",24 +4217956,"Tigrinhos",23 +4218004,"Tijucas",23 +4127601,"Tijucas do Sul",15 +2615300,"Timbaúba",16 +2414308,"Timbaúba dos Batistas",19 +4218103,"Timbé do Sul",23 +2112100,"Timbiras",9 +4218202,"Timbó",23 +4218251,"Timbó Grande",23 +3554607,"Timburi",24 +2112209,"Timon",9 +3168705,"Timóteo",12 +4321469,"Tio Hugo",20 +3168804,"Tiradentes",12 +4321477,"Tiradentes do Sul",20 +3168903,"Tiros",12 +2807402,"Tobias Barreto",25 +1721109,"Tocantínia",26 +1721208,"Tocantinópolis",26 +2211001,"Teresina",17 +1101559,"Teixeirópolis",21 +1101609,"Theobroma",21 +3169000,"Tocantins",12 +3169059,"Tocos do Moji",12 +3169109,"Toledo",12 +4127700,"Toledo",15 +2807501,"Tomar do Geru",25 +4127809,"Tomazina",15 +3169208,"Tombos",12 +1508001,"Tomé-Açu",13 +1304237,"Tonantins",4 +2615409,"Toritama",16 +5108204,"Torixoréu",10 +4321493,"Toropi",20 +3554656,"Torre de Pedra",24 +4321501,"Torres",20 +3554706,"Torrinha",24 +2414407,"Touros",19 +3554755,"Trabiju",24 +1508035,"Tracuateua",13 +2615508,"Tracunhaém",16 +2709202,"Traipu",2 +1508050,"Trairão",13 +2313500,"Trairi",6 +3305901,"Trajano de Moraes",18 +4321600,"Tramandaí",20 +4321626,"Travesseiro",20 +2931806,"Tremedal",5 +3554805,"Tremembé",24 +4321634,"Três Arroios",20 +4218301,"Três Barras",23 +4127858,"Três Barras do Paraná",15 +4321667,"Três Cachoeiras",20 +3169307,"Três Corações",12 +4321709,"Três Coroas",20 +4321808,"Três de Maio",20 +4321832,"Três Forquilhas",20 +3554904,"Três Fronteiras",24 +5008305,"Três Lagoas",11 +3169356,"Três Marias",12 +4321857,"Três Palmeiras",20 +4321907,"Três Passos",20 +3169406,"Três Pontas",12 +5221304,"Três Ranchos",8 +3306008,"Três Rios",18 +4218350,"Treviso",23 +4218400,"Treze de Maio",23 +4218509,"Treze Tílias",23 +5221403,"Trindade",8 +2615607,"Trindade",16 +4321956,"Trindade do Sul",20 +4322004,"Triunfo",20 +2516805,"Triunfo",14 +2615706,"Triunfo",16 +2414456,"Triunfo Potiguar",19 +2112233,"Trizidela do Vale",9 +5221452,"Trombas",8 +4218608,"Trombudo Central",23 +4218707,"Tubarão",23 +2931905,"Tucano",5 +1508084,"Tucumã",13 +4322103,"Tucunduva",20 +1508100,"Tucuruí",13 +2112274,"Tufilândia",9 +3554953,"Tuiuti",24 +3169505,"Tumiritinga",12 +4218756,"Tunápolis",23 +4322152,"Tunas",20 +4127882,"Tunas do Paraná",15 +4127908,"Tuneiras do Oeste",15 +2112308,"Tuntum",9 +3555000,"Tupã",24 +3169604,"Tupaciguara",12 +2615805,"Tupanatinga",16 +4322186,"Tupanci do Sul",20 +4322202,"Tupanciretã",20 +4322251,"Tupandi",20 +4322301,"Tuparendi",20 +2615904,"Tuparetama",16 +4127957,"Tupãssi",15 +3555109,"Tupi Paulista",24 +1721257,"Tupirama",26 +1721307,"Tupiratins",26 +2112407,"Turiaçu",9 +2112456,"Turilândia",9 +3555208,"Turiúba",24 +3555307,"Turmalina",24 +3169703,"Turmalina",12 +4322327,"Turuçu",20 +2313559,"Tururu",6 +5221502,"Turvânia",8 +5221551,"Turvelândia",8 +4127965,"Turvo",15 +4218806,"Turvo",23 +3169802,"Turvolândia",12 +2112506,"Tutóia",9 +1304260,"Uarini",4 +2932002,"Uauá",5 +3169901,"Ubá",12 +3170008,"Ubaí",12 +2932101,"Ubaíra",5 +2932200,"Ubaitaba",5 +2313609,"Ubajara",6 +3170057,"Ubaporanga",12 +3555356,"Ubarana",24 +2932309,"Ubatã",5 +3555406,"Ubatuba",24 +3170107,"Uberaba",12 +3170206,"Uberlândia",12 +3555505,"Ubirajara",24 +4128005,"Ubiratã",15 +4322343,"Ubiretama",20 +3555604,"Uchoa",24 +2932408,"Uibaí",5 +1400704,"Uiramutã",22 +5221577,"Uirapuru",8 +2516904,"Uiraúna",14 +1508126,"Ulianópolis",13 +2313708,"Umari",6 +2414506,"Umarizal",19 +2807600,"Umbaúba",25 +2932457,"Umburanas",5 +3170305,"Umburatiba",12 +2517001,"Umbuzeiro",14 +2313757,"Umirim",6 +4128104,"Umuarama",15 +2932507,"Una",5 +3170404,"Unaí",12 +2211100,"União",17 +4322350,"União da Serra",20 +4128203,"União da Vitória",15 +3170438,"União de Minas",12 +4218855,"União do Oeste",23 +5108303,"União do Sul",10 +2709301,"União dos Palmares",2 +3555703,"União Paulista",24 +4128302,"Uniflor",15 +4322376,"Unistalda",20 +2414605,"Upanema",19 +4128401,"Uraí",15 +2932606,"Urandi",5 +3555802,"Urânia",24 +2112605,"Urbano Santos",9 +3555901,"Uru",24 +5221601,"Uruaçu",8 +5221700,"Uruana",8 +3170479,"Uruana de Minas",12 +1508159,"Uruará",13 +4218905,"Urubici",23 +2313807,"Uruburetama",6 +3170503,"Urucânia",12 +1304302,"Urucará",4 +2932705,"Uruçuca",5 +2211209,"Uruçuí",17 +3170529,"Urucuia",12 +1304401,"Urucurituba",4 +4322400,"Uruguaiana",20 +2313906,"Uruoca",6 +4218954,"Urupema",23 +3556008,"Urupês",24 +4219002,"Urussanga",23 +5221809,"Urutaí",8 +2932804,"Utinga",5 +4322509,"Vacaria",20 +5108352,"Vale de São Domingos",10 +4322533,"Vale do Sol",20 +4322541,"Vale Real",20 +4322525,"Vale Verde",20 +2932903,"Valença",5 +3306107,"Valença",18 +2211308,"Valença do Piauí",17 +2933000,"Valente",5 +3556107,"Valentim Gentil",24 +3556206,"Valinhos",24 +3556305,"Valparaíso",24 +5221858,"Valparaíso de Goiás",8 +4322558,"Vanini",20 +4219101,"Vargeão",23 +4219150,"Vargem",23 +3556354,"Vargem",24 +3170578,"Vargem Alegre",12 +3205036,"Vargem Alta",7 +3170602,"Vargem Bonita",12 +4219176,"Vargem Bonita",23 +2112704,"Vargem Grande",9 +3170651,"Vargem Grande do Rio Pardo",12 +3556404,"Vargem Grande do Sul",24 +3556453,"Vargem Grande Paulista",24 +3170701,"Varginha",12 +5221908,"Varjão",8 +3170750,"Varjão de Minas",12 +2313955,"Varjota",6 +3306156,"Varre-Sai",18 +2414704,"Várzea",19 +2517100,"Várzea",14 +2314003,"Várzea Alegre",6 +2211357,"Várzea Branca",17 +3170800,"Várzea da Palma",12 +2933059,"Várzea da Roça",5 +2933109,"Várzea do Poço",5 +2211407,"Várzea Grande",17 +5108402,"Várzea Grande",10 +2933158,"Várzea Nova",5 +3556503,"Várzea Paulista",24 +2933174,"Varzedo",5 +3170909,"Varzelândia",12 +3306206,"Vassouras",18 +3171006,"Vazante",12 +4322608,"Venâncio Aires",20 +3205069,"Venda Nova do Imigrante",7 +2414753,"Venha-Ver",19 +4128534,"Ventania",15 +2616001,"Venturosa",16 +5108501,"Vera",10 +2414803,"Vera Cruz",19 +2933208,"Vera Cruz",5 +4322707,"Vera Cruz",20 +3556602,"Vera Cruz",24 +4128559,"Vera Cruz do Oeste",15 +2211506,"Vera Mendes",17 +4322806,"Veranópolis",20 +2616100,"Verdejante",16 +3171030,"Verdelândia",12 +4128609,"Verê",15 +2933257,"Vereda",5 +3171071,"Veredinha",12 +3171105,"Veríssimo",12 +3171154,"Vermelho Novo",12 +2616183,"Vertente do Lério",16 +2616209,"Vertentes",16 +3171204,"Vespasiano",12 +4322855,"Vespasiano Corrêa",20 +4322905,"Viadutos",20 +4323002,"Viamão",20 +3205101,"Viana",7 +2112803,"Viana",9 +5222005,"Vianópolis",8 +2616308,"Vicência",16 +4323101,"Vicente Dutra",20 +5008404,"Vicentina",11 +5222054,"Vicentinópolis",8 +2414902,"Viçosa",19 +2709400,"Viçosa",2 +3171303,"Viçosa",12 +2314102,"Viçosa do Ceará",6 +4323200,"Victor Graeff",20 +4219200,"Vidal Ramos",23 +4219309,"Videira",23 +3171402,"Vieiras",12 +2517209,"Vieirópolis",14 +1508209,"Vigia",13 +5105507,"Vila Bela da Santíssima Trindade",10 +5222203,"Vila Boa",8 +2415008,"Vila Flor",19 +4323309,"Vila Flores",20 +4323358,"Vila Lângaro",20 +4323408,"Vila Maria",20 +2211605,"Vila Nova do Piauí",17 +4323457,"Vila Nova do Sul",20 +2112852,"Vila Nova dos Martírios",9 +3205150,"Vila Pavão",7 +5222302,"Vila Propício",8 +5108600,"Vila Rica",10 +3205176,"Vila Valério",7 +3205200,"Vila Velha",7 +3556701,"Vinhedo",24 +3556800,"Viradouro",24 +3171600,"Virgem da Lapa",12 +3171709,"Virgínia",12 +3171808,"Virginópolis",12 +3171907,"Virgolândia",12 +4128658,"Virmond",15 +3172004,"Visconde do Rio Branco",12 +1508308,"Viseu",13 +4323507,"Vista Alegre",20 +3556909,"Vista Alegre do Alto",24 +4323606,"Vista Alegre do Prata",20 +4323705,"Vista Gaúcha",20 +2505501,"Vista Serrana",14 +4219358,"Vitor Meireles",23 +3556958,"Vitória Brasil",24 +2933307,"Vitória da Conquista",5 +4323754,"Vitória das Missões",20 +2616407,"Vitória de Santo Antão",16 +1600808,"Vitória do Jari",3 +2112902,"Vitória do Mearim",9 +1508357,"Vitória do Xingu",13 +4128708,"Vitorino",15 +2113009,"Vitorino Freire",9 +3172103,"Volta Grande",12 +3306305,"Volta Redonda",18 +3557006,"Votorantim",24 +3557105,"Votuporanga",24 +2933406,"Wagner",5 +2211704,"Wall Ferraz",17 +1722081,"Wanderlândia",26 +2933455,"Wanderley",5 +3172202,"Wenceslau Braz",12 +4128500,"Wenceslau Braz",15 +2933505,"Wenceslau Guimarães",5 +4323770,"Westfália",20 +4219408,"Witmarsum",23 +1722107,"Xambioá",26 +4128807,"Xambrê",15 +4323804,"Xangri-lá",20 +4219507,"Xanxerê",23 +4219606,"Xavantina",23 +4219705,"Xaxim",23 +2616506,"Xexéu",16 +1508407,"Xinguara",13 +2933604,"Xique-Xique",5 +2517407,"Zabelê",14 +3557154,"Zacarias",24 +2114007,"Zé Doca",9 +4219853,"Zortéa",23 +2704302,"Maceió",2 +1200708,"Xapuri",1 +1101757,"Vale do Anari",21 +1101807,"Vale do Paraíso",21 +1100304,"Vilhena",21 +1200013,"Acrelândia",1 +1200054,"Assis Brasil",1 +1200104,"Brasiléia",1 +1200138,"Bujari",1 +1200179,"Capixaba",1 +1200203,"Cruzeiro do Sul",1 +1200252,"Epitaciolândia",1 +1200328,"Jordão",1 +1200385,"Plácido de Castro",1 +1200807,"Porto Acre",1 +1200401,"Rio Branco",1 +1200427,"Rodrigues Alves",1 +1200435,"Santa Rosa do Purus",1 +1200500,"Sena Madureira",1 +1200609,"Tarauacá",1 +3550308,"São Paulo",24 +3106200,"Belo Horizonte",12 +1600303,"Macapá",3 +2304400,"Fortaleza",6 +3205309,"Vitória",7 +5208707,"Goiânia",8 +1100403,"Alto Paraíso",21 +1100908,"Castanheiras",21 +2408102,"Natal",19 +1721000,"Palmas",26 +1100924,"Chupinguaia",21 +1100098,"Espigão D'Oeste",21 +1101005,"Governador Jorge Teixeira",21 +1101104,"Itapuã do Oeste",21 +1101203,"Ministro Andreazza",21 +1101401,"Monte Negro",21 +1100338,"Nova Mamoré",21 +1100155,"Ouro Preto do Oeste",21 +1101476,"Primavera de Rondônia",21 +1101484,"São Felipe D'Oeste",21 +1101492,"São Francisco do Guaporé",21 +1101708,"Urupá",21 diff --git a/app/Program.cs b/app/Program.cs index 70cce3f..aa83f2d 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -1,7 +1,8 @@ using app.DI; -using dominio.Mapper; +using app.Entidades; +using app.Services.Mapper; +using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; -using DotNetEnv; var builder = WebApplication.CreateBuilder(args); @@ -23,17 +24,17 @@ { Version = "v1", Title = "UsuarioService", - Description = "Microserivo UsuarioService" + Description = "Microserviço UsuarioService" }); }); +builder.Services.AddConfiguracoes(builder.Configuration); + builder.Services.AddConfigServices(builder.Configuration); builder.Services.AddConfigRepositorios(); -builder.Services.AddContexto(builder.Configuration); - -builder.Services.AddCors(options => +builder.Services.AddCors(options => { options.AddPolicy("AllowAllOrigins", builder => @@ -54,10 +55,20 @@ app.UseSwaggerUI(); -app.UseHttpsRedirection(); +//app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider + .GetRequiredService(); + + dbContext.Database.Migrate(); + dbContext.Popula(); +} + app.Run(); diff --git a/app/Properties/launchSettings.json b/app/Properties/launchSettings.json index af4ec8d..3984f3e 100644 --- a/app/Properties/launchSettings.json +++ b/app/Properties/launchSettings.json @@ -11,7 +11,7 @@ "UsuarioService": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "applicationUrl": "https://localhost:7083", "environmentVariables": { @@ -19,12 +19,12 @@ } }, "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "commandName": "IISExpress", + "launchBrowser": false, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } } } diff --git a/app/Repositorios/Interfaces/IPerfilRepositorio.cs b/app/Repositorios/Interfaces/IPerfilRepositorio.cs new file mode 100644 index 0000000..9cd80fb --- /dev/null +++ b/app/Repositorios/Interfaces/IPerfilRepositorio.cs @@ -0,0 +1,16 @@ +using api; +using api.Perfis; +using app.Entidades; + +namespace app.Repositorios.Interfaces +{ + public interface IPerfilRepositorio + { + public Perfil RegistraPerfil(Perfil perfil); + public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permissao); + public void RemovePerfil(Perfil perfil); + public void RemovePermissaoDoPerfil(PerfilPermissao perfilPermissao); + public Task ObterPerfilPorIdAsync(Guid id); + public Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null); + } +} \ No newline at end of file diff --git a/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs new file mode 100644 index 0000000..24dcfaf --- /dev/null +++ b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs @@ -0,0 +1,9 @@ +using api; + +namespace app.Repositorios.Interfaces +{ + public interface IUnidadeFederativaRepositorio + { + IEnumerable ObterDominio(); + } +} diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs new file mode 100644 index 0000000..029f8cc --- /dev/null +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -0,0 +1,19 @@ +using api; +using api.Usuarios; +using app.Entidades; + +namespace app.Repositorios.Interfaces +{ + public interface IUsuarioRepositorio + { + Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); + Usuario? ObterUsuario(string email); + Task ObterUsuarioAsync(int? id = null, string? email = null, bool includePerfil = false); + UsuarioModel? TrocarSenha(string senha, string email); + void InserirDadosRecuperacao(string uuid, int idUsuario); + string? ObterEmailRedefinicaoSenha(string uuid); + void RemoverUuidRedefinicaoSenha(string uuid); + Task CadastrarUsuarioDnit(UsuarioDnit usuario); + Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); + } +} diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs new file mode 100644 index 0000000..60f79df --- /dev/null +++ b/app/Repositorios/PerfilRepositorio.cs @@ -0,0 +1,90 @@ +using api; +using app.Entidades; +using app.Repositorios.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace app.Repositorios +{ + public class PerfilRepositorio : IPerfilRepositorio + { + private AppDbContext dbContext; + + public PerfilRepositorio(AppDbContext dbContext) + { + this.dbContext = dbContext; + } + + public Perfil RegistraPerfil(Perfil perfil) + { + perfil.Id = Guid.NewGuid(); + + dbContext.Add(perfil); + + return perfil; + } + + public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permissao) + { + var novoPerfilPermissao = new PerfilPermissao + { + Id = Guid.NewGuid(), + PerfilId = perfilId, + Permissao = permissao + }; + + dbContext.Add(novoPerfilPermissao); + + return novoPerfilPermissao; + } + + public void RemovePerfil(Perfil perfil) + { + DefinirPerfilBasicoParaUsuariosComPerfilParaExcluir(perfil.Id); + dbContext.Perfis.Remove(perfil); + } + + private void DefinirPerfilBasicoParaUsuariosComPerfilParaExcluir(Guid perfilParaExcluirId) + { + var usuariosComPerfilParaExcluir = dbContext.Usuario.Where(u => u.PerfilId == perfilParaExcluirId); + if (usuariosComPerfilParaExcluir.Any()) + { + var perfilBasico = dbContext.Perfis.Where(p => p.Tipo == TipoPerfil.Basico).First(); + foreach (var u in usuariosComPerfilParaExcluir) + u.PerfilId = perfilBasico.Id; + } + } + + public void RemovePermissaoDoPerfil(PerfilPermissao perfilPermissao) + { + dbContext.PerfilPermissoes.Remove(perfilPermissao); + } + + public async Task ObterPerfilPorIdAsync(Guid id) + { + var query = dbContext.Perfis.AsQueryable(); + + query = query.Include(p => p.PerfilPermissoes); + + return await query.FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) + { + var query = dbContext.Perfis.AsQueryable(); + + query = query.Include(p => p.PerfilPermissoes) + .Include(p => p.Usuarios); + + if (!string.IsNullOrWhiteSpace(nome)) + { + query = query.Where(p => p.Nome.ToLower().Contains(nome.ToLower())); + } + + return await query + .OrderBy(p => p.Nome) + .Skip((pageIndex - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs new file mode 100644 index 0000000..9cddeea --- /dev/null +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -0,0 +1,20 @@ +using api; +using app.Repositorios.Interfaces; +using AutoMapper; + +namespace app.Repositorios +{ + public class UnidadeFederativaRepositorio : IUnidadeFederativaRepositorio + { + private readonly IMapper mapper; + public UnidadeFederativaRepositorio(IMapper mapper) + { + this.mapper = mapper; + } + + public IEnumerable ObterDominio() + { + return Enum.GetValues().Select(uf => mapper.Map(uf)).OrderBy(uf => uf.Sigla); + } + } +} diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs new file mode 100644 index 0000000..bf62e84 --- /dev/null +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -0,0 +1,153 @@ +using Microsoft.EntityFrameworkCore; +using AutoMapper; + +using app.Entidades; +using api.Usuarios; +using app.Repositorios.Interfaces; +using api; + +namespace app.Repositorios +{ + public class UsuarioRepositorio : IUsuarioRepositorio + { + private readonly AppDbContext dbContext; + private readonly IMapper mapper; + + public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) + { + this.dbContext = dbContext; + this.mapper = mapper; + } + + public Usuario? ObterUsuario(string email) + { + return dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); + } + + public async Task ObterUsuarioAsync(int? id = null, string? email = null, bool includePerfil = false) + { + var query = dbContext.Usuario.AsQueryable(); + + if (includePerfil) + { + query = query.Include(u => u.Perfil).ThenInclude(p => p.PerfilPermissoes); + } + if (!string.IsNullOrWhiteSpace(email)) + { + query = query.Where(u => u.Email.ToLower() == email!.ToLower()); + } + if (id.HasValue) + { + query = query.Where(u => u.Id == id); + } + + return await query.FirstOrDefaultAsync(); + } + + public async Task CadastrarUsuarioDnit(UsuarioDnit usuario) + { + var novoUsuario = new Usuario + { + Nome = usuario.Nome, + Email = usuario.Email, + Senha = usuario.Senha, + UfLotacao = usuario.UfLotacao, + Perfil = await RecuperaPerfilBasicoAsync() + }; + + dbContext.Add(novoUsuario); + } + + public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) + { + var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); + + List empresas = new() { empresa }; + + var novoUsuarioTerceiro = new Usuario + { + Nome = usuarioTerceiro.Nome, + Email = usuarioTerceiro.Email, + Senha = usuarioTerceiro.Senha, + Empresas = empresas, + Perfil = await RecuperaPerfilBasicoAsync() + }; + + dbContext.Usuario.Add(novoUsuarioTerceiro); + } + + public UsuarioModel? TrocarSenha(string email, string senha) + { + var usuario = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); + + if (usuario != null) + { + usuario.Senha = senha; + } + + return mapper.Map(usuario); + } + + public string? ObterEmailRedefinicaoSenha(string uuid) + { + var query = from rs in dbContext.RedefinicaoSenha + join u in dbContext.Usuario on rs.IdUsuario equals u.Id + where rs.Uuid == uuid + select u.Email; + + return query.FirstOrDefault(); + } + + public void RemoverUuidRedefinicaoSenha(string uuid) + { + var registro = dbContext.RedefinicaoSenha.Where(rs => rs.Uuid == uuid).FirstOrDefault(); + + dbContext.RedefinicaoSenha.Remove(registro); + } + + public void InserirDadosRecuperacao(string uuid, int idUsuario) + { + var newRs = new RedefinicaoSenha + { + Uuid = uuid, + IdUsuario = idUsuario, + }; + + dbContext.RedefinicaoSenha.Add(newRs); + } + + private async Task RecuperaPerfilBasicoAsync() + { + return await dbContext.Perfis.Where(p => p.Tipo == TipoPerfil.Basico) + .FirstOrDefaultAsync(); + } + + public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + { + var query = dbContext.Usuario + .Include(u => u.Municipio) + .Include(u => u.Perfil) + .AsQueryable(); + + if (filtro.Nome != null) + query = query.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); + + if (filtro.PerfilId != null) + query = query.Where(u => u.PerfilId == filtro.PerfilId); + + if (filtro.UfLotacao != null) + query = query.Where(u => u.UfLotacao == filtro.UfLotacao); + + if (filtro.MunicipioId != null) + query = query.Where(u => u.MunicipioId == filtro.MunicipioId); + + var total = await query.CountAsync(); + var items = await query + .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) + .Take(filtro.ItemsPorPagina) + .ToListAsync(); + + return new ListaPaginada(items, filtro.Pagina, filtro.ItemsPorPagina, total); + } + } +} diff --git a/app/Services/ApiException.cs b/app/Services/ApiException.cs new file mode 100644 index 0000000..fbe36b9 --- /dev/null +++ b/app/Services/ApiException.cs @@ -0,0 +1,79 @@ +using api; +using EnumsNET; +using Newtonsoft.Json; + +namespace app.Services +{ + public class ApiException : Exception + { + public ErrorModel Error { get; set; } + + public ApiException(ErrorCodes error, string? message = null, object? details = null) : base(message ?? error.AsString(EnumFormat.Description)) + { + Error = new ErrorModel + { + Code = error, + Message = Message, + CodeStr = error.ToString(), + Details = getDetailsDictionary(details), + }; + } + + private static Dictionary? getDetailsDictionary(object? details) + { + if (details == null) + { + return null; + } + var dic = new Dictionary(); + foreach (var descriptor in details.GetType().GetProperties()) + { + dic[descriptor.Name] = descriptor.GetValue(details, null)?.ToString() ?? "null"; + } + return dic; + } + } + + public class ErrorModel + { + [JsonProperty("code")] + public string CodeStr { get; set; } + + [JsonIgnore] + public ErrorCodes Code + { + get + { + ErrorCodes res; + try + { + res = (ErrorCodes)Enum.Parse(typeof(ErrorCodes), CodeStr); + } + catch + { + res = ErrorCodes.Unknown; + } + return res; + } + set + { + CodeStr = value.ToString(); + } + } + + public string Message { get; set; } + + public Dictionary Details { get; set; } + + public override string ToString() + { + var detailsString = string.Empty; + + if (Details != null && Details.Count > 0) + { + detailsString = Environment.NewLine + string.Join(Environment.NewLine, Details); + } + return $"{CodeStr} - {Message}{detailsString}"; + } + } +} diff --git a/app/Services/AppController.cs b/app/Services/AppController.cs new file mode 100644 index 0000000..e008575 --- /dev/null +++ b/app/Services/AppController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace app.Services +{ + public class AppController : ControllerBase + { + public ClaimsPrincipal? AppUsuario { get; set; } + public ClaimsPrincipal Usuario => AppUsuario ?? User; + } +} diff --git a/service/EmailService.cs b/app/Services/EmailService.cs similarity index 64% rename from service/EmailService.cs rename to app/Services/EmailService.cs index 2d21b72..ffcbcec 100644 --- a/service/EmailService.cs +++ b/app/Services/EmailService.cs @@ -1,32 +1,33 @@ -using service.Interfaces; +using app.Services.Interfaces; using System.Net.Mail; using System.Net; using System; -namespace service +namespace app.Services { public class EmailService : IEmailService { public void EnviarEmail(string emailDestinatario, string assunto, string corpo) { - MailMessage mensagem = new MailMessage(); - string emailRemetente = Environment.GetEnvironmentVariable("EMAIL_SERVICE_ADDRESS"); - string senhaRemetente = Environment.GetEnvironmentVariable("EMAIL_SERVICE_PASSWORD"); + string emailRemetente = DotNetEnv.Env.GetString("EMAIL_SERVICE_ADDRESS"); + string senhaRemetente = DotNetEnv.Env.GetString("EMAIL_SERVICE_PASSWORD"); mensagem.From = new MailAddress(emailRemetente); mensagem.Subject = assunto; mensagem.To.Add(new MailAddress(emailDestinatario)); mensagem.Body = corpo; - var clienteSmtp = new SmtpClient("smtp-mail.outlook.com") + var enderecoSmtp = DotNetEnv.Env.GetString("EMAIL_SERVICE_SMTP") ?? "smtp-mail.outlook.com"; + var clienteSmtp = new SmtpClient(enderecoSmtp) { Port = 587, Credentials = new NetworkCredential(emailRemetente, senhaRemetente), EnableSsl = true, }; + clienteSmtp.Send(mensagem); } } diff --git a/service/Interfaces/IEmailService.cs b/app/Services/Interfaces/IEmailService.cs similarity index 79% rename from service/Interfaces/IEmailService.cs rename to app/Services/Interfaces/IEmailService.cs index 601c4e6..b357295 100644 --- a/service/Interfaces/IEmailService.cs +++ b/app/Services/Interfaces/IEmailService.cs @@ -1,4 +1,4 @@ -namespace service.Interfaces +namespace app.Services.Interfaces { public interface IEmailService { diff --git a/app/Services/Interfaces/IPerfilService.cs b/app/Services/Interfaces/IPerfilService.cs new file mode 100644 index 0000000..592254e --- /dev/null +++ b/app/Services/Interfaces/IPerfilService.cs @@ -0,0 +1,14 @@ +using app.Entidades; +using api; + +namespace app.Services.Interfaces +{ + public interface IPerfilService + { + Perfil CriarPerfil(Perfil perfil, List permissoes); + Task EditarPerfil(Perfil perfil, List permissoes); + Task ExcluirPerfil(Guid id); + Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null); + Task ObterPorIdAsync(Guid id); + } +} \ No newline at end of file diff --git a/app/Services/Interfaces/IPermissaoService.cs b/app/Services/Interfaces/IPermissaoService.cs new file mode 100644 index 0000000..bb03f05 --- /dev/null +++ b/app/Services/Interfaces/IPermissaoService.cs @@ -0,0 +1,10 @@ +using api; + +namespace app.Services.Interfaces +{ + public interface IPermissaoService + { + List CategorizarPermissoes(List permissaos); + List ObterCategorias(); + } +} \ No newline at end of file diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs new file mode 100644 index 0000000..6d9a843 --- /dev/null +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -0,0 +1,20 @@ +using api.Usuarios; +using api.Senhas; +using api; + +namespace app.Services.Interfaces +{ + public interface IUsuarioService + { + Task AutenticarUsuarioAsync(string email, string senha); + bool ValidaLogin(UsuarioDTO usuarioDTO); + Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); + Task RecuperarSenha(UsuarioDTO usuarioDto); + Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); + void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); + Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); + Task> ListarPermissoesAsync(int userId); + Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); + Task EditarUsuarioPerfil(int usuarioId ,string novoPerfilId); + } +} diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs new file mode 100644 index 0000000..fade123 --- /dev/null +++ b/app/Services/Mapper.cs @@ -0,0 +1,56 @@ +using AutoMapper; +using api.Senhas; +using api.Usuarios; +using app.Entidades; +using api; +using EnumsNET; +using api.Perfis; +using api.Permissoes; +using api.Municipios; + +namespace app.Services.Mapper +{ + public class AutoMapperConfig : Profile + { + public AutoMapperConfig() + { + CreateMap(); + + CreateMap() + .ForMember(u => u.Cnpj, opt => opt.Ignore()); + + CreateMap() + .ForMember(u => u.CNPJ, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(model => model.Id, opt => opt.MapFrom(uf => (int)uf)) + .ForMember(model => model.Sigla, opt => opt.MapFrom(uf => uf.ToString())) + .ForMember(model => model.Nome, opt => opt.MapFrom(uf => uf.AsString(EnumFormat.Description))); + + CreateMap(); + + CreateMap() + .ForMember(p => p.Id, opt => opt.Ignore()) + .ForMember(p => p.Permissoes, opt => opt.Ignore()) + .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) + .ForMember(p => p.Usuarios, opt => opt.Ignore()) + .ForMember(p => p.Tipo, opt => opt.Ignore()) + .ForMember(p => p.PermissoesSessao, opt => opt.Ignore()); + + CreateMap() + .ForMember(model => model.Permissoes, opt => opt.MapFrom + ( + perf => perf.Permissoes.Select(p => new PermissaoModel + { + Codigo = p, + Descricao = p.AsString(EnumFormat.Description)! + }).ToList() + ) + ) + .ForMember(model => model.QuantidadeUsuarios, opt => opt.MapFrom(p => p.Usuarios.Count())) + .ForMember(model => model.CategoriasPermissao, opt => opt.Ignore()); + } + } +} \ No newline at end of file diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs new file mode 100644 index 0000000..4d056fe --- /dev/null +++ b/app/Services/PerfilService.cs @@ -0,0 +1,111 @@ +using api; +using app.Entidades; +using app.Repositorios.Interfaces; +using app.Services.Interfaces; +using AutoMapper; + +namespace app.Services +{ + public class PerfilService : IPerfilService + { + private readonly IPerfilRepositorio perfilRepositorio; + private readonly IUsuarioRepositorio usuarioRepositorio; + private readonly AppDbContext dbContext; + private readonly IMapper mapper; + + public PerfilService(IPerfilRepositorio perfilRepositorio, AppDbContext dbContext, IMapper mapper) + { + this.perfilRepositorio = perfilRepositorio; + this.dbContext = dbContext; + this.mapper = mapper; + } + + public Perfil CriarPerfil(Perfil perfil, List permissoes) + { + var novoPerfil = perfilRepositorio.RegistraPerfil(perfil); + + foreach (var permissao in permissoes) + { + perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); + } + + dbContext.SaveChanges(); + + return novoPerfil; + } + + public async Task EditarPerfil(Perfil perfil, List permissoes) + { + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? + throw new KeyNotFoundException("Perfil não encontrado"); + + perfilDb.Nome = perfil.Nome; + + var permissoesDeletadas = perfilDb.PerfilPermissoes!.Where(p => !permissoes.Contains(p.Permissao)).ToList(); + var permissoesNovas = permissoes.Where(p => !perfilDb.PerfilPermissoes!.Exists(pr => pr.Permissao == p)).ToList(); + + foreach (var permissao in permissoesDeletadas) + { + perfilRepositorio.RemovePermissaoDoPerfil(permissao); + perfilDb.PerfilPermissoes!.Remove(permissao); + } + + foreach (var permissao in permissoesNovas) + { + perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); + } + + await dbContext.SaveChangesAsync(); + + return perfilDb; + } + + public async Task ExcluirPerfil(Guid id) + { + var perfilParaExcluir = await perfilRepositorio.ObterPerfilPorIdAsync(id) ?? + throw new KeyNotFoundException("Perfil não encontrado"); + + if (perfilParaExcluir.Tipo == TipoPerfil.Basico || perfilParaExcluir.Tipo == TipoPerfil.Administrador) + { + throw new InvalidOperationException("Esse Perfil não pode ser excluído."); + } + + foreach (var perfilPermissao in perfilParaExcluir.PerfilPermissoes!) + { + perfilRepositorio.RemovePermissaoDoPerfil(perfilPermissao); + } + + perfilRepositorio.RemovePerfil(perfilParaExcluir); + dbContext.SaveChanges(); + } + + public async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) + { + var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize, nome); + var administrador = perfis.FirstOrDefault(p => p.Tipo == TipoPerfil.Administrador); + + if (administrador != null) + { + PreencherPermissoesAdministrador(administrador); + } + return perfis; + } + + public async Task ObterPorIdAsync(Guid id) + { + var perfil = await perfilRepositorio.ObterPerfilPorIdAsync(id); + + if (perfil != null && perfil.Tipo == TipoPerfil.Administrador) + { + PreencherPermissoesAdministrador(perfil); + } + + return perfil; + } + + private void PreencherPermissoesAdministrador(Perfil perfil) + { + perfil.PermissoesSessao = Enum.GetValues().ToList(); + } + } +} \ No newline at end of file diff --git a/app/Services/PermissaoService.cs b/app/Services/PermissaoService.cs new file mode 100644 index 0000000..e1cc719 --- /dev/null +++ b/app/Services/PermissaoService.cs @@ -0,0 +1,49 @@ +using System.Text.RegularExpressions; +using api; +using api.Permissoes; +using app.Services.Interfaces; +using EnumsNET; + +namespace app.Services +{ + public class PermissaoService : IPermissaoService + { + private const string pattern = @"^([A-Z][a-z]+)"; + + public List CategorizarPermissoes(List permissaos) + { + var categorias = ObterCategorias(); + + return categorias.ConvertAll(c => new CategoriaPermissaoModel + { + Categoria = c, + Permissoes = ObterPermissoesPorCategoria(c, permissaos) + }); + } + + public List ObterCategorias() + { + var permissoesOrdenadas = Enum.GetNames().OrderBy(str => str); + + var categorias = new HashSet(); + + foreach (var p in permissoesOrdenadas) + { + categorias.Add(Regex.Match(p, pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()); + } + + return categorias.ToList(); + } + + public List ObterPermissoesPorCategoria(string categoria, List permissoes) + { + return permissoes + .Where(p => categoria == Regex.Match(p.ToString(), pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()) + .Select(p => new PermissaoModel + { + Codigo = p, + Descricao = p.AsString(EnumFormat.Description)! + }).ToList(); + } + } +} \ No newline at end of file diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs new file mode 100644 index 0000000..8ed4832 --- /dev/null +++ b/app/Services/UsuarioService.cs @@ -0,0 +1,226 @@ +using api.Usuarios; +using api.Senhas; +using app.Repositorios.Interfaces; +using app.Services.Interfaces; +using AutoMapper; +using BCryptNet = BCrypt.Net.BCrypt; +using app.Entidades; +using api; +using auth; +using Microsoft.Extensions.Options; +using app.Configuracoes; + +namespace app.Services +{ + public class UsuarioService : IUsuarioService + { + + private readonly IUsuarioRepositorio usuarioRepositorio; + private readonly IPerfilRepositorio perfilRepositorio; + private readonly IMapper mapper; + private readonly IEmailService emailService; + private readonly SenhaConfig senhaConfig; + private readonly AppDbContext dbContext; + private readonly AuthService autenticacaoService; + private readonly AuthConfig authConfig; + + public UsuarioService + ( + IUsuarioRepositorio usuarioRepositorio, + IPerfilRepositorio perfilRepositorio, + IMapper mapper, + IEmailService emailService, + IOptions senhaConfig, + AppDbContext dbContext, + AuthService autenticacaoService, + IOptions authConfig + ) + { + this.perfilRepositorio = perfilRepositorio; + this.usuarioRepositorio = usuarioRepositorio; + this.mapper = mapper; + this.emailService = emailService; + this.senhaConfig = senhaConfig.Value; + this.dbContext = dbContext; + this.autenticacaoService = autenticacaoService; + this.authConfig = authConfig.Value; + } + + public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) + { + if (usuarioDTO.UfLotacao == 0) + throw new ApiException(ErrorCodes.CodigoUfInvalido); + + var usuario = mapper.Map(usuarioDTO); + + usuario.Senha = EncriptarSenha(usuario.Senha); + + await usuarioRepositorio.CadastrarUsuarioDnit(usuario); + + await dbContext.SaveChangesAsync(); + } + + private string EncriptarSenha(string senha) + { + string salt = BCryptNet.GenerateSalt(); + + return BCryptNet.HashPassword(senha, salt); + } + + public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) + { + var usuario = mapper.Map(usuarioDTO); + usuario.Senha = EncriptarSenha(usuario.Senha); + usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); + } + + public async Task AutenticarUsuarioAsync(string email, string senha) + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(email: email, includePerfil: true) + ?? throw new KeyNotFoundException(); + + ValidaSenha(senha, usuario.Senha); + + return await CriarTokenAsync(usuario); + } + + private Usuario? Obter(string email) + { + Usuario? usuario = usuarioRepositorio.ObterUsuario(email); + + if (usuario == null) + throw new KeyNotFoundException(); + + return usuario; + } + + public bool ValidaLogin(UsuarioDTO usuarioDTO) + { + Usuario? usuarioBanco = Obter(usuarioDTO.Email); + + return ValidaSenha(usuarioDTO.Senha, usuarioBanco.Senha); + } + + private bool ValidaSenha(string senhaUsuarioEntrada, string senhaUsuarioBanco) + { + if (BCryptNet.Verify(senhaUsuarioEntrada, senhaUsuarioBanco)) + return true; + + throw new UnauthorizedAccessException(); + } + + public async Task TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) + { + RedefinicaoSenhaModel dadosRedefinicaoSenha = mapper.Map(redefinicaoSenhaDTO); + + string emailUsuario = usuarioRepositorio.ObterEmailRedefinicaoSenha(dadosRedefinicaoSenha.UuidAutenticacao) ?? throw new KeyNotFoundException(); + string senha = EncriptarSenha(dadosRedefinicaoSenha.Senha); + + usuarioRepositorio.TrocarSenha(emailUsuario, senha); + + emailService.EnviarEmail(emailUsuario, "Senha Atualizada", "A sua senha foi atualizada com sucesso."); + + usuarioRepositorio.RemoverUuidRedefinicaoSenha(dadosRedefinicaoSenha.UuidAutenticacao); + + await dbContext.SaveChangesAsync(); + } + + public async Task RecuperarSenha(UsuarioDTO usuarioDTO) + { + var usuarioEntrada = mapper.Map(usuarioDTO); + + Usuario usuarioBanco = Obter(usuarioEntrada.Email); + + string UuidAutenticacao = Guid.NewGuid().ToString(); + + usuarioRepositorio.InserirDadosRecuperacao(UuidAutenticacao, usuarioBanco.Id); + + string mensagem = "Olá!!\n\n" + + "Recebemos uma solicitação para redefinir a sua senha.\n\n" + + "Clique no link abaixo para ser direcionado a página de Redefinição de Senha.\n\n" + + $"{GerarLinkDeRecuperacao(UuidAutenticacao)}"; + + emailService.EnviarEmail(usuarioBanco.Email, "Link de Recuperação", mensagem); + + await dbContext.SaveChangesAsync(); + } + private string GerarLinkDeRecuperacao(string UuidAutenticacao) + { + var baseUrl = senhaConfig.RedefinirSenhaUrl; + + string link = $"{baseUrl}?token={UuidAutenticacao}"; + + return link; + } + + public async Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto) + { + var usuarioId = autenticacaoService.GetUserId(atualizarTokenDto.Token); + var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId, includePerfil: true); + + if (usuario?.TokenAtualizacao != atualizarTokenDto.TokenAtualizacao || !usuario.TokenAtualizacaoExpiracao.HasValue || usuario.TokenAtualizacaoExpiracao.Value <= DateTimeOffset.Now) + { + throw new AuthForbiddenException("Token expirado. Realize o login novamente."); + } + return await CriarTokenAsync(usuario); + } + + private async Task CriarTokenAsync(Usuario usuario) + { + var permissoes = usuario.Perfil?.Permissoes?.ToList() ?? new(); + + if (!authConfig.Enabled || usuario.Perfil.Tipo == TipoPerfil.Administrador) + permissoes = Enum.GetValues().ToList(); + + var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel + { + Id = usuario.Id, + Name = usuario.Nome, + Permissions = permissoes, + }); + + var (tokenAtualizacao, tokenAtualizacaoExpiracao) = autenticacaoService.GenerateRefreshToken(); + + usuario.TokenAtualizacao = tokenAtualizacao; + usuario.TokenAtualizacaoExpiracao = tokenAtualizacaoExpiracao; + await dbContext.SaveChangesAsync(); + + return new LoginModel() + { + Token = "Bearer " + token, + ExpiraEm = expiraEm, + TokenAtualizacao = tokenAtualizacao, + Permissoes = permissoes, + }; + } + + public async Task> ListarPermissoesAsync(int userId) + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(userId, includePerfil: true); + if (usuario!.Perfil?.Tipo == TipoPerfil.Administrador || !authConfig.Enabled) + { + usuario.Perfil.PermissoesSessao = Enum.GetValues().ToList(); + } + return usuario!.Perfil?.Permissoes?.ToList() ?? new(); + } + + public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + { + var usuarios = await usuarioRepositorio.ObterUsuariosAsync(filtro); + var modelos = mapper.Map>(usuarios.Items); + return new ListaPaginada(modelos, filtro.Pagina, filtro.ItemsPorPagina, usuarios.Total); + } + + public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId) + ?? throw new ApiException(ErrorCodes.UsuarioNaoEncontrado); + + var permissao = await perfilRepositorio.ObterPerfilPorIdAsync(Guid.Parse(novoPerfilId)) + ?? throw new ApiException(ErrorCodes.PermissaoNaoEncontrada); + + usuario.PerfilId = permissao.Id; + dbContext.SaveChanges(); + } + } +} diff --git a/app/app.csproj b/app/app.csproj index 4953896..349bb09 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -1,21 +1,48 @@ - - net6.0 - enable - enable - + + net6.0 + enable + enable + 458d8e9b-3d94-4e66-8b4d-cd89daee7157 + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + - - - - - + + + + + + + Migrations\**, DI\**, Program.cs, Entidades/AppDbContext.cs + + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 70b8722..4edc16d 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,11 +1,26 @@ { - "ConnectionStrings": { - "PostgreSql": "Host=database-dnit-eps-mds.coteugcvtnid.us-east-1.rds.amazonaws.com;Port=5432;Database=postgres;Username=epsmds;Password=epsmds2023" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "ConnectionStrings": { + "PostgreSql": "Host=localhost;Port=5432;Database=usuarioservice;Username=postgres;Password=1234", + "PostgreSqlDocker": "Host=dnit-usuario-db;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Senha": { + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" + }, + "Auth": { + "Enabled": true, + "Key": "chave secreta chave secreta chave secreta chave secreta chave secreta chave secreta", + "Issuer": "https://localhost:7083/", + "Audience": "https://localhost:7083/", + "ValidateIssuer": false, + "ValidateAudience": false, + "ValidateIssuerSigningKey": false, + "ExpireMinutes": 10, + "RefreshTokenExpireMinutes": 120 } - } -} +} \ No newline at end of file diff --git a/app/appsettings.Test.json b/app/appsettings.Test.json new file mode 100644 index 0000000..b7b6cf0 --- /dev/null +++ b/app/appsettings.Test.json @@ -0,0 +1,12 @@ +{ + "Auth": { + "Enabled": true, + "Key": "tese secreta tese secreta tese secreta tese secreta tese secreta tese secreta", + "Issuer": "https://localhost:7083/", + "Audience": "https://localhost:7083/", + "ValidateIssuer": false, + "ValidateAudience": false, + "ValidateIssuerSigningKey": false, + "ExpireMinutes": 5 + } +} \ No newline at end of file diff --git a/app/appsettings.json b/app/appsettings.json index 8ea10a2..9c98fd8 100644 --- a/app/appsettings.json +++ b/app/appsettings.json @@ -1,13 +1,15 @@ { - "ConnectionStrings": { - "PostgreSql": "Host=database-dnit-eps-mds.coteugcvtnid.us-east-1.rds.amazonaws.com;Port=5432;Database=postgres;Username=epsmds;Password=epsmds2023" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "ConnectionStrings": { + "PostgreSql": "" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Senha": { + "RedefinirSenhaUrl": "http://dnit.eps-fga.live/redefinirSenha" } - }, - "AllowedHosts": "*", - "RedefinirSenhaUrl": "https://dnit.vercel.app/redefinirSenha" } diff --git a/auth/AuthConfig.cs b/auth/AuthConfig.cs new file mode 100644 index 0000000..1159376 --- /dev/null +++ b/auth/AuthConfig.cs @@ -0,0 +1,14 @@ +namespace auth +{ + public class AuthConfig + { + public bool Enabled { get; set; } = false; + public string Key { get; set; } = "chave secreta chave secreta chave secreta chave secreta chave secreta chave secreta"; + public string Issuer { get; set; } + public string Audience { get; set; } + public bool ValidateIssuer { get; set; } + public bool ValidateIssuerSigningKey { get; set; } + public int ExpireMinutes { get; set; } = 10; + public int RefreshTokenExpireMinutes { get; set; } = 120; + } +} diff --git a/auth/AuthExceptionHandler.cs b/auth/AuthExceptionHandler.cs new file mode 100644 index 0000000..d638e5f --- /dev/null +++ b/auth/AuthExceptionHandler.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace auth +{ + public class AuthExceptionHandler : IExceptionFilter + { + public void OnException(ExceptionContext context) + { + if (context.Exception is AuthException apiException) + { + context.Result = new JsonResult(new { Details = apiException.Message }) + { + StatusCode = (int)HttpStatusCode.Forbidden, + }; + context.ExceptionHandled = true; + } + } + } +} diff --git a/auth/AuthExceptions.cs b/auth/AuthExceptions.cs new file mode 100644 index 0000000..97f93b7 --- /dev/null +++ b/auth/AuthExceptions.cs @@ -0,0 +1,13 @@ +namespace auth { + public abstract class AuthException : Exception + { + protected AuthException(string message) : base(message) { } + protected AuthException() { } + } + + public class AuthForbiddenException : AuthException + { + public AuthForbiddenException() { } + public AuthForbiddenException(string message): base(message) { } + } +} diff --git a/auth/AuthService.cs b/auth/AuthService.cs new file mode 100644 index 0000000..f30b93f --- /dev/null +++ b/auth/AuthService.cs @@ -0,0 +1,122 @@ +using auth; +using EnumsNET; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; + +namespace app.Services +{ + public class AuthService + { + private readonly AuthConfig authConfig; + private const string CLAIM_PERMISSIONS = "permissions"; + private const string CLAIM_ID = "id"; + private const char PERMISSIONS_SEPARATOR = ','; + + public AuthService(IOptions authConfig) + { + this.authConfig = authConfig.Value; + } + + public void Require(ClaimsPrincipal user, TPermission permission) where TPermission : struct + { + if (!authConfig.Enabled) + return; + + if (!HasPermission(user, permission)) + throw new AuthForbiddenException($"O usuário não tem a permissão: {Enums.AsStringUnsafe(permission, EnumFormat.Description)} ({permission})"); + } + + public bool HasPermission(ClaimsPrincipal user, TPermission permission) where TPermission : struct + { + var permissionsText = user.Claims.FirstOrDefault(c => c.Type == CLAIM_PERMISSIONS)?.Value; + return DecodePermissions(permissionsText)?.Any(p => permission.Equals(p)) ?? false; + } + + public int GetUserId(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var id = tokenHandler.ReadJwtToken(token).Claims.FirstOrDefault(c => c.Type == CLAIM_ID); + if (id != null) + { + return int.Parse(id.Value); + } + throw new AuthForbiddenException("Token inválido"); + } + + public int GetUserId(ClaimsPrincipal user) + { + return int.Parse(user.Claims.First(c => c.Type == CLAIM_ID).Value); + } + + public (string Token, DateTime ExpiresAt) GenerateToken(AuthUserModel user) where TPermission : struct + { + var issuer = authConfig.Issuer; + var audience = authConfig.Audience; + var key = Encoding.ASCII.GetBytes(authConfig.Key); + var expiraEm = DateTime.UtcNow.AddMinutes(authConfig.ExpireMinutes); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim(CLAIM_ID, user.Id.ToString()), + new Claim(JwtRegisteredClaimNames.Sub, user.Name), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(CLAIM_PERMISSIONS, EncodePermissions(user.Permissions)) + }), + Expires = expiraEm, + Issuer = issuer, + Audience = audience, + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature), + }; + + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + tokenHandler.WriteToken(token); + var stringToken = tokenHandler.WriteToken(token); + return (stringToken, expiraEm); + } + + public (string RefreshToken, DateTime ExpiresAt) GenerateRefreshToken() + { + var randomNumber = new byte[64]; + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(randomNumber); + return (Convert.ToBase64String(randomNumber), DateTime.UtcNow.AddMinutes(authConfig.RefreshTokenExpireMinutes)); + } + + private string EncodePermissions(List? permissions) where TPermission : struct + { + var hasPermissions = permissions?.Any() ?? false; + return hasPermissions ? string.Join(PERMISSIONS_SEPARATOR, permissions!.Select(p => ToLong(p))) : ""; + } + + private IEnumerable? DecodePermissions(string? permissionsText) where TPermission : struct + { + var permissionValues = ((TPermission[])Enum.GetValues(typeof(TPermission))).ToDictionary(p => ToLong(p)); + + var permissions = permissionsText?.Split(PERMISSIONS_SEPARATOR); +#pragma warning disable S2589 // Boolean expressions should not be gratuitous + return permissions? + .Where(p => !string.IsNullOrWhiteSpace(p))? + .Select(long.Parse)? + .Where(p => p > 0)? + .Select(p => { + TPermission result; + return permissionValues.TryGetValue(p, out result) ? result : (TPermission?)null; + })? + .Where(p => p.HasValue)? + .Select(p => (TPermission?)Convert.ChangeType(p, typeof(TPermission)))? + .Where(p => p != null); +#pragma warning restore S2589 // Boolean expressions should not be gratuitous + } + + private long ToLong(TPermission p) where TPermission : struct { + return (long)Convert.ChangeType(p, typeof(long)); + } + } +} diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs new file mode 100644 index 0000000..b71ee15 --- /dev/null +++ b/auth/AuthStartup.cs @@ -0,0 +1,107 @@ +using app.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; + +namespace auth +{ + public static class AuthStartup + { + private static bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters @param) + { + if (expires != null) + { + return expires > DateTime.UtcNow; + } + return false; + } + + public static void AddAuth(this IServiceCollection services, IConfiguration configuration) + { + services.AddAuthSwagger(configuration); + + services.Configure(configuration.GetSection("Auth")); + + services.AddSingleton(); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(o => + { + var configuracaoAutenticaco = configuration.GetSection("Auth"); + o.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = configuracaoAutenticaco["Issuer"], + ValidAudience = configuracaoAutenticaco["Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuracaoAutenticaco["Key"]!)), + ValidateIssuer = bool.Parse(configuracaoAutenticaco["ValidateIssuer"] ?? "false"), + ValidateAudience = bool.Parse(configuracaoAutenticaco["ValidateAudience"] ?? "false"), + ValidateLifetime = true, + LifetimeValidator = CustomLifetimeValidator, + ValidateIssuerSigningKey = bool.Parse(configuracaoAutenticaco["ValidateIssuerSigningKey"] ?? "false") + }; + }); + + services.AddAuthorization(); + + services.AddControllers(o => o.Filters.Add(typeof(AuthExceptionHandler))); + + if (!bool.Parse(configuration.GetSection("Auth")["Enabled"] ?? bool.FalseString)) + { + services.AddSingleton(); + } + } + + public static void AddAuthSwagger(this IServiceCollection services, IConfiguration configuration) + { + services.AddSwaggerGen(c => + { + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "Please enter into field the word 'Bearer' following by space and JWT", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = JwtBearerDefaults.AuthenticationScheme, + Name = "Bearer", + In = ParameterLocation.Header, + + }, + new List() + } + }); + }); + } + } + + public class AllowAnonymous : IAuthorizationHandler + { + public Task HandleAsync(AuthorizationHandlerContext context) + { + foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList()) + context.Succeed(requirement); + + return Task.CompletedTask; + } + } +} diff --git a/auth/AuthUserModel.cs b/auth/AuthUserModel.cs new file mode 100644 index 0000000..e6ec840 --- /dev/null +++ b/auth/AuthUserModel.cs @@ -0,0 +1,9 @@ +namespace auth +{ + public class AuthUserModel where TPermission : struct + { + public int Id { get; set; } + public string Name { get; set; } + public List? Permissions { get; set; } + } +} diff --git a/auth/auth.csproj b/auth/auth.csproj new file mode 100644 index 0000000..08ffa50 --- /dev/null +++ b/auth/auth.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + Dnit Eps FGA authorization services + https://github.com/fga-eps-mds/2023.2-Dnit-UsuarioService + https://github.com/fga-eps-mds/2023.2-Dnit-UsuarioService + 1.0.7 + DnitEpsFga.auth + + + + + + + + + + + + + + AuthExceptionHandler.cs + + + + diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 0000000..a8e7ab1 --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,30 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build + +WORKDIR /app + +COPY UsuarioService.sln ./ +COPY app/app.csproj ./app/ +COPY dominio/dominio.csproj ./dominio/ +COPY repositorio/repositorio.csproj ./repositorio/ +COPY service/service.csproj ./service/ +COPY test/test.csproj ./test/ + +RUN dotnet restore + +COPY . ./ + +RUN dotnet build -c Release + +RUN dotnet publish app/app.csproj -c Release -o /app/out +RUN dotnet publish service/service.csproj -c Release -o /app/out +RUN dotnet publish repositorio/repositorio.csproj -c Release -o /app/out +RUN dotnet publish dominio/dominio.csproj -c Release -o /app/out +RUN dotnet publish test/test.csproj -c Release -o /app/out + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime + +WORKDIR /app + +COPY --from=build /app/out . + +ENTRYPOINT ["dotnet", "app.dll"] diff --git a/docker-compose.yml b/docker-compose.yml index 967e2fd..1713202 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,58 @@ version: "3.0" + services: usuario-service: + depends_on: + - dnit-usuario-db build: context: . ports: - "7083:7083" - container_name: usuarioService + container_name: usuario-service + volumes: + - ./api:/app/api + - ./app:/app/app + - ./test:/app/test + - ./auth:/app/auth + environment: + - MODE=container env_file: - .env + networks: + - dnit-network + + dnit-usuario-db: + container_name: dnit-usuario-db + image: postgres:15.4 + restart: always + environment: + POSTGRES_DB: usuarioservice + POSTGRES_PASSWORD: 1234 + ports: + - "5432:5432" + volumes: + - pg-data-volume:/var/lib/postgresql/data + networks: + - dnit-network + + pgadmin: + container_name: dnit-pg-admin + image: dpage/pgadmin4 + ports: + - "5555:80" + volumes: + - pg-admin-volume:/var/lib/pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: dnit@fga.com + PGADMIN_DEFAULT_PASSWORD: fga1234 + networks: + - dnit-network + +volumes: + pg-data-volume: + pg-admin-volume: + +networks: + dnit-network: + name: dnit-network + driver: bridge \ No newline at end of file diff --git a/dominio/Enums/ContextoBancoDeDados.cs b/dominio/Enums/ContextoBancoDeDados.cs deleted file mode 100644 index d997e8b..0000000 --- a/dominio/Enums/ContextoBancoDeDados.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace dominio.Enums -{ - public enum ContextoBancoDeDados - { - Postgresql - } -} diff --git a/dominio/RedefinicaoSenha.cs b/dominio/RedefinicaoSenha.cs deleted file mode 100644 index 1869c41..0000000 --- a/dominio/RedefinicaoSenha.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace dominio -{ - public class RedefinicaoSenha - { - public string Senha {get; set;} - public string UuidAutenticacao {get; set;} - } -} diff --git a/dominio/RedefinicaoSenhaDTO.cs b/dominio/RedefinicaoSenhaDTO.cs deleted file mode 100644 index 0f4eaf9..0000000 --- a/dominio/RedefinicaoSenhaDTO.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace dominio -{ - public class RedefinicaoSenhaDTO - { - public string Senha {get; set;} - public string UuidAutenticacao {get; set;} - } -} \ No newline at end of file diff --git a/dominio/UnidadeFederativa.cs b/dominio/UnidadeFederativa.cs deleted file mode 100644 index 2805d6e..0000000 --- a/dominio/UnidadeFederativa.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace dominio -{ - public class UnidadeFederativa - { - public int Id { get; set; } - public string Sigla { get; set; } - public string Descricao { get; set; } - } -} diff --git a/dominio/Usuario.cs b/dominio/Usuario.cs deleted file mode 100644 index 36b3432..0000000 --- a/dominio/Usuario.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace dominio -{ - public class Usuario - { - public int Id { get; set; } - public string Email { get; set; } - public string Senha { get; set; } - public string Nome { get; set; } - } -} diff --git a/dominio/UsuarioDTO.cs b/dominio/UsuarioDTO.cs deleted file mode 100644 index ffd3940..0000000 --- a/dominio/UsuarioDTO.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace dominio -{ - public class UsuarioDTO - { - public string Email { get; set; } - public string Senha { get; set; } - public string Nome { get; set; } - public int? UF { get; set; } - public string? CNPJ { get; set; } - } -} diff --git a/dominio/UsuarioDnit.cs b/dominio/UsuarioDnit.cs deleted file mode 100644 index 155d3a8..0000000 --- a/dominio/UsuarioDnit.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace dominio -{ - public class UsuarioDnit : Usuario - { - public int UF { get; set; } - } -} \ No newline at end of file diff --git a/dominio/UsuarioTerceiro.cs b/dominio/UsuarioTerceiro.cs deleted file mode 100644 index d045445..0000000 --- a/dominio/UsuarioTerceiro.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace dominio -{ - public class UsuarioTerceiro : Usuario - { - public string CNPJ { get; set; } - } -} diff --git a/repositorio/Contexto/ContextoPostgresql.cs b/repositorio/Contexto/ContextoPostgresql.cs deleted file mode 100644 index fd725ce..0000000 --- a/repositorio/Contexto/ContextoPostgresql.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Npgsql; -using System; -using System.Data; - -namespace repositorio.Contexto -{ - public class ContextoPostgresql : IContexto, IDisposable - { - public IDbConnection Conexao { get; } - public ContextoPostgresql(string connectionString) => Conexao = new NpgsqlConnection(connectionString); - public void Dispose() => Conexao.Dispose(); - } -} diff --git a/repositorio/Contexto/IContexto.cs b/repositorio/Contexto/IContexto.cs deleted file mode 100644 index 188dcf2..0000000 --- a/repositorio/Contexto/IContexto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Data; - -namespace repositorio.Contexto -{ - public interface IContexto - { - IDbConnection Conexao { get; } - } -} diff --git a/repositorio/Contexto/ResolverContexto.cs b/repositorio/Contexto/ResolverContexto.cs deleted file mode 100644 index 3c510d4..0000000 --- a/repositorio/Contexto/ResolverContexto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using dominio.Enums; - -namespace repositorio.Contexto -{ - public class ResolverContexto - { - public delegate IContexto? ResolverContextoDelegate(ContextoBancoDeDados contexto); - } -} diff --git a/repositorio/Interfaces/IUnidadeFederativaRepositorio.cs b/repositorio/Interfaces/IUnidadeFederativaRepositorio.cs deleted file mode 100644 index 6f60762..0000000 --- a/repositorio/Interfaces/IUnidadeFederativaRepositorio.cs +++ /dev/null @@ -1,10 +0,0 @@ -using dominio; -using System.Collections.Generic; - -namespace repositorio.Interfaces -{ - public interface IUnidadeFederativaRepositorio - { - IEnumerable ObterDominio(); - } -} diff --git a/repositorio/Interfaces/IUsuarioRepositorio.cs b/repositorio/Interfaces/IUsuarioRepositorio.cs deleted file mode 100644 index 297d1c2..0000000 --- a/repositorio/Interfaces/IUsuarioRepositorio.cs +++ /dev/null @@ -1,16 +0,0 @@ -using dominio; -using System.Collections.Generic; - -namespace repositorio.Interfaces -{ - public interface IUsuarioRepositorio - { - public Usuario? ObterUsuario(string email); - public UsuarioDnit TrocarSenha(string senha, string email); - public RedefinicaoSenha InserirDadosRecuperacao(string uuid, int idUsuario); - public string? ObterEmailRedefinicaoSenha(string uuid); - public void RemoverUuidRedefinicaoSenha(string uuid); - public void CadastrarUsuarioDnit(UsuarioDnit usuario); - public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); - } -} diff --git a/repositorio/UnidadeFederativaRepositorio.cs b/repositorio/UnidadeFederativaRepositorio.cs deleted file mode 100644 index cbeea61..0000000 --- a/repositorio/UnidadeFederativaRepositorio.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Dapper; -using dominio; -using dominio.Enums; -using repositorio.Contexto; -using repositorio.Interfaces; -using System.Collections.Generic; -using System.Linq; -using static repositorio.Contexto.ResolverContexto; - -namespace repositorio -{ - public class UnidadeFederativaRepositorio : IUnidadeFederativaRepositorio - { - private readonly IContexto contexto; - public UnidadeFederativaRepositorio(ResolverContextoDelegate resolverContexto) - { - contexto = resolverContexto(ContextoBancoDeDados.Postgresql); - } - public IEnumerable ObterDominio() - { - var sql = @"SELECT id, sigla, descricao FROM public.unidade_federativa"; - - var unidadesFederativas = contexto?.Conexao.Query(sql); - - return unidadesFederativas ?? Enumerable.Empty(); - } - } -} diff --git a/repositorio/UsuarioRepositorio.cs b/repositorio/UsuarioRepositorio.cs deleted file mode 100644 index 898fa63..0000000 --- a/repositorio/UsuarioRepositorio.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Dapper; -using dominio; -using dominio.Enums; -using repositorio.Contexto; -using repositorio.Interfaces; -using static repositorio.Contexto.ResolverContexto; - -namespace repositorio -{ - public class UsuarioRepositorio : IUsuarioRepositorio - { - private readonly IContexto contexto; - - public UsuarioRepositorio(ResolverContextoDelegate resolverContexto) - { - contexto = resolverContexto(ContextoBancoDeDados.Postgresql); - } - - - public Usuario? ObterUsuario(string email) - { - var sqlBuscarEmail = @"SELECT id, email, senha, nome FROM public.usuario WHERE email = @Email"; - - var parametro = new - { - Email = email - }; - - var usuario = contexto?.Conexao.QuerySingleOrDefault(sqlBuscarEmail, parametro); - - return usuario; - } - - public void CadastrarUsuarioDnit(UsuarioDnit usuario) - { - var sqlInserirUsuario = @"INSERT INTO public.usuario(nome, email, senha) VALUES(@Nome, @Email, @Senha) RETURNING id"; - - var parametrosUsuario = new - { - Nome = usuario.Nome, - Email = usuario.Email, - Senha = usuario.Senha, - }; - - int? usuarioId = contexto?.Conexao.ExecuteScalar(sqlInserirUsuario, parametrosUsuario); - - var sqlInserirUnidadeFederativaUsuario = @"INSERT INTO - public.usuario_unidade_federativa_lotacao(id_usuario, id_unidade_federativa) - VALUES (@IdUsuario, @IdUnidadeFederativa)"; - var parametrosUnidadeFederativaUsuario = new - { - IdUsuario = usuarioId, - IdUnidadeFederativa = usuario.UF - }; - - contexto?.Conexao.Execute(sqlInserirUnidadeFederativaUsuario, parametrosUnidadeFederativaUsuario); - } - - public UsuarioDnit TrocarSenha(string email, string senha) - { - var sqlTrocarSenha = @"UPDATE public.usuario SET senha = @Senha WHERE email = @Email"; - - var parametro = new - { - Email = email, - Senha = senha - }; - var usuarioDnit = contexto?.Conexao.QuerySingleOrDefault(sqlTrocarSenha, parametro); - - return usuarioDnit; - } - - public string? ObterEmailRedefinicaoSenha(string uuid) - { - var sqlBuscarDados = @"SELECT u.email FROM public.recuperacao_senha rs INNER JOIN public.usuario u ON rs.id_usuario = u.id WHERE uuid = @Uuid"; - - var parametro = new - { - Uuid = uuid, - }; - - string? email = contexto?.Conexao.QuerySingleOrDefault(sqlBuscarDados, parametro); - - return email; - } - - public void RemoverUuidRedefinicaoSenha(string uuid) - { - var sqlBuscarDados = @"DELETE FROM public.recuperacao_senha WHERE uuid = @Uuid"; - - var parametro = new - { - Uuid = uuid, - }; - - contexto?.Conexao.Execute(sqlBuscarDados, parametro); - } - - public RedefinicaoSenha InserirDadosRecuperacao(string uuid, int idUsuario) - { - var sqlInserirDadosRecuperacao = @"INSERT INTO public.recuperacao_senha(uuid, id_usuario) VALUES(@Uuid, @IdUsuario) RETURNING id"; - - var parametro = new - { - Uuid = uuid, - IdUsuario = idUsuario - }; - - var dadosRedefinicao = contexto?.Conexao.QuerySingleOrDefault(sqlInserirDadosRecuperacao, parametro); - - return dadosRedefinicao; - } - - public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) - { - var sqlInserirUsuarioTerceiro = @"INSERT INTO public.usuario(nome, email, senha) VALUES(@Nome, @Email, @Senha) RETURNING id"; - - var parametrosUsuarioTerceiro = new - { - Nome = usuarioTerceiro.Nome, - Email = usuarioTerceiro.Email, - Senha = usuarioTerceiro.Senha - }; - - int? usuarioTerceiroId = contexto?.Conexao.ExecuteScalar(sqlInserirUsuarioTerceiro, parametrosUsuarioTerceiro); - - var sqlInserirEmpresa = @"INSERT INTO public.usuario_empresa(id_usuario, cnpj_empresa) VALUES(@IdUsuario, @CnpjEmpresa)"; - - var parametrosEmpresa = new - { - IdUsuario = usuarioTerceiroId, - CnpjEmpresa = usuarioTerceiro.CNPJ - }; - - contexto?.Conexao.Execute(sqlInserirEmpresa, parametrosEmpresa); - } - } -} diff --git a/repositorio/repositorio.csproj b/repositorio/repositorio.csproj deleted file mode 100644 index c48ae57..0000000 --- a/repositorio/repositorio.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - enable - - - - - - - - - - - - diff --git a/service/Interfaces/IUsuarioService.cs b/service/Interfaces/IUsuarioService.cs deleted file mode 100644 index e587ff5..0000000 --- a/service/Interfaces/IUsuarioService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using dominio; -using System.Collections.Generic; - -namespace service.Interfaces -{ - public interface IUsuarioService - { - public bool ValidaLogin(UsuarioDTO usuarioDTO); - public void TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); - public void RecuperarSenha(UsuarioDTO usuarioDto); - public void CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); - public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); - } -} diff --git a/service/Mapper/UsuarioProfile.cs b/service/Mapper/UsuarioProfile.cs deleted file mode 100644 index 61bfe40..0000000 --- a/service/Mapper/UsuarioProfile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; - -namespace dominio.Mapper -{ - public class AutoMapperConfig : Profile - { - public AutoMapperConfig() - { - CreateMap() - .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); - - CreateMap(); - - CreateMap() - .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); - } - } -} diff --git a/service/UsuarioService.cs b/service/UsuarioService.cs deleted file mode 100644 index 73dfa44..0000000 --- a/service/UsuarioService.cs +++ /dev/null @@ -1,117 +0,0 @@ -using dominio; -using repositorio.Interfaces; -using service.Interfaces; -using AutoMapper; -using System.Collections.Generic; -using System; -using BCryptNet = BCrypt.Net.BCrypt; -using Microsoft.Extensions.Configuration; - -namespace service -{ - public class UsuarioService : IUsuarioService - { - - private readonly IUsuarioRepositorio usuarioRepositorio; - private readonly IMapper mapper; - private readonly IEmailService emailService; - private readonly IConfiguration configuration; - - public UsuarioService(IUsuarioRepositorio usuarioRepositorio, IMapper mapper, IEmailService emailService, IConfiguration configuration) - { - this.usuarioRepositorio = usuarioRepositorio; - this.mapper = mapper; - this.emailService = emailService; - this.configuration = configuration; - } - - public void CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) - { - var usuario = mapper.Map(usuarioDTO); - - usuario.Senha = EncriptarSenha(usuario.Senha); - - usuarioRepositorio.CadastrarUsuarioDnit(usuario); - } - - private string EncriptarSenha(string senha) - { - string salt = BCryptNet.GenerateSalt(); - - return BCryptNet.HashPassword(senha, salt); - } - - public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) - { - var usuario = mapper.Map(usuarioDTO); - - usuario.Senha = EncriptarSenha(usuario.Senha); - - usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); - } - - private Usuario? Obter(string email) - { - Usuario? usuario = usuarioRepositorio.ObterUsuario(email); - - if (usuario == null) - throw new KeyNotFoundException(); - - return usuario; - } - - public bool ValidaLogin(UsuarioDTO usuarioDTO) - { - Usuario? usuarioBanco = Obter(usuarioDTO.Email); - - return ValidaSenha(usuarioDTO.Senha, usuarioBanco.Senha); - } - - private bool ValidaSenha(string senhaUsuarioEntrada, string senhaUsuarioBanco) - { - if (BCryptNet.Verify(senhaUsuarioEntrada, senhaUsuarioBanco)) - return true; - - throw new UnauthorizedAccessException(); - } - - public void TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) - { - RedefinicaoSenha dadosRedefinicaoSenha = mapper.Map(redefinicaoSenhaDTO); - - string emailUsuario = usuarioRepositorio.ObterEmailRedefinicaoSenha(dadosRedefinicaoSenha.UuidAutenticacao) ?? throw new KeyNotFoundException(); - string senha = EncriptarSenha(dadosRedefinicaoSenha.Senha); - - usuarioRepositorio.TrocarSenha(emailUsuario, senha); - - emailService.EnviarEmail(emailUsuario, "Senha Atualizada", "A sua senha foi atualizada com sucesso."); - - usuarioRepositorio.RemoverUuidRedefinicaoSenha(dadosRedefinicaoSenha.UuidAutenticacao); - } - - public void RecuperarSenha(UsuarioDTO usuarioDTO) - { - var usuarioEntrada = mapper.Map(usuarioDTO); - Usuario usuarioBanco = Obter(usuarioEntrada.Email); - - string UuidAutenticacao = Guid.NewGuid().ToString(); - - usuarioRepositorio.InserirDadosRecuperacao(UuidAutenticacao, usuarioBanco.Id); - - string mensagem = "Olá!!\n\n" + - "Recebemos uma solicitação para redefinir a sua senha.\n\n" + - "Clique no link abaixo para ser direcionado a página de Redefinição de Senha.\n\n" + - $"{GerarLinkDeRecuperacao(UuidAutenticacao)}"; - - emailService.EnviarEmail(usuarioBanco.Email, "Link de Recuperação", mensagem); - } - private string GerarLinkDeRecuperacao(string UuidAutenticacao) - { - var baseUrl = configuration["RedefinirSenhaUrl"]; - - string link = $"{baseUrl}?token={UuidAutenticacao}"; - - return link; - } - } -} diff --git a/service/service.csproj b/service/service.csproj deleted file mode 100644 index 6e9b0a2..0000000 --- a/service/service.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net6.0 - enable - - - - - - - - - - - - - - - diff --git a/test/AuthTest.cs b/test/AuthTest.cs new file mode 100644 index 0000000..89cc3d0 --- /dev/null +++ b/test/AuthTest.cs @@ -0,0 +1,136 @@ +using api; +using app.Services; +using auth; +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace test +{ + public class AuthenticationTest + { + + AuthService authService; + AuthConfig authConfig; + + public AuthenticationTest() + { + authConfig = new AuthConfig() + { + Enabled = true, + Key = "teste secreta teste secreta teste secreta teste secreta teste secreta teste secreta", + ExpireMinutes = 5, + RefreshTokenExpireMinutes = 5, + }; + + authService = new AuthService(Options.Create(authConfig)); + } + + [Fact] + public void GenerateRefreshToken_QuandoForValido_DeveTokenRetornarComAExpiracao() + { + var (refreshToken, expiracao) = authService.GenerateRefreshToken(); + + Assert.NotEmpty(refreshToken); + Assert.True(DateTime.UtcNow < expiracao); + } + + [Fact] + public void GenerateToken_QuandoForValido_DeveRetornarTokenComAExpiracao() + { + var (token, expiracao) = ObterTokenValido(); + + Assert.NotEmpty(token); + Assert.True(DateTime.UtcNow < expiracao); + } + + [Fact] + public void GetUserId_QuandoForValido_DeveRetornarId() + { + var id = 1; + var (token, _) = ObterTokenValido(id); + + var usuarioId = authService.GetUserId(token); + + Assert.Equal(id, usuarioId); + } + + [Fact] + public void GetUserId_QuandoForClaim_DeveRetornarId() + { + var id = 1; + var (token, _) = ObterTokenValido(id); + var identity = ObterClaim(token); + + var usuarioId = authService.GetUserId(identity); + + Assert.Equal(id, usuarioId); + } + + [Fact] + public void GetUserId_QuandoForInvalido_DeveLancarExcecao() + { + var id = 1; + var (token, _) = ObterTokenValido(id); + token = Convert.ToBase64String(Encoding.ASCII.GetBytes('0' + token)); + Assert.Throws(() => authService.GetUserId(token)); + } + + [Fact] + public void HasPermission_QuandoTiverPermissao_DeveRetornarTrue() + { + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); + Assert.True(authService.HasPermission(ObterClaim(token), Permissao.RodoviaCadastrar)); + } + + [Fact] + public void HasPermission_QuandoNaoTiverPermissao_DeveRetornarFalse() + { + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); + Assert.False(authService.HasPermission(ObterClaim(token), Permissao.PerfilVisualizar)); + } + + [Fact] + public void Require_QuandoTiverPermissao_DevePassar() + { + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); + authService.Require(ObterClaim(token), Permissao.RodoviaCadastrar); + Assert.True(true); + } + + [Fact] + public void Require_QuandoTiverDesabilitado_DevePassar() + { + authConfig.Enabled = false; + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); + authService.Require(ObterClaim(token), Permissao.PerfilVisualizar); + Assert.True(true); + } + + [Fact] + public void Require_QuandoNaoTiverPermissao_DeveLancarExcecao() + { + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); + Assert.Throws(() => authService.Require(ObterClaim(token), Permissao.PerfilVisualizar)); + } + + private ClaimsPrincipal ObterClaim(string token) + { + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); + return new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); + } + + private (string, DateTime) ObterTokenValido(int id = 1, List? permissoes = null) + { + var usuario = new AuthUserModel() + { + Id = id, + Name = "Test", + Permissions = permissoes ?? new() { Permissao.EscolaCadastrar }, + }; + return authService.GenerateToken(usuario); + } + } +} diff --git a/test/AutoMapperConfigTest.cs b/test/AutoMapperConfigTest.cs index 116db0f..fc1b570 100644 --- a/test/AutoMapperConfigTest.cs +++ b/test/AutoMapperConfigTest.cs @@ -1,6 +1,5 @@ using AutoMapper; -using dominio.Mapper; -using Xunit; +using app.Services.Mapper; namespace test { diff --git a/test/Contexto.cs b/test/Contexto.cs deleted file mode 100644 index 587abba..0000000 --- a/test/Contexto.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Data; -using repositorio.Contexto; -using Dapper; - -namespace test -{ - public class Contexto : IContexto - { - public IDbConnection Conexao { get; } - public Contexto(IDbConnection conexao) - { - Conexao = conexao; - - string sql = @" - ATTACH DATABASE ':memory:' AS public; - "; - - Conexao.Execute(sql); - } - } -} diff --git a/test/ContextoConfigTest.cs b/test/ContextoConfigTest.cs deleted file mode 100644 index 8e95e9d..0000000 --- a/test/ContextoConfigTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -using app.DI; -using dominio.Enums; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using repositorio.Contexto; -using System.Threading.Tasks; -using Xunit; - -namespace test -{ - public class ContextoConfigTest - { - [Fact] - public void AddContexto_QuandoMetodoForChamado_DeveRegistrarServicoCorretamente() - { - var services = new ServiceCollection(); - var configurationBuilder = new ConfigurationBuilder().Build(); - - services.AddContexto(configurationBuilder); - - var contexto = services.BuildServiceProvider().GetService(); - - Assert.NotNull(contexto); - } - - [Fact] - public async Task ObterConnectionString_QuandoMetodoForChamado_DeveRetornarStringDeConexao() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - - var contexto = ContextoBancoDeDados.Postgresql; - - var connectionString = await ObterConnectionString(configuration, contexto); - - Assert.NotNull(connectionString); - } - - private async Task ObterConnectionString(IConfiguration configuration, ContextoBancoDeDados contexto) - { - var metodo = typeof(ContextoConfig).GetMethod("ObterConnectionString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); - return await (Task)metodo.Invoke(null, new object[] { configuration, contexto }); - } - } -} diff --git a/test/ContextoPostgresqlTest.cs b/test/ContextoPostgresqlTest.cs deleted file mode 100644 index 31c69f2..0000000 --- a/test/ContextoPostgresqlTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Npgsql; -using repositorio.Contexto; -using System.Data; -using Xunit; - -namespace test -{ - public class ContextoPostgresqlTest - { - [Fact] - public void ContextoPostgresql_QuandoForInstanciado_DeveSerConfiguradoCorretamente() - { - var connectionString = "Host=localhost;Port=1234;Database=postgres;Username=usuario;Password=teste"; - - using (var contexto = new ContextoPostgresql(connectionString)) - { - Assert.NotNull(contexto.Conexao); - Assert.IsType(contexto.Conexao); - Assert.Equal(ConnectionState.Closed, contexto.Conexao.State); - } - } - } -} diff --git a/test/DominioControllerTest.cs b/test/DominioControllerTest.cs index eb43c3d..c80bd4b 100644 --- a/test/DominioControllerTest.cs +++ b/test/DominioControllerTest.cs @@ -1,24 +1,47 @@ -using app.Controllers; +using test.Fixtures; +using app.Controllers; using Microsoft.AspNetCore.Mvc; -using Moq; -using repositorio.Interfaces; -using Xunit; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using auth; +using api; +using System.Collections.Generic; namespace test { - public class DominioControllerTest + public class DominioControllerTest : AuthTest { + DominioController dominioController; + + public DominioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dominioController = fixture.GetService(testOutputHelper)!; + + AutenticarUsuario(dominioController); + } + [Fact] public void ObterLista_QuandoMetodoForChamado_DeveRetornarListaDeUFs() { - Mock usuarioFederativaRepositorioMock = new(); + var resultado = dominioController.ObterLista(); + Assert.IsType(resultado); + } - var controller = new DominioController(usuarioFederativaRepositorioMock.Object); + [Fact] + public void ObterListaDePermissoes_QuandoNaoTemPermissao_DeveBloquear() + { + AutenticarUsuario(dominioController, permissoes: new()); + Assert.Throws(() => dominioController.ObterListaDePermissoes()); + } - var resultado = controller.ObterLista(); + [Fact] + public void ObterListaDePermissoes_QuandoNaoPermissao_DeveRetornarOk() + { + AutenticarUsuario(dominioController, permissoes: new(){Permissao.PerfilVisualizar}); + + var resposta = dominioController.ObterListaDePermissoes(); - usuarioFederativaRepositorioMock.Verify(repo => repo.ObterDominio(), Times.Once); - Assert.IsType(resultado); + Assert.NotEmpty(resposta); } } } diff --git a/test/EditarUsuarioPerfilTest.cs b/test/EditarUsuarioPerfilTest.cs new file mode 100644 index 0000000..cceda41 --- /dev/null +++ b/test/EditarUsuarioPerfilTest.cs @@ -0,0 +1,213 @@ +using Moq; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit.Abstractions; +using System.Linq; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +using test.Fixtures; +using test.Stub; +using auth; +using app.Services.Interfaces; +using app.Entidades; +using app.Controllers; +using api.Usuarios; +using api.Senhas; +using api; +using app.Services; + +namespace test +{ + public class EditarUsuarioPerfilTest : AuthTest, IDisposable + { + readonly UsuarioController controller; + readonly AppDbContext dbContext; + + public EditarUsuarioPerfilTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dbContext = fixture.GetService(testOutputHelper)!; + controller = fixture.GetService(testOutputHelper)!; + dbContext.PopulaUsuarios(5); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoNãoTemPermissaoVisualizar_RetornaErroDePermissao() + { + AutenticarUsuario(controller, permissoes: new()); + var ex = await Assert.ThrowsAsync(async () => + await controller.ListarAsync(new PesquisaUsuarioFiltro())); + + Assert.Contains("não tem a permissão: Visualizar Usuário", ex.Message); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); + var filtro = new PesquisaUsuarioFiltro + { + ItemsPorPagina = 10, + }; + var usuarios = await controller.ListarAsync(filtro); + Assert.Equal(5, usuarios.Total); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); + var filtro = new PesquisaUsuarioFiltro + { + UfLotacao = UF.DF, + }; + var u = dbContext.Usuario.ToList(); + u[0].UfLotacao = UF.DF; + u[1].UfLotacao = UF.DF; + u[2].UfLotacao = UF.DF; + u[3].UfLotacao = UF.AM; + u[4].UfLotacao = UF.AM; + dbContext.SaveChanges(); + + var lista = await controller.ListarAsync(filtro); + + Assert.Equal(UF.DF, lista.Items[0].UfLotacao); + Assert.Equal(UF.DF, lista.Items[1].UfLotacao); + Assert.Equal(UF.DF, lista.Items[2].UfLotacao); + Assert.Equal(3, lista.Items.Count); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorMunicipio_RetornaUsuariosDoMunicipioDado() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); + var m = new Municipio { Id = 1, Nome = "Municipio", Uf = UF.DF }; + dbContext.Municipio.Add(m); + var filtro = new PesquisaUsuarioFiltro + { + MunicipioId = m.Id, + }; + var u = dbContext.Usuario.ToList(); + u[0].MunicipioId = 1; + u[1].MunicipioId = 1; + u[2].MunicipioId = 2; + u[3].MunicipioId = 2; + u[4].MunicipioId = 2; + dbContext.SaveChanges(); + + var lista = await controller.ListarAsync(filtro); + + Assert.Equal(1, lista.Items[0].Municipio!.Id); + Assert.Equal(1, lista.Items[1].Municipio!.Id); + Assert.Equal(2, lista.Items.Count); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorNome_RetornaUsuariosComNomeDado() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); + var m = new Municipio { Id = 1, Nome = "Municipio", Uf = UF.DF }; + dbContext.Municipio.Add(m); + var filtro = new PesquisaUsuarioFiltro + { + Nome = "Silva" + }; + var u = dbContext.Usuario.ToList(); + u[0].Nome = "Anderson Silva"; + u[1].Nome = "Silvania Almeida"; + u[2].Nome = "Silvio Santos"; + u[3].Nome = "Marcos"; + u[4].Nome = "Bianca"; + dbContext.SaveChanges(); + + var lista = await controller.ListarAsync(filtro); + + Assert.Equal(2, lista.Items.Count); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComPerfilDado() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); + var filtro = new PesquisaUsuarioFiltro + { + PerfilId = Guid.NewGuid(), + }; + var u = dbContext.Usuario.ToList(); + u[0].PerfilId = filtro.PerfilId; + u[1].PerfilId = filtro.PerfilId; + u[2].PerfilId = filtro.PerfilId; + dbContext.SaveChanges(); + + var lista = await controller.ListarAsync(filtro); + + Assert.Equal(3, lista.Items.Count); + } + + [Fact] + public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() + { + AutenticarUsuario(controller, permissoes: new()); + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = "id" }; + var ex = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(1, dto)); + + Assert.Contains("não tem a permissão: Editar Perfil", ex.Message); + } + + [Fact] + public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrado() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioPerfilEditar }); + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = "id" }; + var ex = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(-1, dto)); + + Assert.Equal(ErrorCodes.UsuarioNaoEncontrado, ex.Error.Code); + } + + [Fact] + public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEncontrado() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioPerfilEditar }); + var usuarioId = dbContext.Usuario.First().Id; + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = Guid.NewGuid().ToString() }; + var excecao = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(usuarioId, dto)); + + Assert.Equal(ErrorCodes.PermissaoNaoEncontrada, excecao.Error.Code); + } + + [Fact] + public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarPerfilDoUsuario() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioPerfilEditar }); + var novoPerfilParaUsuario = new Perfil + { + Id = Guid.NewGuid(), + // Se remover esses parâmetros, os testes acusam que já foi inserido uma row duplicada + Nome = "Teste", + Tipo = TipoPerfil.Administrador + }; + dbContext.Perfis.Add(novoPerfilParaUsuario); + dbContext.SaveChanges(); + var usuario = dbContext.Usuario.First(); + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = novoPerfilParaUsuario.Id.ToString() }; + + await controller.EditarPerfilUsuario(usuario.Id, dto); + + var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; + + Assert.Equal(novoPerfilParaUsuario.Id, usuarioEditado.PerfilId); + } + + public new void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Municipio); + } + } +} diff --git a/test/Fixtures/AuthTest.cs b/test/Fixtures/AuthTest.cs new file mode 100644 index 0000000..f117090 --- /dev/null +++ b/test/Fixtures/AuthTest.cs @@ -0,0 +1,52 @@ +using api; +using api.Usuarios; +using app.Controllers; +using app.Services; +using auth; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace test.Fixtures +{ + public class AuthTest : TestBed + { + ClaimsPrincipal Usuario; + AuthService authService; + + public AuthTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + authService = fixture.GetService(testOutputHelper)!; + } + + public (string Token, ClaimsPrincipal Usuario) AutenticarUsuario(AppController controller, AuthUserModel? usuario = null, List? permissoes = null) + { + if (usuario == null) + { + usuario = new AuthUserModel + { + Id = 1, + Name = "test", + Permissions = Enum.GetValues().ToList(), + }; + } + if (permissoes != null) + { + usuario.Permissions = permissoes; + } + + var (token, _) = authService.GenerateToken(usuario); + + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); + + Usuario = new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); + controller.AppUsuario = Usuario; + return (token, Usuario); + } + } +} diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs new file mode 100644 index 0000000..3665217 --- /dev/null +++ b/test/Fixtures/Base.cs @@ -0,0 +1,54 @@ +using app.Controllers; +using app.Entidades; +using app.Repositorios; +using app.Repositorios.Interfaces; +using app.Services; +using app.Services.Interfaces; +using app.Services.Mapper; +using auth; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit.Microsoft.DependencyInjection; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace test.Fixtures +{ + public class Base : TestBedFixture + { + protected override void AddServices(IServiceCollection services, IConfiguration? configuration) + { + // Para evitar a colisão durante a testagem paralela, o nome deve ser diferente para cada classe de teste + var databaseName = "DbInMemory" + Random.Shared.Next().ToString(); + services.AddDbContext(o => o.UseInMemoryDatabase(databaseName)); + + // Repositorios + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Services + services.AddScoped(); + services.AddScoped(); + services.AddAutoMapper(typeof(AutoMapperConfig)); + services.AddScoped(); + services.AddScoped(); + + // Controllers + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddAuth(configuration); + } + + protected override ValueTask DisposeAsyncCore() => new(); + + protected override IEnumerable GetTestAppSettings() + { + yield return new() { Filename = "appsettings.Test.json", IsOptional = false }; + } + } +} \ No newline at end of file diff --git a/test/PerfilControllerTest.cs b/test/PerfilControllerTest.cs new file mode 100644 index 0000000..9c82e77 --- /dev/null +++ b/test/PerfilControllerTest.cs @@ -0,0 +1,241 @@ +using System.IO; +using app.Controllers; +using app.Entidades; +using auth; +using test.Stub; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using test.Fixtures; +using Xunit.Abstractions; +using api; +using api.Perfis; +using System.Threading.Tasks; +using System.Data.Common; +using System.Collections.Generic; + + +namespace test +{ + public class PerfilControllerTest : AuthTest, IDisposable + { + readonly AppDbContext dbContext; + readonly PerfilController perfilController; + + public PerfilControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dbContext = fixture.GetService(testOutputHelper)!; + perfilController = fixture.GetService(testOutputHelper)!; + + AutenticarUsuario(perfilController); + + } + + [Fact] + public void CriarPerfil_QuandoNaoTemPermissao_DeveBloquear() + { + var perfil = PerfilStub.RetornaPerfilDTO(); + + AutenticarUsuario(perfilController, permissoes: new()); + Assert.Throws(() => perfilController.CriarPerfil(perfil)); + } + + [Fact] + public void CriarPerfil_QuandoTemPermissao_DeveRetornarOk() + { + var perfil = PerfilStub.RetornaPerfilDTO(); + + var resposta = perfilController.CriarPerfil(perfil); + var retorno = (resposta as OkObjectResult)!.Value as PerfilModel; + + Assert.IsType(resposta); + Assert.NotNull(retorno); + Assert.Equal(perfil.Nome, retorno.Nome); + } + + [Fact] + public async Task EditarPerfil_QuandoNaoExiste_DeveRetornarNotFound() + { + var perfil = PerfilStub.RetornaPerfilDTO(); + var resposta = await perfilController.EditarPerfil(Guid.NewGuid(), perfil); + + Assert.IsType(resposta); + + var retorno = (resposta as NotFoundObjectResult)!.Value as string; + + Assert.Equal("Perfil não encontrado", retorno); + } + + [Fact] + public async Task EditarPerfil_QuandoNaoTemPermissao_DeveBloquear() + { + var perfil = PerfilStub.RetornaPerfilDTO(); + + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar}); + + var resposta = perfilController.CriarPerfil(perfil); + var retorno = (resposta as OkObjectResult)!.Value as PerfilModel; + + perfil.Nome = "Novo Nome"; + + AutenticarUsuario(perfilController, permissoes: new()); + await Assert.ThrowsAsync(async () => await perfilController.EditarPerfil(retorno.Id, perfil)); + } + + [Fact] + public async Task EditarPerfil_QuandoTemPermissao_DeveRetornarOk() + { + var perfil = PerfilStub.RetornaPerfilDTO(); + + var resposta = perfilController.CriarPerfil(perfil); + var retorno = (resposta as OkObjectResult)!.Value as PerfilModel; + + perfil.Nome = "Novo Nome"; + + var respostaEditar = await perfilController.EditarPerfil(retorno.Id, perfil); + var perfilEditado = (respostaEditar as OkObjectResult)!.Value as PerfilModel; + + Assert.IsType(resposta); + Assert.NotNull(perfilEditado); + Assert.NotEqual(retorno.Nome, perfilEditado.Nome); + } + + [Fact] + public async Task ExcluirPerfil_QuandoNaoTemPermissao_DeveBloquear() + { + AutenticarUsuario(perfilController, permissoes: new()); + await Assert.ThrowsAsync(async () => await perfilController.ExcluirPerfil(Guid.NewGuid())); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilNaoExiste_DeveLancarNotFound() + { + var resposta = await perfilController.ExcluirPerfil(Guid.NewGuid()); + + Assert.IsType(resposta); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilBasico_DeveRetornarStatusCode400() + { + var perfil = PerfilStub.RetornaPerfil(tipo: TipoPerfil.Basico); + perfil.Id = Guid.NewGuid(); + + dbContext.Perfis.Add(perfil); + dbContext.SaveChanges(); + + var resposta = await perfilController.ExcluirPerfil(perfil.Id); + var retorno = resposta as ObjectResult; + + Assert.IsType(resposta); + Assert.Equal(400, retorno.StatusCode); + Assert.Equal("Esse Perfil não pode ser excluído.", retorno.Value.ToString()); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilAdministrador_DeveRetornarStatusCode400() + { + var perfil = PerfilStub.RetornaPerfil(tipo: TipoPerfil.Administrador); + perfil.Id = Guid.NewGuid(); + + dbContext.Perfis.Add(perfil); + dbContext.SaveChanges(); + + var resposta = await perfilController.ExcluirPerfil(perfil.Id); + var retorno = resposta as ObjectResult; + + Assert.IsType(resposta); + Assert.Equal(400, retorno.StatusCode); + Assert.Equal("Esse Perfil não pode ser excluído.", retorno.Value.ToString()); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilCustomizavel_DeveRetornarOk() + { + var perfil = PerfilStub.RetornaPerfil(); + perfil.Id = Guid.NewGuid(); + + dbContext.Perfis.Add(perfil); + dbContext.SaveChanges(); + + var resposta = await perfilController.ExcluirPerfil(perfil.Id); + + Assert.IsType(resposta); + } + + [Fact] + public async Task ListarPerfis_QuandoNaoTemPermissao_DeveBloquear() + { + AutenticarUsuario(perfilController, permissoes: new()); + await Assert.ThrowsAsync(async () => await perfilController.ListarPerfis(1, 20)); + } + + [Fact] + public async Task ListarPerfis_QuandoTemPermissao_DeveRetornarOk() + { + var lista = PerfilStub.RetornaListaPerfilDTO(5); + var administrador = PerfilStub.RetornaPerfil(tipo: TipoPerfil.Administrador); + + dbContext.Perfis.Add(administrador); + dbContext.SaveChanges(); + + lista.ForEach(p => perfilController.CriarPerfil(p)); + + var resposta = await perfilController.ListarPerfis(1, 10); + + Assert.IsType(resposta); + + var listaRetorno = (resposta as OkObjectResult)!.Value as List; + + Assert.NotEmpty(listaRetorno); + Assert.Equal(6, listaRetorno.Count); + } + + [Fact] + public async Task ObterPorId_QuandoNaoTemPermissao_DeveRetornarBloquear() + { + AutenticarUsuario(perfilController, permissoes: new()); + await Assert.ThrowsAsync(async () => await perfilController.ObterPorId(Guid.NewGuid())); + } + + + [Fact] + public async Task ObterPorId_QuandoNaoExiste_DeveRetornarNotFound() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilVisualizar}); + + var resposta = await perfilController.ObterPorId(Guid.NewGuid()); + + Assert.IsType(resposta); + + var retorno = (resposta as NotFoundObjectResult)!.Value as string; + + Assert.Equal("Perfil não encontrado", retorno); + } + + [Fact] + public async Task ObterPorId_QuandoTemPermissao_DeveRetornarOk() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilVisualizar,Permissao.PerfilCadastrar}); + var perfil = PerfilStub.RetornaPerfilDTO(); + var resposta = perfilController.CriarPerfil(perfil); + var perfilCriado = (resposta as OkObjectResult)!.Value as PerfilModel; + + Assert.IsType(resposta); + + var respostaObter = await perfilController.ObterPorId(perfilCriado.Id); + + Assert.IsType(respostaObter); + + var retorno = (respostaObter as OkObjectResult)!.Value as PerfilModel; + + Assert.Equal(perfilCriado.Id, retorno.Id); + } + + public void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/test/PerfilRepositorioTest.cs b/test/PerfilRepositorioTest.cs new file mode 100644 index 0000000..c7f07cd --- /dev/null +++ b/test/PerfilRepositorioTest.cs @@ -0,0 +1,149 @@ +using Xunit.Microsoft.DependencyInjection.Abstracts; +using Xunit.Abstractions; +using test.Fixtures; +using app.Repositorios.Interfaces; +using app.Entidades; +using Xunit.Sdk; +using api.Perfis; +using System.Linq; +using System.Collections.Generic; + +namespace test +{ + public class PerfilRepositorioTest : TestBed, IDisposable + { + private readonly IPerfilRepositorio repositorio; + private readonly AppDbContext dbContext; + + public PerfilRepositorioTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + this.repositorio = fixture.GetService(testOutputHelper)!; + this.dbContext = fixture.GetService(testOutputHelper)!; + } + + [Fact] + public void RegistrarNovoPerfil_QuandoPerfilForPassado_DeveRetornarPerfilRegistrado() + { + Perfil perfil = Stub.PerfilStub.RetornaPerfil(); + + var perfilRetornado = repositorio.RegistraPerfil(perfil); + + dbContext.SaveChanges(); + + Assert.Equal(perfilRetornado.Nome, perfil.Nome); + + var perfilDb = dbContext.Perfis.Find(perfilRetornado.Id); + Assert.NotNull(perfilDb); + } + + [Fact] + public void AdicionaPermissaoAoPerfil_QuandoPermissõesPassadas_DeveRetornarRelacaoCriada() + { + Perfil perfil = Stub.PerfilStub.RetornaPerfil(); + + var perfilRetornado = repositorio.RegistraPerfil(perfil); + + PerfilPermissao perfilPermissao = repositorio.AdicionaPermissaoAoPerfil(perfilRetornado.Id, perfil.Permissoes.First()); + + dbContext.SaveChanges(); + + var perfilPermissaoDb = dbContext.PerfilPermissoes.Find(perfilPermissao.Id); + + var perfilatualizadoDb = dbContext.Perfis.Find(perfilRetornado.Id); + + Assert.Equal(perfilatualizadoDb.PerfilPermissoes[0].Permissao, perfilPermissaoDb.Permissao); + Assert.NotNull(perfilatualizadoDb.Permissoes); + Assert.NotNull(perfilPermissaoDb); + Assert.Equal(perfilPermissaoDb.PerfilId, perfilRetornado.Id); + Assert.Equal(perfilPermissaoDb.Permissao, perfil.Permissoes.First()); + } + + [Fact] + public void RemovePerfil_QuandoPerfilCadastrado_DeveRemoverPerfilDoBanco() + { + Perfil perfil = Stub.PerfilStub.RetornaPerfil(); + + var perfilRetornado = repositorio.RegistraPerfil(perfil); + + repositorio.RemovePerfil(perfilRetornado); + + dbContext.SaveChanges(); + + var perfilDb = dbContext.Perfis.Find(perfilRetornado.Id); + + Assert.Null(perfilDb); + } + + [Fact] + public void RemovePermissaoDoPerfil_QuandoPerfilTemUmaPermissao_DeveRemoverPermissao() + { + var perfil = Stub.PerfilStub.RetornaPerfil(); + + var perfilRetornado = repositorio.RegistraPerfil(perfil); + + var perfilPermissao = repositorio.AdicionaPermissaoAoPerfil(perfilRetornado.Id, perfil.Permissoes.First()); + + repositorio.RemovePermissaoDoPerfil(perfilPermissao); + + dbContext.SaveChanges(); + + var perfilPermissaoDb = dbContext.PerfilPermissoes.Find(perfilPermissao.Id); + + Assert.Null(perfilPermissaoDb); + } + + [Fact] + public async void ObterPerfilPorIdAsync_QuandoPerfilExiste_DeveRetornarPerfil() + { + var perfil = Stub.PerfilStub.RetornaPerfil(); + + var perfilRetornado = repositorio.RegistraPerfil(perfil); + + dbContext.SaveChanges(); + + var perfilRecuperado = await repositorio.ObterPerfilPorIdAsync(perfilRetornado.Id); + + Assert.NotNull(perfilRecuperado); + Assert.Equal(perfilRecuperado.Nome, perfil.Nome); + } + + [Fact] + public async void ObterPerfilPorIdAsync_QuandoPerfilNaoExiste_DeveRetornarNull() + { + var id = Guid.NewGuid(); + + var perfilRecuperado = await repositorio.ObterPerfilPorIdAsync(id); + + Assert.Null(perfilRecuperado); + } + + [Fact] + public async void ListarPerfis_QuandoMuitosPerfis_DeveRetornarUmaListaNaoVazia() + { + var lista = Stub.PerfilStub.RetornaListaDePerfis(); + List nomeLista = new(); + + lista.ForEach(p => nomeLista.Add(p.Nome)); + + lista.ForEach(p => repositorio.RegistraPerfil(p)); + + dbContext.SaveChanges(); + + var listaRetornada = await repositorio.ListarPerfisAsync(1, 3); + + Assert.NotNull(listaRetornada); + + foreach (var item in listaRetornada) + { + Assert.Contains(item.Nome, nomeLista); + } + } + + public void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/test/PerfilServiceTest.cs b/test/PerfilServiceTest.cs new file mode 100644 index 0000000..9894e99 --- /dev/null +++ b/test/PerfilServiceTest.cs @@ -0,0 +1,168 @@ +using Xunit.Microsoft.DependencyInjection.Abstracts; +using Xunit.Abstractions; +using test.Fixtures; +using test.Stub; +using app.Services.Interfaces; +using app.Entidades; +using AutoMapper; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using api; +using app.Repositorios.Interfaces; +using System.Collections.Generic; + +namespace test +{ + public class PerfilServiceTest : TestBed, IDisposable + { + private readonly IPerfilService perfilService; + private readonly IPerfilRepositorio perfilRepositorio; + private readonly IMapper mapper; + private readonly AppDbContext dbContext; + + public PerfilServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + perfilService = fixture.GetService(testOutputHelper)!; + perfilRepositorio = fixture.GetService(testOutputHelper)!; + mapper = fixture.GetService(testOutputHelper)!; + dbContext = fixture.GetService(testOutputHelper)!; + } + + [Fact] + public async Task CriarPerfil_QuandoPerfilPassado_DeveRetornarPerfilRegistrado() + { + var perfilDTO = PerfilStub.RetornaPerfilDTO(); + + var perfil = mapper.Map(perfilDTO); + + var perfilRetorno = perfilService.CriarPerfil(perfil, perfilDTO.Permissoes); + + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfilRetorno.Id); + + Assert.NotNull(perfilDb); + Assert.Equal(perfilDTO.Nome, perfilDb.Nome); + Assert.Equal(1, perfilDb.Permissoes.Count()); + } + + [Fact] + public async Task EditarPerfil_QuandoNomeJaCadastrado_DeveRetornarPerfilAtualizado() + { + var perfilDTO = PerfilStub.RetornaPerfilDTO(); + var perfilDTOEdicao = PerfilStub.RetornaPerfilDTO("Novo Nome"); + perfilDTOEdicao.Permissoes.Add(Permissao.RodoviaCadastrar); + + var perfil = mapper.Map(perfilDTO); + var perfilEdicao = mapper.Map(perfilDTOEdicao); + + var perfilRetorno = perfilService.CriarPerfil(perfil, perfilDTO.Permissoes); + + perfilEdicao.Id = perfilRetorno.Id; + var perfilRetornoEditado = await perfilService.EditarPerfil(perfilEdicao, perfilDTOEdicao.Permissoes); + + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfilRetorno.Id); + + Assert.NotNull(perfilRetorno); + Assert.NotNull(perfilRetornoEditado); + Assert.NotNull(perfilDb); + Assert.Equal(perfilRetorno.Id, perfilDb.Id); + Assert.Equal(perfilRetornoEditado, perfilDb); + Assert.NotEqual(perfilDb.Nome, perfilDTO.Nome); + Assert.NotEqual(perfilDb.Permissoes.Count(), perfilDTO.Permissoes.Count()); + } + + [Fact] + public async Task EditarPerfil_QuandoPerfilNaoExiste_DeveLancarKeyNotFoundException() + { + var perfilDTO = PerfilStub.RetornaPerfilDTO(); + var perfil = mapper.Map(perfilDTO); + + await Assert.ThrowsAsync(async () => await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes)); + } + + [Fact] + public async Task ExcluirPerfil_QuandoPerfilExiste_DeveExcluirPerfilDoDB() + { + var perfilDTO = PerfilStub.RetornaPerfilDTO(); + var perfil = mapper.Map(perfilDTO); + + var perfilRetorno = perfilService.CriarPerfil(perfil, perfilDTO.Permissoes); + + await perfilService.ExcluirPerfil(perfilRetorno.Id); + + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfilRetorno.Id); + + Assert.Null(perfilDb); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemUsuariosComEssePerfilExcluido_PerfisDosUsuariosSaoDefinidosComoBasico() + { + var perfilParaExcluir = new Perfil { Nome = "Será excluído", Tipo = TipoPerfil.Customizavel }; + var perfilParaManter = new Perfil { Nome = "Para manter", Tipo = TipoPerfil.Customizavel }; + var perfilBasico = new Perfil { Nome = "Básico", Tipo = TipoPerfil.Basico }; + dbContext.Perfis.Add(perfilParaExcluir); + dbContext.Perfis.Add(perfilParaManter); + dbContext.Perfis.Add(perfilBasico); + dbContext.SaveChanges(); + + dbContext.PopulaUsuarios(5); + var usuarios = dbContext.Usuario.ToList(); + usuarios[0].PerfilId = perfilParaExcluir.Id; + usuarios[1].PerfilId = perfilParaExcluir.Id; + usuarios[2].PerfilId = perfilParaExcluir.Id; + usuarios[3].PerfilId = perfilParaManter.Id; + usuarios[4].PerfilId = perfilParaManter.Id; + dbContext.SaveChanges(); + + await perfilService.ExcluirPerfil(perfilParaExcluir.Id); + + var usuariosComPerfilBasico = dbContext.Usuario + .Include(u => u.Perfil) + .Where(u => u.PerfilId == perfilBasico.Id) + .Count(); + Assert.Equal(3, usuariosComPerfilBasico); + Assert.Null(dbContext.Perfis.Find(perfilParaExcluir.Id)); + + var usuariosComPerfilManter = dbContext.Usuario + .Include(u => u.Perfil) + .Where(u => u.PerfilId == perfilParaManter.Id) + .Count(); + Assert.Equal(2, usuariosComPerfilManter); + } + + [Fact] + public async Task ExcluirPerfil_QuandoPerfilNaoExiste_DeveLancarKeyNotFoundException() + { + await Assert.ThrowsAsync(async () => await perfilService.ExcluirPerfil(Guid.NewGuid())); + } + + [Fact] + public async Task ListarPerfis_QuandoNaoExistir_DeveRetornarListaVazia() + { + var lista = await perfilService.ListarPerfisAsync(1, 20); + + Assert.Empty(lista); + } + + [Fact] + public async Task ListarPerfis_QuandoExistir_DeveRetornarListaDePerfis() + { + var lista = PerfilStub.RetornaListaDePerfis(5); + + lista.ForEach(p => perfilService.CriarPerfil(p, p.Permissoes.ToList())); + + var listaRetorno = await perfilService.ListarPerfisAsync(1, 5); + + Assert.Equal(5, listaRetorno.Count()); + } + + public void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/test/PermissaoServiceTest.cs b/test/PermissaoServiceTest.cs new file mode 100644 index 0000000..1c56757 --- /dev/null +++ b/test/PermissaoServiceTest.cs @@ -0,0 +1,29 @@ +using test.Fixtures; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using Xunit.Abstractions; +using app.Services.Interfaces; +using api; +using System.Linq; +using System.Collections.Generic; +using app.Services; + +namespace test +{ + public class PermissaoServiceTest : TestBed, IDisposable + { + private readonly IPermissaoService permissaoService; + + public PermissaoServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + permissaoService = fixture.GetService(testOutputHelper)!; + } + + [Fact] + public void ObterCategorias_DeveRetornarListaComCategoriasDePermissoes() + { + var categorias = permissaoService.ObterCategorias(); + Assert.NotEmpty(categorias); + } + } + +} \ No newline at end of file diff --git a/test/RepositorioConfigTest.cs b/test/RepositorioConfigTest.cs index b7c27ea..36aec05 100644 --- a/test/RepositorioConfigTest.cs +++ b/test/RepositorioConfigTest.cs @@ -1,7 +1,7 @@ using app.DI; using Microsoft.Extensions.DependencyInjection; -using repositorio.Interfaces; -using repositorio; +using app.Repositorios.Interfaces; +using app.Repositorios; using Xunit; namespace test diff --git a/test/ServicesConfigTest.cs b/test/ServicesConfigTest.cs index 4343a65..cbac51e 100644 --- a/test/ServicesConfigTest.cs +++ b/test/ServicesConfigTest.cs @@ -1,8 +1,8 @@ using app.DI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using service; -using service.Interfaces; +using app.Services; +using app.Services.Interfaces; using Xunit; namespace test diff --git a/test/Stub/AppDbContextExtensions.cs b/test/Stub/AppDbContextExtensions.cs new file mode 100644 index 0000000..58ac1dc --- /dev/null +++ b/test/Stub/AppDbContextExtensions.cs @@ -0,0 +1,55 @@ +using api; +using app.Entidades; +using System.Collections.Generic; +using System.Linq; + +namespace test.Stub +{ + public static class AppDbContextExtensions + { + public static List PopulaUsuarios(this AppDbContext context, int quantidade, bool includePerfil = false) + { + var usuariosTeste = UsuarioStub.Listar().Take(quantidade).ToList(); + foreach (var usuarioDto in usuariosTeste) + { + var salt = BCrypt.Net.BCrypt.GenerateSalt(); + + usuarioDto.SenhaHash = BCrypt.Net.BCrypt.HashPassword(usuarioDto.Senha, salt); + + var usuario = new Usuario() + { + Id = Random.Shared.Next(), + Email = usuarioDto.Email, + Nome = usuarioDto.Nome, + Senha = usuarioDto.SenhaHash, + UfLotacao = UF.DF, + }; + + usuarioDto.Id = usuario.Id; + + if (includePerfil) + { + var perfil = new Perfil + { + Id = Guid.NewGuid(), + Nome = "Teste", + PerfilPermissoes = new() + { + new PerfilPermissao + { + Id = Guid.NewGuid(), + Permissao = Permissao.EscolaCadastrar, + } + } + }; + usuario.Perfil = perfil; + context.Add(perfil); + } + + context.Add(usuario); + } + context.SaveChanges(); + return usuariosTeste; + } + } +} diff --git a/test/Stub/PerfilStub.cs b/test/Stub/PerfilStub.cs new file mode 100644 index 0000000..95dc599 --- /dev/null +++ b/test/Stub/PerfilStub.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.ComponentModel; +using api.Perfis; +using app.Entidades; +using api; + +namespace test.Stub +{ + public static class PerfilStub + { + + public static PerfilPermissao RetornaPerfilPermissao(Permissao permissao = Permissao.EscolaCadastrar) + { + return new PerfilPermissao + { + Id = Guid.NewGuid(), + PerfilId = Guid.NewGuid(), + Permissao = permissao + }; + } + + public static Perfil RetornaPerfil(string nome = "PerfilTeste", TipoPerfil tipo = TipoPerfil.Customizavel) + { + return new Perfil + { + Nome = nome, + PerfilPermissoes = new List + { + RetornaPerfilPermissao(), RetornaPerfilPermissao(Permissao.PerfilEditar) + }, + Tipo = tipo + }; + } + + public static List RetornaListaDePerfis(int n = 4) + { + var lista = new List(); + + for(int i = 0; i < n; i++) + { + lista.Add(RetornaPerfil("PerfilTeste_" + i.ToString())); + } + + return lista; + } + + public static PerfilDTO RetornaPerfilDTO(string nome = "Perfil Teste") + { + return new PerfilDTO + { + Nome = nome, + Permissoes = new List(){Permissao.PerfilCadastrar} + }; + } + + public static List RetornaListaPerfilDTO(int n = 4) + { + var lista = new List(); + + for (int i = 0; i < n; i++) + { + lista.Add(RetornaPerfilDTO("Perfil" + i.ToString())); + } + + return lista; + } + } +} \ No newline at end of file diff --git a/test/Stub/RedefinicaoSenhaStub.cs b/test/Stub/RedefinicaoSenhaStub.cs index 583f7aa..24805a6 100644 --- a/test/Stub/RedefinicaoSenhaStub.cs +++ b/test/Stub/RedefinicaoSenhaStub.cs @@ -1,4 +1,4 @@ -using dominio; +using api.Senhas; namespace test.Stub { @@ -13,9 +13,9 @@ public RedefinicaoSenhaDTO ObterRedefinicaoSenhaDTO() }; } - public RedefinicaoSenha ObterRedefinicaoSenha() + public RedefinicaoSenhaModel ObterRedefinicaoSenha() { - return new RedefinicaoSenha + return new RedefinicaoSenhaModel { Senha = "senha1234", UuidAutenticacao = "123e4567-e89b-12d3-a456-426655440000" diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 9732f29..fff76c2 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -1,9 +1,40 @@ -using dominio; +using api.Usuarios; +using app.Entidades; +using api; +using System.Collections.Generic; +using System.Linq; namespace test.Stub { + public class TesteUsuarioStub : UsuarioDTO + { + public int Id { get; set; } + public string SenhaHash { get; set; } + } + public class UsuarioStub { + static readonly UF[] ListaUfs = Enum.GetValues(); + + static private UF UfAleatoria() + { + return ListaUfs[Random.Shared.Next() % ListaUfs.Length]; + } + + public static IEnumerable Listar() + { + while (true) + { + yield return new TesteUsuarioStub() + { + Nome = "teste " + Random.Shared.Next().ToString(), + Email = $"teste{Random.Shared.Next()}@email.com", + Senha = $"teste_senha_{Random.Shared.Next()}", + UfLotacao = UfAleatoria(), + }; + } + } + public UsuarioDTO RetornarUsuarioDnitDTO() { return new UsuarioDTO @@ -11,7 +42,7 @@ public UsuarioDTO RetornarUsuarioDnitDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UfAleatoria(), }; } @@ -22,7 +53,7 @@ public UsuarioDTO RetornarUsuarioTerceiroDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - CNPJ = "12345678901234" + UfLotacao = UfAleatoria() }; } @@ -33,7 +64,18 @@ public UsuarioDnit RetornarUsuarioDnit() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UfAleatoria() + }; + } + + public Usuario RetornarUsuarioDnitBanco() + { + return new Usuario + { + Email = "usuarioteste@gmail.com", + Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", + Nome = "Usuario Dnit", + UfLotacao = UfAleatoria() }; } @@ -44,7 +86,8 @@ public UsuarioTerceiro RetornarUsuarioTerceiro() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - CNPJ = "12345678901234" + CNPJ = "12345678901234", + UfLotacao = UfAleatoria() }; } @@ -54,7 +97,8 @@ public Usuario RetornarUsuarioValidoLogin() { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", - Nome = "Usuario Dnit" + Nome = "Usuario Dnit", + UfLotacao = UfAleatoria() }; } @@ -64,7 +108,8 @@ public Usuario RetornarUsuarioInvalidoLogin() { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J68", - Nome = "Usuario Dnit" + Nome = "Usuario Dnit", + UfLotacao = UfAleatoria() }; } } diff --git a/test/UnidadeFederativaRepositorioTest.cs b/test/UnidadeFederativaRepositorioTest.cs index 8bfa234..a01d340 100644 --- a/test/UnidadeFederativaRepositorioTest.cs +++ b/test/UnidadeFederativaRepositorioTest.cs @@ -1,60 +1,49 @@ -using Microsoft.Data.Sqlite; -using repositorio; -using repositorio.Interfaces; -using Dapper; -using Xunit; +using app.Repositorios; +using app.Repositorios.Interfaces; using System.Linq; +using test.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using System.Collections.Generic; namespace test { - public class UnidadeFederativaRepositorioTest + public class UnidadeFederativaRepositorioTest : TestBed, IDisposable { IUnidadeFederativaRepositorio repositorio; - SqliteConnection connection; - public UnidadeFederativaRepositorioTest() - { - connection = new SqliteConnection("Data Source=:memory:"); - connection.Open(); - - repositorio = new UnidadeFederativaRepositorio(contexto => new Contexto(connection)); - - string sql = @" - CREATE TABLE public.unidade_federativa ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - sigla TEXT, - descricao TEXT - ); - - INSERT INTO public.unidade_federativa(sigla, descricao) - VALUES ('DF', 'Distrito Federal'), ('GO', 'Goiás'); - "; - - connection.Execute(sql); + public UnidadeFederativaRepositorioTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + repositorio = fixture.GetService(testOutputHelper)!; } [Fact] public void ObterDominio_QuandoHouverUFsCadastradas_DeveRetornarListaDeUFs() { - var dominios = repositorio.ObterDominio(); + var dominios = repositorio.ObterDominio().ToList(); - Assert.Equal("Distrito Federal", dominios.ElementAt(0).Descricao); - Assert.Equal("DF", dominios.ElementAt(0).Sigla); + Assert.Equal("Acre", dominios.ElementAt(0).Nome); + Assert.Equal("AC", dominios.ElementAt(0).Sigla); - Assert.Equal("Goiás", dominios.ElementAt(1).Descricao); - Assert.Equal("GO", dominios.ElementAt(1).Sigla); + Assert.Equal("Alagoas", dominios.ElementAt(1).Nome); + Assert.Equal("AL", dominios.ElementAt(1).Sigla); - Assert.Equal(2, dominios.Count()); - } - [Fact] - public void ObterUnidadeFederativa_QuandoNaoHouverUFsCadastradas_DeveRetornarListaVazia() - { - string sql = "DELETE FROM public.unidade_federativa"; - connection.Execute(sql); + Assert.Equal("Amazonas", dominios.ElementAt(2).Nome); + Assert.Equal("AM", dominios.ElementAt(2).Sigla); + + Assert.Equal("Amapá", dominios.ElementAt(3).Nome); + Assert.Equal("AP", dominios.ElementAt(3).Sigla); + + Assert.Equal("Bahia", dominios.ElementAt(4).Nome); + Assert.Equal("BA", dominios.ElementAt(4).Sigla); + + Assert.Equal("Ceará", dominios.ElementAt(5).Nome); + Assert.Equal("CE", dominios.ElementAt(5).Sigla); - var dominios = repositorio.ObterDominio(); + Assert.Equal("Distrito Federal", dominios.ElementAt(6).Nome); + Assert.Equal("DF", dominios.ElementAt(6).Sigla); - Assert.Empty(dominios); + Assert.Equal(27, dominios.Count()); } } } diff --git a/test/Usings.cs b/test/Usings.cs new file mode 100644 index 0000000..e6909ab --- /dev/null +++ b/test/Usings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using System; \ No newline at end of file diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 1ebed00..a63eac6 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -1,137 +1,191 @@ -using app.Controllers; -using dominio; +using Moq; using Microsoft.AspNetCore.Mvc; -using Moq; -using service.Interfaces; -using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit.Abstractions; +using System.Linq; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +using test.Fixtures; using test.Stub; -using Xunit; +using auth; +using app.Services.Interfaces; +using app.Entidades; +using app.Controllers; +using api.Usuarios; +using api.Senhas; +using Xunit.Microsoft.DependencyInjection.Abstracts; namespace test { - public class UsuarioControllerTest + public class UsuarioControllerTest : TestBed, IDisposable { const int CREATED = 201; + const int BAD_REQUEST = 400; const int INTERNAL_SERVER_ERROR = 500; + readonly UsuarioController controller; + readonly AppDbContext dbContext; - [Fact] - public void Logar_QuandoLoginForValidado_DeveRetornarOk() + public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { - UsuarioStub usuarioStub = new(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); + dbContext = fixture.GetService(testOutputHelper)!; + controller = fixture.GetService(testOutputHelper)!; + dbContext.PopulaUsuarios(5); + } - Mock usuarioServiceMock = new(); + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioDTO usuario) + { + var resultado = await controller.Logar(usuario); - var controller = new UsuarioController(usuarioServiceMock.Object); + Assert.IsType(resultado); - var resultado = controller.Logar(usuarioDTO); + var login = (resultado as OkObjectResult)!.Value as LoginModel; + var token = login.Token.Split(" ")[1]; - usuarioServiceMock.Verify(service => service.ValidaLogin(usuarioDTO), Times.Once); - Assert.IsType(resultado); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); + controller.AppUsuario = new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); + return (token, login.TokenAtualizacao); } [Fact] - public void Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() + public async Task Logar_QuandoLoginForValidado_DeveRetornarOk() { - UsuarioStub usuarioStub = new(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - - Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new UnauthorizedAccessException()); + var usuario = dbContext.PopulaUsuarios(1, true).First(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var resultado = await controller.Logar(usuario); - var resultado = controller.Logar(usuarioDTO); + Assert.IsType(resultado); - usuarioServiceMock.Verify(service => service.ValidaLogin(usuarioDTO), Times.Once); - Assert.IsType(resultado); + var login = (resultado as OkObjectResult)!.Value as LoginModel; + Assert.NotEmpty(login.Token); + var token = login.Token.Split(" "); + Assert.True(token[0] == "Bearer"); + Assert.NotEmpty(token[1]); + Assert.NotEmpty(login.TokenAtualizacao); } [Fact] - public void Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() + public async Task ListarPermissoes_QuandoTiverLogado_DeveRetornarPermissoes() { - UsuarioStub usuarioStub = new(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new KeyNotFoundException()); + await AutenticarUsuario(usuario); + var permissoes = await controller.ListarPermissoes(); + Assert.NotEmpty(permissoes); + } + + [Fact] + public async Task AtualizarToken_QuandoTiverValido_DeveRetornarNovoToken() + { + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); + + var login = await AutenticarUsuario(usuario); + var novoLogin = await controller.AtualizarToken(new AtualizarTokenDto + { + Token = login.Token, + TokenAtualizacao = login.TokenAtualizacao, + }); + Assert.NotEmpty(novoLogin.Token); + Assert.NotEmpty(novoLogin.TokenAtualizacao); + Assert.NotEqual(novoLogin.TokenAtualizacao, login.TokenAtualizacao); + Assert.NotEqual(novoLogin.Token, login.Token); + } - var controller = new UsuarioController(usuarioServiceMock.Object); + [Fact] + public async Task AtualizarToken_QuandoTiverInvalido_DeveRetornarNovoToken() + { + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var resultado = controller.Logar(usuarioDTO); + var login = await AutenticarUsuario(usuario); + var atualizarTokenDto = new AtualizarTokenDto + { + Token = login.Token, + TokenAtualizacao = login.TokenAtualizacao + "aaaa", + }; - usuarioServiceMock.Verify(service => service.ValidaLogin(usuarioDTO), Times.Once); - Assert.IsType(resultado); + await Assert.ThrowsAsync(async () => await controller.AtualizarToken(atualizarTokenDto)); } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + public async Task Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() { - UsuarioStub usuarioStub = new(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuario = dbContext.PopulaUsuarios(1).First(); + usuario.Senha = "teste"; - Mock usuarioServiceMock = new(); + var resultado = await controller.Logar(usuario); - var controller = new UsuarioController(usuarioServiceMock.Object); + Assert.IsType(resultado); + } - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + [Fact] + public async Task Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() + { + var usuarioStub = new UsuarioStub(); + var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); - var objeto = Assert.IsType(resultado); + var resultado = await controller.Logar(usuarioDTO); - Assert.Equal(CREATED, objeto.StatusCode); + Assert.IsType(resultado); } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() + public async Task CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Mock usuarioServiceMock = new(); - var excecao = new Npgsql.PostgresException("", "", "", "23505"); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); + var objeto = Assert.IsType(resultado); + Assert.Equal(CREATED, objeto.StatusCode); - var controller = new UsuarioController(usuarioServiceMock.Object); + var usuarioBanco = dbContext.Usuario.Single(u => u.Email == usuarioDTO.Email); + Assert.True(usuarioDTO.UfLotacao != 0); + Assert.Equal(usuarioDTO.UfLotacao, usuarioDTO.UfLotacao); + } + + [Fact] + public async Task CadastrarUsuarioDnit_QuandoUsuarioTemUfInvalido_RetornaBadRequest() + { + var usuarioDTO = new UsuarioStub().RetornarUsuarioDnitDTO(); + usuarioDTO.UfLotacao = 0; - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); - usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); - var objeto = Assert.IsType(resultado); + var objeto = Assert.IsType(resultado); + Assert.Equal(BAD_REQUEST, objeto.StatusCode); + Assert.Equal("Código UF inválido", objeto!.Value); } [Fact] - public void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() + public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - var excecao = new Npgsql.PostgresException("", "", "", ""); + var excecao = new Npgsql.PostgresException("", "", "", "23505"); usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); - var objeto = Assert.IsType(resultado); - - Assert.Equal(INTERNAL_SERVER_ERROR, objeto.StatusCode); + var objeto = Assert.IsType(resultado); } [Fact] public void CadastrarUsuarioTerceiro_QuandoUsuarioForCadastrado_DeveRetornarCreated() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.CadastrarUsuarioTerceiro(usuarioDTO); @@ -144,7 +198,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioForCadastrado_DeveRetornarCrea [Fact] public void CadastrarUsuarioTerceiro_QuandoUsuarioJaExistir_DeveRetornarConflict() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -152,7 +206,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioJaExistir_DeveRetornarConflict usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.CadastrarUsuarioTerceiro(usuarioDTO); @@ -163,15 +217,15 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioJaExistir_DeveRetornarConflict [Fact] public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroInterno() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - var excecao = new Npgsql.PostgresException("", "", "", ""); + var excecao = new Exception(""); usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.CadastrarUsuarioTerceiro(usuarioDTO); @@ -182,56 +236,56 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern } [Fact] - public void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() + public async void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); - var resultado = controller.RecuperarSenha(usuarioDTO); + var resultado = await controller.RecuperarSenhaAsync(usuarioDTO); usuarioServiceMock.Verify(service => service.RecuperarSenha(usuarioDTO), Times.Once); Assert.IsType(resultado); } [Fact] - public void RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() + public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsync() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); usuarioServiceMock.Setup(service => service.RecuperarSenha(It.IsAny())).Throws(new KeyNotFoundException()); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); - var resultado = controller.RecuperarSenha(usuarioDTO); + var resultado = await controller.RecuperarSenhaAsync(usuarioDTO); usuarioServiceMock.Verify(service => service.RecuperarSenha(usuarioDTO), Times.Once); Assert.IsType(resultado); } [Fact] - public void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() + public async void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() { RedefinicaoSenhaStub redefinicaoSenhaStub = new(); var redefinicaoSenhaDTO = redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO(); Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); - var resultado = controller.RedefinirSenha(redefinicaoSenhaDTO); + var resultado = await controller.RedefinirSenhaAsync(redefinicaoSenhaDTO); usuarioServiceMock.Verify(service => service.TrocaSenha(redefinicaoSenhaDTO), Times.Once); Assert.IsType(resultado); } [Fact] - public void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() + public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() { RedefinicaoSenhaStub redefinicaoSenhaStub = new(); var redefinicaoSenhaDTO = redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO(); @@ -239,9 +293,9 @@ public void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() Mock usuarioServiceMock = new(); usuarioServiceMock.Setup(service => service.TrocaSenha(It.IsAny())).Throws(new KeyNotFoundException()); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); - var resultado = controller.RedefinirSenha(redefinicaoSenhaDTO); + var resultado = await controller.RedefinirSenhaAsync(redefinicaoSenhaDTO); usuarioServiceMock.Verify(service => service.TrocaSenha(redefinicaoSenhaDTO), Times.Once); Assert.IsType(resultado); diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 3dabadc..a9c6c5b 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -1,58 +1,33 @@ -using Xunit; -using repositorio; -using repositorio.Interfaces; -using dominio; -using Microsoft.Data.Sqlite; -using Dapper; +using app.Repositorios.Interfaces; using test.Stub; -using System; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using System.Linq; +using System.Threading.Tasks; namespace test { - public class UsuarioRepositorioTest : IDisposable + public class UsuarioRepositorioTest : TestBed, IDisposable { - IUsuarioRepositorio repositorio; - SqliteConnection conexao; + readonly IUsuarioRepositorio repositorio; + readonly AppDbContext dbContext; - public UsuarioRepositorioTest() + public UsuarioRepositorioTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { - conexao = new SqliteConnection("Data Source=:memory:"); - conexao.Open(); - - repositorio = new UsuarioRepositorio(contexto => new Contexto(conexao)); - - string sql = @" - CREATE TABLE public.usuario ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - email TEXT UNIQUE, - senha TEXT, - nome TEXT - ); - - CREATE TABLE public.usuario_unidade_federativa_lotacao ( - id_usuario INTEGER REFERENCES usuario (id), - id_unidade_federativa INTEGER); - - CREATE TABLE public.usuario_empresa ( - id_usuario INTEGER REFERENCES usuario (id), - cnpj_empresa INTEGER); - - CREATE TABLE public.recuperacao_senha ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - uuid TEXT, - id_usuario INTEGER REFERENCES usuario (id)); - "; - - conexao.Execute(sql); + dbContext = fixture.GetService(testOutputHelper)!; + repositorio = fixture.GetService(testOutputHelper)!; } [Fact] - public void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() + public async Task ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario("usuarioteste@gmail.com"); @@ -62,38 +37,35 @@ public void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() + public async Task CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); - var sql = $@"SELECT u.id, u.email, u.senha, u.nome, uufl.id_unidade_federativa uf - FROM public.usuario u - JOIN public.usuario_unidade_federativa_lotacao uufl - ON u.id = uufl.id_usuario - WHERE email = '{usuarioDNIT.Email}';"; - - UsuarioDnit? usuarioObtido = conexao.QueryFirst(sql); + var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault()!; Assert.Equal(usuarioDNIT.Email, usuarioObtido.Email); Assert.Equal(usuarioDNIT.Senha, usuarioObtido.Senha); Assert.Equal(usuarioDNIT.Nome, usuarioObtido.Nome); - Assert.Equal(usuarioDNIT.UF, usuarioObtido.UF); + Assert.Equal(usuarioDNIT.UfLotacao, usuarioObtido.UfLotacao); } [Fact] - public void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() + public async Task TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); string novaSenha = "NovaSenha"; repositorio.TrocarSenha(usuarioDNIT.Email, novaSenha); + await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); @@ -101,17 +73,20 @@ public void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() } [Fact] - public void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCorrespondente() + public async Task ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCorrespondente() { - UsuarioStub usuarioStub = new(); - RedefinicaoSenhaStub redefinicaoSenhaStub = new(); + var usuarioStub = new UsuarioStub(); + var redefinicaoSenhaStub = new RedefinicaoSenhaStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); + var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); repositorio.InserirDadosRecuperacao(redefinicaoSenha.UuidAutenticacao, usuarioObtido!.Id); + await dbContext.SaveChangesAsync(); var email = repositorio.ObterEmailRedefinicaoSenha(redefinicaoSenha.UuidAutenticacao); @@ -119,18 +94,23 @@ public void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCor } [Fact] - public void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBanco() + public async Task RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBanco() { - UsuarioStub usuarioStub = new(); - RedefinicaoSenhaStub redefinicaoSenhaStub = new(); + var usuarioStub = new UsuarioStub(); + var redefinicaoSenhaStub = new RedefinicaoSenhaStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); + var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); repositorio.InserirDadosRecuperacao(redefinicaoSenha.UuidAutenticacao, usuarioObtido!.Id); + await dbContext.SaveChangesAsync(); + repositorio.RemoverUuidRedefinicaoSenha(redefinicaoSenha.UuidAutenticacao); + await dbContext.SaveChangesAsync(); var email = repositorio.ObterEmailRedefinicaoSenha(redefinicaoSenha.UuidAutenticacao); @@ -139,30 +119,36 @@ public void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBa [Fact] - public void CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() + public async Task CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); - repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); + var empresa = new Empresa + { + Cnpj = usuarioTerceiro.CNPJ, + RazaoSocial = "Empresa1" + }; + + dbContext.Empresa.Add(empresa); + await dbContext.SaveChangesAsync(); - var sql = $@"SELECT u.id, u.email, u.senha, u.nome, ue.cnpj_empresa cnpj - FROM public.usuario u - JOIN public.usuario_empresa ue - ON u.id = ue.id_usuario - WHERE email = '{usuarioTerceiro.Email}';"; + await repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); + await dbContext.SaveChangesAsync(); - UsuarioTerceiro? usuarioObtido = conexao.QueryFirst(sql); + var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email)!; Assert.Equal(usuarioTerceiro.Email, usuarioObtido.Email); Assert.Equal(usuarioTerceiro.Senha, usuarioObtido.Senha); Assert.Equal(usuarioTerceiro.Nome, usuarioObtido.Nome); - Assert.Equal(usuarioTerceiro.CNPJ, usuarioObtido.CNPJ); + Assert.Equal(usuarioTerceiro.CNPJ, usuarioObtido?.Empresas?.First()?.Cnpj); } - public void Dispose() + + public new void Dispose() { - conexao.Close(); - conexao.Dispose(); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Empresa); + dbContext.SaveChanges(); } } } diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 3860d23..1eb5099 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -1,37 +1,63 @@ using AutoMapper; -using dominio; +using api.Senhas; +using api.Usuarios; using Microsoft.Extensions.Configuration; using Moq; -using repositorio.Interfaces; -using service; -using service.Interfaces; -using System; +using app.Repositorios.Interfaces; +using app.Services; +using app.Services.Interfaces; using System.Collections.Generic; using test.Stub; -using Xunit; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using Microsoft.Extensions.Options; +using auth; +using System.Threading.Tasks; +using app.Configuracoes; namespace test { - public class UsuarioServiceTest + public class UsuarioServiceTest : TestBed, IDisposable { + readonly AppDbContext dbContext; + readonly Mock mapper; + readonly Mock usuarioRepositorio; + readonly Mock perfilRepositorio; + readonly Mock emailService; + readonly AuthService authService; + readonly Mock> authConfig; + readonly IUsuarioService usuarioServiceMock; + + public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dbContext = fixture.GetService(testOutputHelper)!; + + mapper = new Mock(); + usuarioRepositorio = new Mock(); + perfilRepositorio = new Mock(); + emailService = new Mock(); + var senhaConfig = new SenhaConfig(); + authConfig = new Mock>(); + authService = new AuthService(authConfig.Object); + + usuarioServiceMock = new UsuarioService( + usuarioRepositorio.Object, perfilRepositorio.Object, + mapper.Object, emailService.Object, + Options.Create(senhaConfig), dbContext, authService, authConfig.Object); + } + [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrarUsuarioDnitComSenhaEncriptografada() + public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrarUsuarioDnitComSenhaEncriptografada() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); string senhaAntesDaEncriptografia = usuarioDNIT.Senha; - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); + await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); @@ -44,18 +70,11 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr UsuarioStub usuarioStub = new(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); - string senhaAntesDaEncriptografia = usuarioTerceiro.Senha; - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var senhaAntesDaEncriptografia = usuarioTerceiro.Senha; mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); + usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); @@ -63,91 +82,57 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_DeveLancarExececaoFalandoQueEmailJaExiste() + public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_DeveLancarExececaoFalandoQueEmailJaExiste() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - string senhaAntesDaEncriptografia = usuarioDNIT.Senha; - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email j cadastrado.")); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + var cadastrarUsuario = async () => await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - Action cadastrarUsuario = () => usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - - Exception exception = Assert.Throws(cadastrarUsuario); - - Assert.Equal("Email j cadastrado.", exception.Message); + var exception = await Assert.ThrowsAsync(cadastrarUsuario); } [Fact] public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroJaExistenteForPassado_DeveLancarExececaoFalandoQueEmalJaExiste() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); - string senhaAntesDaEncriptografia = usuarioTerceiro.Senha; - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email j cadastrado.")); - - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); - Action cadastrarUsuario = () => usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); + var cadastrarUsuario = () => usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); - Exception exception = Assert.Throws(cadastrarUsuario); + var exception = Assert.Throws(cadastrarUsuario); - Assert.Equal("Email j cadastrado.", exception.Message); + Assert.Equal("Email já cadastrado.", exception.Message); } [Fact] public void ValidaLogin_QuandoUsuarioCorretoForPassado_DeveRealizarLogin() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioStub = new UsuarioStub(); + var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioValidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - Assert.True(usuarioService.ValidaLogin(usuarioDnitDTO)); + Assert.True(usuarioServiceMock.ValidaLogin(usuarioDnitDTO)); } [Fact] public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioStub = new UsuarioStub(); + var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioInvalidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); + Action validarLogin = () => usuarioServiceMock.ValidaLogin(usuarioDnitDTO); Assert.Throws(validarLogin); } @@ -155,132 +140,92 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() [Fact] public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioStub = new UsuarioStub(); + var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); + Action validarLogin = () => usuarioServiceMock.ValidaLogin(usuarioDnitDTO); Assert.Throws(validarLogin); } [Fact] - public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSenha() + public async void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSenha() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); + var usuarioStub = new UsuarioStub(); + var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioRetorno = usuarioStub.RetornarUsuarioDnitBanco(); mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); - usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioDNIT); + usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioRetorno); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - usuarioService.RecuperarSenha(usuarioDnitDTO); + await usuarioServiceMock.RecuperarSenha(usuarioDnitDTO); emailService.Verify(x => x.EnviarEmail(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() + public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioStub = new UsuarioStub(); + var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - Action validarLogin = () => usuarioService.RecuperarSenha(usuarioDnitDTO); - - Assert.Throws(validarLogin); + await Assert.ThrowsAsync(async () => await usuarioServiceMock.RecuperarSenha(usuarioDnitDTO)); } [Fact] public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() { - UsuarioStub usuarioStub = new(); - RedefinicaoSenhaStub redefinicaoSenhaStub = new(); - string emailRedefinicaoSenha = "usuarioTeste@gmail.com"; + var usuarioStub = new UsuarioStub(); + var redefinicaoSenhaStub = new RedefinicaoSenhaStub(); + var emailRedefinicaoSenha = "usuarioTeste@gmail.com"; - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - RedefinicaoSenha redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); - - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); + var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); + mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(emailRedefinicaoSenha); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); + usuarioServiceMock.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); emailService.Verify(x => x.EnviarEmail(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); usuarioRepositorio.Verify(x => x.RemoverUuidRedefinicaoSenha(It.IsAny()), Times.Once); } [Fact] - public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() + public async Task TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() { - UsuarioStub usuarioStub = new(); - RedefinicaoSenhaStub redefinicaoSenhaStub = new(); - - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - RedefinicaoSenha redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); + var usuarioStub = new UsuarioStub(); + var redefinicaoSenhaStub = new RedefinicaoSenhaStub(); - Mock mapper = new(); - Mock usuarioRepositorio = new(); - Mock emailService = new(); - Mock configuration = new(); + var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); + var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); + mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); - - Action trocarSenha = () => usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); - - Assert.Throws(trocarSenha); + await Assert.ThrowsAsync(async () => await usuarioServiceMock.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO())); emailService.Verify(x => x.EnviarEmail(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); usuarioRepositorio.Verify(x => x.RemoverUuidRedefinicaoSenha(It.IsAny()), Times.Never); diff --git a/test/test.csproj b/test/test.csproj index 6379dff..dbbcc13 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -1,32 +1,46 @@  - - net6.0 - enable + + net6.0 + enable - false - + false + - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + - - - + + **/** + ** + - +