From ccd3a39f12c6ffc02dca8517d6f9ad60d61044bf Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Thu, 21 Sep 2023 17:48:48 -0300 Subject: [PATCH 001/137] Atualizando docker-compose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - retira temporariamente o build do app - adiciona as configurações do compose para rodar a base de dados postgres e a ferramenta pgadmin4 --- docker-compose.yml | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 967e2fd..e883ca3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,36 @@ version: "3.0" services: - usuario-service: - build: - context: . +# usuario-service: +# build: +# context: . +# ports: +# - "7083:7083" +# container_name: usuarioService +# env_file: +# - .env + + postgres: + container_name: dnit-pg-database + image: postgres:15 + restart: always + environment: + POSTGRES_PASSWORD: fga1234 ports: - - "7083:7083" - container_name: usuarioService - env_file: - - .env + - "5432:5432" + volumes: + - pg-data-volume:/var/lib/postgresql/data + + 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 + +volumes: + pg-data-volume: + pg-admin-volume: \ No newline at end of file From d9e3e1a51dc40bcda61616f296ae3c6ba7000467 Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Wed, 27 Sep 2023 16:31:22 -0300 Subject: [PATCH 002/137] Dockerfile modificado para ambiente de dev - Dockerfile para deploy movido para a pasta ci - Dockerfile de dev configurado para funcionar com hot reload - Docker-compose configurado para o ambiente de dev --- .dockerignore | 3 +++ .gitignore | 2 ++ Dockerfile | 27 ++------------------------- ci/Dockerfile | 30 ++++++++++++++++++++++++++++++ docker-compose.yml | 22 ++++++++++++++-------- 5 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 .dockerignore create mode 100644 ci/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cef95d4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.idea/ +.git/ +.github/ diff --git a/.gitignore b/.gitignore index c80e612..194a7be 100644 --- a/.gitignore +++ b/.gitignore @@ -362,3 +362,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd /dominio/UsuarioDNIT.cs + +.idea/ \ 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/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 e883ca3..d20a255 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,19 @@ version: "3.0" services: -# usuario-service: -# build: -# context: . -# ports: -# - "7083:7083" -# container_name: usuarioService -# env_file: -# - .env + usuario-service: + build: + context: . + ports: + - "7083:7083" + container_name: usuarioService + volumes: + - ./app:/app/app + - ./dominio:/app/dominio + - ./repositorio:/app/repositorio + - ./service:/app/service + - ./test:/app/test + env_file: + - .env postgres: container_name: dnit-pg-database From 44612250f486420d7b8033ccdeb3fccee8addafa Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Wed, 27 Sep 2023 16:42:56 -0300 Subject: [PATCH 003/137] adiciona pasta publish no .dockerignore --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index cef95d4..8b126c3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .idea/ .git/ .github/ +publish/ From ab764129f727fce9f32cdde81c200e4e8abef773 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:16:17 -0300 Subject: [PATCH 004/137] Update docker-compose.yml Co-authored-by: Yudi Yamane --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d20a255..d2945d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: context: . ports: - "7083:7083" - container_name: usuarioService + container_name: usuario-service volumes: - ./app:/app/app - ./dominio:/app/dominio From ac1db3bad69cecdc58a7d4802117d2127a60caf8 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:18:55 -0300 Subject: [PATCH 005/137] atualiza senha do postgres de desenvolvimento Co-authored-by: Yudi Yamane --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d2945d3..c2593fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: image: postgres:15 restart: always environment: - POSTGRES_PASSWORD: fga1234 + POSTGRES_PASSWORD: 1234 ports: - "5432:5432" volumes: From e924a4f8f43736a61065f01244155aa1c493a171 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:21:32 -0300 Subject: [PATCH 006/137] Apply suggestions from code review Co-authored-by: Yudi Yamane --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c2593fa..2457540 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,9 +15,9 @@ services: env_file: - .env - postgres: - container_name: dnit-pg-database - image: postgres:15 + dnit-usuario-db: + container_name: dnit-usuario-db + image: postgres:15.4 restart: always environment: POSTGRES_PASSWORD: 1234 From 67c9c13f5dfe5e8c58870628d592e3974899a222 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:49:22 -0300 Subject: [PATCH 007/137] chore: Atualiza analise do sonar para usar a branch develop --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b289cea..fe21381 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: SonarCloud on: push: branches: - - main + - develop pull_request: types: [opened, synchronize, reopened] jobs: From 94daa8dd4653e8d5a7585c3b21e459a967f9e107 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:06:29 -0300 Subject: [PATCH 008/137] chore: Cria sonar-project.properties --- sonar-project.properties | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 sonar-project.properties diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..9cccfd1 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,2 @@ +sonar.projectKey=fga-eps-mds_2023.2-Dnit-UsuarioService +sonar.organization=fga-eps-mds-1 From a1b4c9677641606315cd89dda9674e2edaead82d Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Wed, 11 Oct 2023 19:09:47 -0300 Subject: [PATCH 009/137] fix: sonarcloud build.yml --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe21381..793a309 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: SonarCloud on: push: branches: + - main - develop pull_request: types: [opened, synchronize, reopened] @@ -53,12 +54,11 @@ jobs: - 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 + .\.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.login="${{ secrets.SONAR_TOKEN }}" + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" From b2731ee972ea5e8c047b9a05734491ef07d29cfc Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Wed, 11 Oct 2023 19:16:21 -0300 Subject: [PATCH 010/137] fix: remove sonar-project.properties file --- sonar-project.properties | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 sonar-project.properties diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 9cccfd1..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,2 +0,0 @@ -sonar.projectKey=fga-eps-mds_2023.2-Dnit-UsuarioService -sonar.organization=fga-eps-mds-1 From 0f23d7e1db604ad26c1eb7d41bdd7307bc6fc07c Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 11 Oct 2023 21:47:34 -0300 Subject: [PATCH 011/137] refactor: Adiciona o EntityFramework Refs: #2 --- app/DI/ServicesConfig.cs | 4 +++- app/Entidades/AppDbContext.cs | 23 +++++++++++++++++++++++ app/Program.cs | 11 ++++++++++- app/app.csproj | 11 +++++++++++ app/appsettings.Development.json | 4 ++-- 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 app/Entidades/AppDbContext.cs diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index f78d753..cb21709 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,4 +1,5 @@ -using service; +using app.Entidades; +using service; using service.Interfaces; namespace app.DI @@ -7,6 +8,7 @@ public static class ServicesConfig { public static void AddConfigServices(this IServiceCollection services, IConfiguration configuration) { + services.AddDbContext(); services.AddScoped(); services.AddScoped(); } diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs new file mode 100644 index 0000000..0061d87 --- /dev/null +++ b/app/Entidades/AppDbContext.cs @@ -0,0 +1,23 @@ +using dominio; +using Microsoft.EntityFrameworkCore; + +namespace app.Entidades +{ + public class AppDbContext : DbContext + { + private readonly IConfiguration configuration; + + public AppDbContext (IConfiguration configuration) + { + this.configuration = configuration; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql")); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } + } +} \ No newline at end of file diff --git a/app/Program.cs b/app/Program.cs index 70cce3f..cf71bc3 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -1,7 +1,8 @@ using app.DI; +using app.Entidades; +using Microsoft.EntityFrameworkCore; using dominio.Mapper; using Microsoft.OpenApi.Models; -using DotNetEnv; var builder = WebApplication.CreateBuilder(args); @@ -60,4 +61,12 @@ app.MapControllers(); +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider + .GetRequiredService(); + + dbContext.Database.Migrate(); +} + app.Run(); diff --git a/app/app.csproj b/app/app.csproj index 4953896..5a8f7fc 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -7,6 +7,17 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 70b8722..2bc8bd4 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "PostgreSql": "Host=database-dnit-eps-mds.coteugcvtnid.us-east-1.rds.amazonaws.com;Port=5432;Database=postgres;Username=epsmds;Password=epsmds2023" + "PostgreSql": "Host=localhost;Port=5433;Database=usuarioservice;Username=postgres;Password=1234" }, "Logging": { "LogLevel": { @@ -8,4 +8,4 @@ "Microsoft.AspNetCore": "Warning" } } -} +} \ No newline at end of file From 7d8d1daa3325061172de29d6c3c4d171e5dea205 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 10:06:23 -0300 Subject: [PATCH 012/137] =?UTF-8?q?refactor:=20adiciona=20migra=C3=A7?= =?UTF-8?q?=C3=B5es=20iniciais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20231012122128_Initial.Designer.cs | 29 ++++++++++ app/Migrations/20231012122128_Initial.cs | 22 ++++++++ app/Migrations/AppDbContextModelSnapshot.cs | 54 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 app/Migrations/20231012122128_Initial.Designer.cs create mode 100644 app/Migrations/20231012122128_Initial.cs create mode 100644 app/Migrations/AppDbContextModelSnapshot.cs 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..38e16a2 --- /dev/null +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +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("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 + } + } +} From bd3011fb56dec685c504ecace2acc1ee722b9eed Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 10:07:58 -0300 Subject: [PATCH 013/137] refactor: adiciona entidade Usuario --- app/Entidades/AppDbContext.cs | 3 +- app/Entidades/Usuario.cs | 21 +++++++ .../20231012123152_Usuario.Designer.cs | 57 +++++++++++++++++++ app/Migrations/20231012123152_Usuario.cs | 37 ++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 app/Entidades/Usuario.cs create mode 100644 app/Migrations/20231012123152_Usuario.Designer.cs create mode 100644 app/Migrations/20231012123152_Usuario.cs diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 0061d87..9ed95de 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -1,4 +1,3 @@ -using dominio; using Microsoft.EntityFrameworkCore; namespace app.Entidades @@ -7,6 +6,8 @@ public class AppDbContext : DbContext { private readonly IConfiguration configuration; + public DbSet Usuario { get; set; } + public AppDbContext (IConfiguration configuration) { this.configuration = configuration; diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs new file mode 100644 index 0000000..5939a2d --- /dev/null +++ b/app/Entidades/Usuario.cs @@ -0,0 +1,21 @@ +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, MaxLength(150)] + public string nome { get; set; } + + [Required, MaxLength(50)] + public string email { get; set; } + + [Required, MaxLength(200)] + public string senha { get; set; } + } +} \ No newline at end of file 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"); + } + } +} From fd75b30cce426015a43ab4ab013939fe71a26686 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 11:29:21 -0300 Subject: [PATCH 014/137] refactor: adiciona entidade RedefinicaoSenha --- app/Entidades/AppDbContext.cs | 12 +++ app/Entidades/RedefinicaoSenha.cs | 19 ++++ app/Entidades/Usuario.cs | 8 +- ...0231012142410_RedefinicaoSenha.Designer.cs | 96 +++++++++++++++++++ .../20231012142410_RedefinicaoSenha.cs | 77 +++++++++++++++ app/Migrations/AppDbContextModelSnapshot.cs | 45 ++++++++- 6 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 app/Entidades/RedefinicaoSenha.cs create mode 100644 app/Migrations/20231012142410_RedefinicaoSenha.Designer.cs create mode 100644 app/Migrations/20231012142410_RedefinicaoSenha.cs diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 9ed95de..4b7668d 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -7,6 +7,7 @@ public class AppDbContext : DbContext private readonly IConfiguration configuration; public DbSet Usuario { get; set; } + public DbSet RedefinicaoSenha { get; set; } public AppDbContext (IConfiguration configuration) { @@ -19,6 +20,17 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(u => u.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .Property(r => r.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasOne(r => r.Usuario) + .WithMany(u => u.RedefinicaoSenha) + .HasForeignKey(r => r.IdUsuario); } } } \ No newline at end of file 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 index 5939a2d..97cd8ca 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -10,12 +10,14 @@ public class Usuario public int Id { get; set; } [Required, MaxLength(150)] - public string nome { get; set; } + public string Nome { get; set; } [Required, MaxLength(50)] - public string email { get; set; } + public string Email { get; set; } [Required, MaxLength(200)] - public string senha { get; set; } + public string Senha { get; set; } + + public List RedefinicaoSenha { get; set; } } } \ No newline at end of file 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 38e16a2..3b5f6fb 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -21,6 +21,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -29,17 +52,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("email") + b.Property("Email") .IsRequired() .HasMaxLength(50) .HasColumnType("character varying(50)"); - b.Property("nome") + b.Property("Nome") .IsRequired() .HasMaxLength(150) .HasColumnType("character varying(150)"); - b.Property("senha") + b.Property("Senha") .IsRequired() .HasMaxLength(200) .HasColumnType("character varying(200)"); @@ -48,6 +71,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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 } } From 4cf8e3ef7c14635a4fb58f69f691becf5f412b99 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 11:56:37 -0300 Subject: [PATCH 015/137] refactor: adiciona entidade Empresa. --- app/Entidades/AppDbContext.cs | 1 + app/Entidades/Empresa.cs | 15 +++ .../20231012145354_Empresa.Designer.cs | 113 ++++++++++++++++++ app/Migrations/20231012145354_Empresa.cs | 33 +++++ app/Migrations/AppDbContextModelSnapshot.cs | 17 +++ 5 files changed, 179 insertions(+) create mode 100644 app/Entidades/Empresa.cs create mode 100644 app/Migrations/20231012145354_Empresa.Designer.cs create mode 100644 app/Migrations/20231012145354_Empresa.cs diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 4b7668d..7016d8c 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -8,6 +8,7 @@ public class AppDbContext : DbContext public DbSet Usuario { get; set; } public DbSet RedefinicaoSenha { get; set; } + public DbSet Empresa { get; set; } public AppDbContext (IConfiguration configuration) { diff --git a/app/Entidades/Empresa.cs b/app/Entidades/Empresa.cs new file mode 100644 index 0000000..db3f672 --- /dev/null +++ b/app/Entidades/Empresa.cs @@ -0,0 +1,15 @@ +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;} + } +} \ No newline at end of file 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 3b5f6fb..076cb35 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -21,6 +21,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") From 4880fd5cc485d87ac095e0f03882f0cbf5a12a8d Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 17:17:18 -0300 Subject: [PATCH 016/137] refactor: adiciona a tabela de relacionamento UsuarioEmpresa --- app/Entidades/AppDbContext.cs | 10 ++ app/Entidades/Empresa.cs | 6 +- app/Entidades/Usuario.cs | 3 + .../20231012200221_UsuarioEmpresa.Designer.cs | 145 ++++++++++++++++++ .../20231012200221_UsuarioEmpresa.cs | 50 ++++++ app/Migrations/AppDbContextModelSnapshot.cs | 32 ++++ 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 app/Migrations/20231012200221_UsuarioEmpresa.Designer.cs create mode 100644 app/Migrations/20231012200221_UsuarioEmpresa.cs diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 7016d8c..29e7f80 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -32,6 +32,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasOne(r => r.Usuario) .WithMany(u => u.RedefinicaoSenha) .HasForeignKey(r => r.IdUsuario); + + 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"); + }); } } } \ No newline at end of file diff --git a/app/Entidades/Empresa.cs b/app/Entidades/Empresa.cs index db3f672..b5e3141 100644 --- a/app/Entidades/Empresa.cs +++ b/app/Entidades/Empresa.cs @@ -7,9 +7,11 @@ public class Empresa { [Key, MaxLength(14)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public string Cnpj { get; set;} + public string Cnpj { get; set; } [Required, MaxLength(200)] - public string RazaoSocial { get; set;} + public string RazaoSocial { get; set; } + + public List Usuarios { get; set; } } } \ No newline at end of file diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 97cd8ca..3d06695 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Security.Cryptography.X509Certificates; namespace app.Entidades { @@ -19,5 +20,7 @@ public class Usuario public string Senha { get; set; } public List RedefinicaoSenha { get; set; } + + public List Empresas { get; set; } } } \ No newline at end of file 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 076cb35..1fb6eca 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -21,6 +21,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -89,6 +106,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") From ff94f4bf963f9c9bf1f397e4402f1f65c545d817 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 12 Oct 2023 17:35:52 -0300 Subject: [PATCH 017/137] refactor: adiciona enum par unidades federativas --- app/Entidades/Enums.cs | 62 ++++++++ app/Entidades/Usuario.cs | 3 + .../20231012202644_UfEnum.Designer.cs | 148 ++++++++++++++++++ app/Migrations/20231012202644_UfEnum.cs | 29 ++++ app/Migrations/AppDbContextModelSnapshot.cs | 3 + 5 files changed, 245 insertions(+) create mode 100644 app/Entidades/Enums.cs create mode 100644 app/Migrations/20231012202644_UfEnum.Designer.cs create mode 100644 app/Migrations/20231012202644_UfEnum.cs diff --git a/app/Entidades/Enums.cs b/app/Entidades/Enums.cs new file mode 100644 index 0000000..6bf046b --- /dev/null +++ b/app/Entidades/Enums.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; + +namespace app.Entidades +{ + 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 + } +} \ No newline at end of file diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 3d06695..5f3c7f9 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -10,6 +10,9 @@ public class Usuario [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } + [Required] + public UF UfLotacao { get; set; } + [Required, MaxLength(150)] public string Nome { get; set; } 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 1fb6eca..3790dba 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -101,6 +101,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("UfLotacao") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("Usuario"); From ba5c67d6abf2dc3d480bb9fc107151747c2d99f2 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Fri, 13 Oct 2023 13:35:18 -0300 Subject: [PATCH 018/137] =?UTF-8?q?refactor:=20reorganiza=20a=20disposi?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20pastas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 62 +++++++ api/Senhas/RedefinicaoSenhaDTO.cs | 8 + api/Senhas/RedefinicaoSenhaModel.cs | 8 + api/UfModel.cs | 9 + api/Usuarios/UsuarioDTO.cs | 11 ++ api/Usuarios/UsuarioDnit.cs | 7 + api/Usuarios/UsuarioModel.cs | 16 ++ api/Usuarios/UsuarioTerceiro.cs | 7 + api/api.csproj | 10 ++ app/Controllers/DominioController.cs | 13 +- app/Controllers/UsuarioController.cs | 9 +- app/DI/ContextoConfig.cs | 38 ----- app/DI/RepositoriosConfig.cs | 4 +- app/DI/ServicesConfig.cs | 4 +- app/Program.cs | 6 +- .../IUnidadeFederativaRepositorio.cs | 10 ++ .../Interfaces/IUsuarioRepositorio.cs | 16 ++ .../UnidadeFederativaRepositorio.cs | 25 +++ app/Repositorios/UsuarioRepositorio.cs | 159 ++++++++++++++++++ app/Services/EmailService.cs | 33 ++++ app/Services/Interfaces/IEmailService.cs | 7 + app/Services/Interfaces/IUsuarioService.cs | 14 ++ app/Services/Mapper.cs | 20 +++ app/Services/UsuarioService.cs | 118 +++++++++++++ app/app.csproj | 11 +- 25 files changed, 565 insertions(+), 60 deletions(-) create mode 100644 api/Enums.cs create mode 100644 api/Senhas/RedefinicaoSenhaDTO.cs create mode 100644 api/Senhas/RedefinicaoSenhaModel.cs create mode 100644 api/UfModel.cs create mode 100644 api/Usuarios/UsuarioDTO.cs create mode 100644 api/Usuarios/UsuarioDnit.cs create mode 100644 api/Usuarios/UsuarioModel.cs create mode 100644 api/Usuarios/UsuarioTerceiro.cs create mode 100644 api/api.csproj delete mode 100644 app/DI/ContextoConfig.cs create mode 100644 app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs create mode 100644 app/Repositorios/Interfaces/IUsuarioRepositorio.cs create mode 100644 app/Repositorios/UnidadeFederativaRepositorio.cs create mode 100644 app/Repositorios/UsuarioRepositorio.cs create mode 100644 app/Services/EmailService.cs create mode 100644 app/Services/Interfaces/IEmailService.cs create mode 100644 app/Services/Interfaces/IUsuarioService.cs create mode 100644 app/Services/Mapper.cs create mode 100644 app/Services/UsuarioService.cs diff --git a/api/Enums.cs b/api/Enums.cs new file mode 100644 index 0000000..90ae429 --- /dev/null +++ b/api/Enums.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; + +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 + } +} \ 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/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs new file mode 100644 index 0000000..7c91791 --- /dev/null +++ b/api/Usuarios/UsuarioDTO.cs @@ -0,0 +1,11 @@ +namespace api.Usuarios +{ + 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/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs new file mode 100644 index 0000000..3b34193 --- /dev/null +++ b/api/Usuarios/UsuarioDnit.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioDnit : UsuarioModel + { + public int UF { 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..555b878 --- /dev/null +++ b/api/Usuarios/UsuarioModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace api.Usuarios +{ + public class UsuarioModel + { + public int Id { get; set; } + public string Email { get; set; } + public string Senha { get; set; } + public string Nome { get; set; } + } +} diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs new file mode 100644 index 0000000..1ac8d6c --- /dev/null +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioTerceiro : UsuarioModel + { + public string CNPJ { get; set; } + } +} diff --git a/api/api.csproj b/api/api.csproj new file mode 100644 index 0000000..16e62dd --- /dev/null +++ b/api/api.csproj @@ -0,0 +1,10 @@ + + + + Library + net6.0 + enable + enable + + + diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index bb4638e..f13c719 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -1,9 +1,10 @@ -using dominio; +using app.Entidades; +using api; using Microsoft.AspNetCore.Mvc; -using repositorio; -using repositorio.Interfaces; -using service; -using service.Interfaces; +using app.Repositorios; +using app.Repositorios.Interfaces; +using app.Services; +using app.Services.Interfaces; namespace app.Controllers { @@ -23,7 +24,7 @@ public DominioController(IUnidadeFederativaRepositorio unidadeFederativaReposito [HttpGet("unidadeFederativa")] public IActionResult ObterLista() { - IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); + IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); return new OkObjectResult(listaUnidadeFederativa); } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 93b954c..1fe9b25 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -1,6 +1,7 @@ -using dominio; +using api.Usuarios; +using api.Senhas; using Microsoft.AspNetCore.Mvc; -using service.Interfaces; +using app.Services.Interfaces; namespace app.Controllers { @@ -43,7 +44,7 @@ public IActionResult CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) catch (Npgsql.PostgresException ex) { if (ex.SqlState == "23505") { - return Conflict("Usurio j cadastrado."); + return Conflict("Usu�rio j� cadastrado."); } return StatusCode(500, "Houve um erro interno no servidor."); @@ -63,7 +64,7 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) { if (ex.SqlState == "23505") { - return Conflict("Usurio j cadastrado."); + return Conflict("Usu�rio j� cadastrado."); } return StatusCode(500, "Houve um erro interno no servidor."); 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/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index eedac06..3be8d57 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 { diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index cb21709..183c77b 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,6 +1,6 @@ using app.Entidades; -using service; -using service.Interfaces; +using app.Services; +using app.Services.Interfaces; namespace app.DI { diff --git a/app/Program.cs b/app/Program.cs index cf71bc3..de4eda4 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -1,7 +1,7 @@ using app.DI; using app.Entidades; +using app.Services.Mapper; using Microsoft.EntityFrameworkCore; -using dominio.Mapper; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -24,7 +24,7 @@ { Version = "v1", Title = "UsuarioService", - Description = "Microserivo UsuarioService" + Description = "Microserviço UsuarioService" }); }); @@ -32,8 +32,6 @@ builder.Services.AddConfigRepositorios(); -builder.Services.AddContexto(builder.Configuration); - builder.Services.AddCors(options => { options.AddPolicy("AllowAllOrigins", diff --git a/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs new file mode 100644 index 0000000..5ff6219 --- /dev/null +++ b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs @@ -0,0 +1,10 @@ +using api; +using System.Collections.Generic; + +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..79e33f9 --- /dev/null +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -0,0 +1,16 @@ +using api.Usuarios; +using api.Senhas; + +namespace app.Repositorios.Interfaces +{ + public interface IUsuarioRepositorio + { + public UsuarioModel? ObterUsuario(string email); + public UsuarioDnit TrocarSenha(string senha, string email); + public RedefinicaoSenhaModel 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/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs new file mode 100644 index 0000000..faab684 --- /dev/null +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -0,0 +1,25 @@ +using Dapper; +using app.Entidades; +using api; +using app.Repositorios.Interfaces; + +namespace app.Repositorios +{ + public class UnidadeFederativaRepositorio : IUnidadeFederativaRepositorio + { + private readonly AppDbContext dbContext; + public UnidadeFederativaRepositorio(AppDbContext dbContext) + { + this.dbContext = dbContext; + } + + public IEnumerable ObterDominio() + { + //Não ajustada ainda, retorna lista vazia + + var mock = new List(); + + return mock; + } + } +} diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs new file mode 100644 index 0000000..4bd2a27 --- /dev/null +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -0,0 +1,159 @@ +using app.Entidades; +using Dapper; +using api.Usuarios; +using api.Senhas; +using app.Repositorios.Interfaces; + +namespace app.Repositorios +{ + public class UsuarioRepositorio : IUsuarioRepositorio + { + private readonly AppDbContext dbContext; + + public UsuarioRepositorio(AppDbContext dbContext) + { + this.dbContext = dbContext; + } + + public UsuarioModel? ObterUsuario(string email) + { + // !! Não ajustado ainda, retorna objeto vazio; + + /* 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); */ + + var mock = new UsuarioModel(); + + return mock; + } + + public void CadastrarUsuarioDnit(UsuarioDnit usuario) + { + + // !! Método não ajustado + + /* 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) + { + // !! Não ajustado ainda, retorna objeto vazio; + + /* 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);*/ + + var mock = new UsuarioDnit(); + + return mock; + } + + public string? ObterEmailRedefinicaoSenha(string uuid) + { + // !! Não ajustado ainda, retorna string generica; + + /* var sqlBuscarDados = @"SELECT u.email FROM public.RedefinicaoSenha 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); */ + + var mock = "foo"; + + return mock; + } + + public void RemoverUuidRedefinicaoSenha(string uuid) + { + // !! Método não ajustado + + /* var sqlBuscarDados = @"DELETE FROM public.RedefinicaoSenha WHERE uuid = @Uuid"; + + var parametro = new + { + Uuid = uuid, + }; + + contexto?.Conexao.Execute(sqlBuscarDados, parametro); */ + } + + public RedefinicaoSenhaModel InserirDadosRecuperacao(string uuid, int idUsuario) + { + // !! Não ajustado ainda, retorna objeto vazio; + + /* var sqlInserirDadosRecuperacao = @"INSERT INTO public.RedefinicaoSenha(uuid, id_usuario) VALUES(@Uuid, @IdUsuario) RETURNING id"; + + var parametro = new + { + Uuid = uuid, + IdUsuario = idUsuario + }; + + var dadosRedefinicao = contexto?.Conexao.QuerySingleOrDefault(sqlInserirDadosRecuperacao, parametro); */ + + var mock = new RedefinicaoSenhaModel(); + + return mock; + } + + public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) + { + // !! Método não ajustado + + /* 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/app/Services/EmailService.cs b/app/Services/EmailService.cs new file mode 100644 index 0000000..a972f0c --- /dev/null +++ b/app/Services/EmailService.cs @@ -0,0 +1,33 @@ +using app.Services.Interfaces; +using System.Net.Mail; +using System.Net; +using System; + +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"); + + 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") + { + Port = 587, + Credentials = new NetworkCredential(emailRemetente, senhaRemetente), + EnableSsl = true, + + }; + clienteSmtp.Send(mensagem); + } + } +} diff --git a/app/Services/Interfaces/IEmailService.cs b/app/Services/Interfaces/IEmailService.cs new file mode 100644 index 0000000..b357295 --- /dev/null +++ b/app/Services/Interfaces/IEmailService.cs @@ -0,0 +1,7 @@ +namespace app.Services.Interfaces +{ + public interface IEmailService + { + public void EnviarEmail(string emailDestinatario, string assunto, string corpo); + } +} diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs new file mode 100644 index 0000000..5808a09 --- /dev/null +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -0,0 +1,14 @@ +using api.Usuarios; +using api.Senhas; + +namespace app.Services.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/app/Services/Mapper.cs b/app/Services/Mapper.cs new file mode 100644 index 0000000..69708dc --- /dev/null +++ b/app/Services/Mapper.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using api.Senhas; +using api.Usuarios; + +namespace app.Services.Mapper +{ + public class AutoMapperConfig : Profile + { + public AutoMapperConfig() + { + CreateMap() + .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); + } + } +} \ No newline at end of file diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs new file mode 100644 index 0000000..ae77645 --- /dev/null +++ b/app/Services/UsuarioService.cs @@ -0,0 +1,118 @@ +using api.Usuarios; +using api.Senhas; +using app.Repositorios.Interfaces; +using app.Services.Interfaces; +using AutoMapper; +using System.Collections.Generic; +using System; +using BCryptNet = BCrypt.Net.BCrypt; +using Microsoft.Extensions.Configuration; + +namespace app.Services +{ + 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 UsuarioModel? Obter(string email) + { + UsuarioModel? usuario = usuarioRepositorio.ObterUsuario(email); + + if (usuario == null) + throw new KeyNotFoundException(); + + return usuario; + } + + public bool ValidaLogin(UsuarioDTO usuarioDTO) + { + UsuarioModel? 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) + { + 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); + } + + public void RecuperarSenha(UsuarioDTO usuarioDTO) + { + var usuarioEntrada = mapper.Map(usuarioDTO); + UsuarioModel 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/app/app.csproj b/app/app.csproj index 5a8f7fc..31c0109 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -7,6 +7,11 @@ + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,9 +29,7 @@ - - - + - + From cd40140c878375a1201720f49b828f95f5bcb292 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Fri, 13 Oct 2023 13:46:42 -0300 Subject: [PATCH 019/137] refactor: ajusta as referencias --- UsuarioService.sln | 38 ++--- dominio/Enums/ContextoBancoDeDados.cs | 7 - dominio/RedefinicaoSenha.cs | 8 - dominio/RedefinicaoSenhaDTO.cs | 8 - dominio/UnidadeFederativa.cs | 9 -- dominio/Usuario.cs | 16 -- dominio/UsuarioDTO.cs | 17 --- dominio/UsuarioDnit.cs | 7 - dominio/UsuarioTerceiro.cs | 13 -- dominio/dominio.csproj | 10 -- repositorio/Contexto/ContextoPostgresql.cs | 13 -- repositorio/Contexto/IContexto.cs | 9 -- repositorio/Contexto/ResolverContexto.cs | 9 -- .../IUnidadeFederativaRepositorio.cs | 10 -- repositorio/Interfaces/IUsuarioRepositorio.cs | 16 -- repositorio/UnidadeFederativaRepositorio.cs | 28 ---- repositorio/UsuarioRepositorio.cs | 138 ------------------ repositorio/repositorio.csproj | 17 --- service/EmailService.cs | 33 ----- service/Interfaces/IEmailService.cs | 7 - service/Interfaces/IUsuarioService.cs | 19 --- service/Mapper/UsuarioProfile.cs | 18 --- service/UsuarioService.cs | 117 --------------- service/service.csproj | 20 --- 24 files changed, 10 insertions(+), 577 deletions(-) delete mode 100644 dominio/Enums/ContextoBancoDeDados.cs delete mode 100644 dominio/RedefinicaoSenha.cs delete mode 100644 dominio/RedefinicaoSenhaDTO.cs delete mode 100644 dominio/UnidadeFederativa.cs delete mode 100644 dominio/Usuario.cs delete mode 100644 dominio/UsuarioDTO.cs delete mode 100644 dominio/UsuarioDnit.cs delete mode 100644 dominio/UsuarioTerceiro.cs delete mode 100644 dominio/dominio.csproj delete mode 100644 repositorio/Contexto/ContextoPostgresql.cs delete mode 100644 repositorio/Contexto/IContexto.cs delete mode 100644 repositorio/Contexto/ResolverContexto.cs delete mode 100644 repositorio/Interfaces/IUnidadeFederativaRepositorio.cs delete mode 100644 repositorio/Interfaces/IUsuarioRepositorio.cs delete mode 100644 repositorio/UnidadeFederativaRepositorio.cs delete mode 100644 repositorio/UsuarioRepositorio.cs delete mode 100644 repositorio/repositorio.csproj delete mode 100644 service/EmailService.cs delete mode 100644 service/Interfaces/IEmailService.cs delete mode 100644 service/Interfaces/IUsuarioService.cs delete mode 100644 service/Mapper/UsuarioProfile.cs delete mode 100644 service/UsuarioService.cs delete mode 100644 service/service.csproj diff --git a/UsuarioService.sln b/UsuarioService.sln index cb4ada2..7c9b9f7 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -3,15 +3,9 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "service", "service\service.csproj", "{7F390C74-6B61-4479-8840-E57D6B9E56C5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "repositorio", "repositorio\repositorio.csproj", "{456103BA-9130-4604-8AF7-49632FBAB6BB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{4D6B0EB8-A866-4742-AF0F-30706B1D282F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "app", "app\app.csproj", "{08942609-AAE1-4D23-B71D-E39D1A7CF566}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,26 +13,14 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE 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/dominio/dominio.csproj b/dominio/dominio.csproj deleted file mode 100644 index 16e62dd..0000000 --- a/dominio/dominio.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Library - net6.0 - enable - enable - - - 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/EmailService.cs b/service/EmailService.cs deleted file mode 100644 index 2d21b72..0000000 --- a/service/EmailService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using service.Interfaces; -using System.Net.Mail; -using System.Net; -using System; - -namespace service -{ - 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"); - - 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") - { - Port = 587, - Credentials = new NetworkCredential(emailRemetente, senhaRemetente), - EnableSsl = true, - - }; - clienteSmtp.Send(mensagem); - } - } -} diff --git a/service/Interfaces/IEmailService.cs b/service/Interfaces/IEmailService.cs deleted file mode 100644 index 601c4e6..0000000 --- a/service/Interfaces/IEmailService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace service.Interfaces -{ - public interface IEmailService - { - public void EnviarEmail(string emailDestinatario, string assunto, string corpo); - } -} 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 - - - - - - - - - - - - - - - From cd0cef696c73af52304e4afa8aebd4e4f841cba9 Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Sat, 14 Oct 2023 19:11:03 -0300 Subject: [PATCH 020/137] fix: release workflow repository --- .github/workflows/release.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 337ed29..3859501 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,13 +12,15 @@ 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 }} + + - name: Uploads file - uses: actions/upload-artifact@v2 with: name: ${{ 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.GITHUB_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 }} From b9acdd4b97b7d0cc1a578078948b378a9c36280f Mon Sep 17 00:00:00 2001 From: Wagner M Cunha Date: Sat, 14 Oct 2023 19:29:14 -0300 Subject: [PATCH 021/137] chore: Renomeia secret GITHUB_TOKEN para GIT_TOKEN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GITHUB_TOKEN é um secret criado automaticamente pelo próprio github actions --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3859501..0ab7fa3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: Send metrics to doc repo uses: dmnemec/copy_file_to_another_repo_action@v1.1.1 env: - API_TOKEN_GITHUB: ${{ secrets.GITHUB_TOKEN }} + API_TOKEN_GITHUB: ${{ secrets.GIT_TOKEN }} with: source_file: ${{ steps.name.outputs.file_name }}.json destination_repo: 'fga-eps-mds/2023.2-Dnit-DOC' From 935fbfaf73204c82753579eb5f97e8df47ced490 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Sun, 15 Oct 2023 21:16:31 -0300 Subject: [PATCH 022/137] fix: arruma formato do release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ab7fa3..0a43e84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: METRICS_URL: ${{ secrets.METRICS_URL }} - name: Uploads file - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 with: name: ${{ steps.name.outputs.file_name }}.json path: ${{ steps.name.outputs.file_name }}.json From 9b0d25846c74014cd16b5a0d4ad887bd2a6b784d Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Mon, 16 Oct 2023 00:27:20 -0300 Subject: [PATCH 023/137] refactor: ajusta rotinas para EF --- .gitignore | 2 + api/Usuarios/UsuarioDTO.cs | 2 +- api/Usuarios/UsuarioDnit.cs | 2 +- app/.env | 2 + app/Controllers/UsuarioController.cs | 13 +- app/Entidades/Enums.cs | 62 -------- app/Entidades/Usuario.cs | 2 +- .../Interfaces/IUsuarioRepositorio.cs | 4 +- .../UnidadeFederativaRepositorio.cs | 13 +- app/Repositorios/UsuarioRepositorio.cs | 132 +++++------------- app/Services/EmailService.cs | 6 +- app/Services/Interfaces/IUsuarioService.cs | 6 +- app/Services/Mapper.cs | 12 ++ app/Services/UsuarioService.cs | 27 +++- app/app.csproj | 1 + app/appsettings.Development.json | 3 +- 16 files changed, 100 insertions(+), 189 deletions(-) create mode 100644 app/.env delete mode 100644 app/Entidades/Enums.cs diff --git a/.gitignore b/.gitignore index 194a7be..2ba547e 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 diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs index 7c91791..a622b46 100644 --- a/api/Usuarios/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -5,7 +5,7 @@ public class UsuarioDTO public string Email { get; set; } public string Senha { get; set; } public string Nome { get; set; } - public int? UF { get; set; } + public UF UfLotacao { get; set; } public string? CNPJ { get; set; } } } diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs index 3b34193..3ce3909 100644 --- a/api/Usuarios/UsuarioDnit.cs +++ b/api/Usuarios/UsuarioDnit.cs @@ -2,6 +2,6 @@ namespace api.Usuarios { public class UsuarioDnit : UsuarioModel { - public int UF { get; set; } + public UF UfLotacao { get; set; } } } \ No newline at end of file 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/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 1fe9b25..146489f 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -33,11 +33,11 @@ public IActionResult Logar([FromBody] UsuarioDTO usuarioDTO) } [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()); } @@ -72,11 +72,11 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) } [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) @@ -86,11 +86,12 @@ public IActionResult RecuperarSenha([FromBody] UsuarioDTO usuarioDto) } [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) diff --git a/app/Entidades/Enums.cs b/app/Entidades/Enums.cs deleted file mode 100644 index 6bf046b..0000000 --- a/app/Entidades/Enums.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.ComponentModel; - -namespace app.Entidades -{ - 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 - } -} \ No newline at end of file diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 5f3c7f9..366349d 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -1,6 +1,6 @@ +using api; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Security.Cryptography.X509Certificates; namespace app.Entidades { diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 79e33f9..07ac20e 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -6,8 +6,8 @@ namespace app.Repositorios.Interfaces public interface IUsuarioRepositorio { public UsuarioModel? ObterUsuario(string email); - public UsuarioDnit TrocarSenha(string senha, string email); - public RedefinicaoSenhaModel InserirDadosRecuperacao(string uuid, int idUsuario); + public UsuarioModel? TrocarSenha(string senha, string email); + public void InserirDadosRecuperacao(string uuid, int idUsuario); public string? ObterEmailRedefinicaoSenha(string uuid); public void RemoverUuidRedefinicaoSenha(string uuid); public void CadastrarUsuarioDnit(UsuarioDnit usuario); diff --git a/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs index faab684..46bc4d3 100644 --- a/app/Repositorios/UnidadeFederativaRepositorio.cs +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -2,24 +2,21 @@ using app.Entidades; using api; using app.Repositorios.Interfaces; +using AutoMapper; namespace app.Repositorios { public class UnidadeFederativaRepositorio : IUnidadeFederativaRepositorio { - private readonly AppDbContext dbContext; - public UnidadeFederativaRepositorio(AppDbContext dbContext) + private readonly IMapper mapper; + public UnidadeFederativaRepositorio(IMapper mapper) { - this.dbContext = dbContext; + this.mapper = mapper; } public IEnumerable ObterDominio() { - //Não ajustada ainda, retorna lista vazia - - var mock = new List(); - - return mock; + return Enum.GetValues().Select(uf => mapper.Map(uf)).OrderBy(uf => uf.Sigla);; } } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 4bd2a27..abce43e 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -3,157 +3,97 @@ using api.Usuarios; using api.Senhas; using app.Repositorios.Interfaces; +using Microsoft.EntityFrameworkCore; +using AutoMapper; namespace app.Repositorios { public class UsuarioRepositorio : IUsuarioRepositorio { private readonly AppDbContext dbContext; + private readonly IMapper mapper; - public UsuarioRepositorio(AppDbContext dbContext) + public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) { this.dbContext = dbContext; + this.mapper = mapper; } public UsuarioModel? ObterUsuario(string email) - { - // !! Não ajustado ainda, retorna objeto vazio; - - /* 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); */ - - var mock = new UsuarioModel(); - - return mock; + { + var query = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); + + return mapper.Map(query); } public void CadastrarUsuarioDnit(UsuarioDnit usuario) { - - // !! Método não ajustado - - /* var sqlInserirUsuario = @"INSERT INTO public.usuario(nome, email, senha) VALUES(@Nome, @Email, @Senha) RETURNING id"; - - var parametrosUsuario = new + var novoUsuario = new Usuario { Nome = usuario.Nome, Email = usuario.Email, Senha = usuario.Senha, + UfLotacao = usuario.UfLotacao }; - 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); */ + dbContext.Add(novoUsuario); } - public UsuarioDnit TrocarSenha(string email, string senha) + public UsuarioModel? TrocarSenha(string email, string senha) { - // !! Não ajustado ainda, retorna objeto vazio; + var usuario = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); - /* var sqlTrocarSenha = @"UPDATE public.usuario SET senha = @Senha WHERE email = @Email"; - - var parametro = new + if (usuario != null) { - Email = email, - Senha = senha - }; - var usuarioDnit = contexto?.Conexao.QuerySingleOrDefault(sqlTrocarSenha, parametro);*/ - - var mock = new UsuarioDnit(); + usuario.Senha = senha; + } - return mock; + return mapper.Map(usuario); } public string? ObterEmailRedefinicaoSenha(string uuid) { - // !! Não ajustado ainda, retorna string generica; - - /* var sqlBuscarDados = @"SELECT u.email FROM public.RedefinicaoSenha 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); */ - - var mock = "foo"; + 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 mock; + return query.FirstOrDefault(); } public void RemoverUuidRedefinicaoSenha(string uuid) { - // !! Método não ajustado - - /* var sqlBuscarDados = @"DELETE FROM public.RedefinicaoSenha WHERE uuid = @Uuid"; - - var parametro = new - { - Uuid = uuid, - }; + var registro = dbContext.RedefinicaoSenha.Where(rs => rs.Uuid == uuid).FirstOrDefault(); - contexto?.Conexao.Execute(sqlBuscarDados, parametro); */ + dbContext.RedefinicaoSenha.Remove(registro); } - public RedefinicaoSenhaModel InserirDadosRecuperacao(string uuid, int idUsuario) + public void InserirDadosRecuperacao(string uuid, int idUsuario) { - // !! Não ajustado ainda, retorna objeto vazio; - - /* var sqlInserirDadosRecuperacao = @"INSERT INTO public.RedefinicaoSenha(uuid, id_usuario) VALUES(@Uuid, @IdUsuario) RETURNING id"; - - var parametro = new - { + var newRs = new RedefinicaoSenha + { Uuid = uuid, - IdUsuario = idUsuario + IdUsuario = idUsuario, }; - var dadosRedefinicao = contexto?.Conexao.QuerySingleOrDefault(sqlInserirDadosRecuperacao, parametro); */ - - var mock = new RedefinicaoSenhaModel(); - - return mock; + dbContext.RedefinicaoSenha.Add(newRs); } public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) { - // !! Método não ajustado + var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); - /* var sqlInserirUsuarioTerceiro = @"INSERT INTO public.usuario(nome, email, senha) VALUES(@Nome, @Email, @Senha) RETURNING id"; + List empresas = new List{ empresa }; - var parametrosUsuarioTerceiro = new + var novoUsuarioTerceiro = new Usuario { 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 + Senha = usuarioTerceiro.Senha, + Empresas = empresas }; - contexto?.Conexao.Execute(sqlInserirEmpresa, parametrosEmpresa); */ + dbContext.Usuario.Add(novoUsuarioTerceiro); } } } diff --git a/app/Services/EmailService.cs b/app/Services/EmailService.cs index a972f0c..793888c 100644 --- a/app/Services/EmailService.cs +++ b/app/Services/EmailService.cs @@ -9,11 +9,10 @@ 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; @@ -27,6 +26,7 @@ public void EnviarEmail(string emailDestinatario, string assunto, string corpo) EnableSsl = true, }; + clienteSmtp.Send(mensagem); } } diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index 5808a09..793d4a4 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -6,9 +6,9 @@ namespace app.Services.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 Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); + public Task RecuperarSenha(UsuarioDTO usuarioDto); + public Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 69708dc..421ff67 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -1,6 +1,10 @@ using AutoMapper; using api.Senhas; using api.Usuarios; +using app.Entidades; +using api; +using EnumsNET; + namespace app.Services.Mapper { @@ -8,6 +12,14 @@ public class AutoMapperConfig : Profile { public AutoMapperConfig() { + 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() .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index ae77645..7e775a9 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -7,6 +7,7 @@ using System; using BCryptNet = BCrypt.Net.BCrypt; using Microsoft.Extensions.Configuration; +using app.Entidades; namespace app.Services { @@ -17,22 +18,33 @@ public class UsuarioService : IUsuarioService private readonly IMapper mapper; private readonly IEmailService emailService; private readonly IConfiguration configuration; - - public UsuarioService(IUsuarioRepositorio usuarioRepositorio, IMapper mapper, IEmailService emailService, IConfiguration configuration) + private readonly AppDbContext dbContext; + + public UsuarioService + ( + IUsuarioRepositorio usuarioRepositorio, + IMapper mapper, + IEmailService emailService, + IConfiguration configuration, + AppDbContext dbContext + ) { this.usuarioRepositorio = usuarioRepositorio; this.mapper = mapper; this.emailService = emailService; this.configuration = configuration; + this.dbContext = dbContext; } - public void CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) + public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) { var usuario = mapper.Map(usuarioDTO); usuario.Senha = EncriptarSenha(usuario.Senha); usuarioRepositorio.CadastrarUsuarioDnit(usuario); + + await dbContext.SaveChangesAsync(); } private string EncriptarSenha(string senha) @@ -76,7 +88,7 @@ private bool ValidaSenha(string senhaUsuarioEntrada, string senhaUsuarioBanco) throw new UnauthorizedAccessException(); } - public void TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) + public async Task TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) { RedefinicaoSenhaModel dadosRedefinicaoSenha = mapper.Map(redefinicaoSenhaDTO); @@ -88,11 +100,14 @@ public void TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) emailService.EnviarEmail(emailUsuario, "Senha Atualizada", "A sua senha foi atualizada com sucesso."); usuarioRepositorio.RemoverUuidRedefinicaoSenha(dadosRedefinicaoSenha.UuidAutenticacao); + + await dbContext.SaveChangesAsync(); } - public void RecuperarSenha(UsuarioDTO usuarioDTO) + public async Task RecuperarSenha(UsuarioDTO usuarioDTO) { var usuarioEntrada = mapper.Map(usuarioDTO); + UsuarioModel usuarioBanco = Obter(usuarioEntrada.Email); string UuidAutenticacao = Guid.NewGuid().ToString(); @@ -105,6 +120,8 @@ public void RecuperarSenha(UsuarioDTO usuarioDTO) $"{GerarLinkDeRecuperacao(UuidAutenticacao)}"; emailService.EnviarEmail(usuarioBanco.Email, "Link de Recuperação", mensagem); + + await dbContext.SaveChangesAsync(); } private string GerarLinkDeRecuperacao(string UuidAutenticacao) { diff --git a/app/app.csproj b/app/app.csproj index 31c0109..bc551b2 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -7,6 +7,7 @@ + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 2bc8bd4..8c65753 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -7,5 +7,6 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" } \ No newline at end of file From d1f9e1ec0c13438fe2cc52c5e738c80671a65d09 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:41:23 -0300 Subject: [PATCH 024/137] refactor: ajusta a rotina de testes para o EF --- UsuarioService.sln | 6 +++++ test/Contexto.cs | 21 --------------- test/ContextoConfigTest.cs | 46 --------------------------------- test/ContextoPostgresqlTest.cs | 23 ----------------- test/Fixtures/Base.cs | 47 ++++++++++++++++++++++++++++++++++ test/Usings.cs | 2 ++ test/test.csproj | 14 +++++++--- 7 files changed, 65 insertions(+), 94 deletions(-) delete mode 100644 test/Contexto.cs delete mode 100644 test/ContextoConfigTest.cs delete mode 100644 test/ContextoPostgresqlTest.cs create mode 100644 test/Fixtures/Base.cs create mode 100644 test/Usings.cs diff --git a/UsuarioService.sln b/UsuarioService.sln index 7c9b9f7..ad9d17a 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "api", "api\api.csproj", "{9 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "app", "app\app.csproj", "{08942609-AAE1-4D23-B71D-E39D1A7CF566}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{1D4F20EE-9463-4E06-805F-F4CADF503164}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE 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/Fixtures/Base.cs b/test/Fixtures/Base.cs new file mode 100644 index 0000000..d41588b --- /dev/null +++ b/test/Fixtures/Base.cs @@ -0,0 +1,47 @@ +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 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 + services.AddScoped(); + services.AddScoped(); + services.AddAutoMapper(typeof(AutoMapperConfig)); + + // Controllers + services.AddScoped(); + services.AddScoped(); + } + + protected override ValueTask DisposeAsyncCore() => new(); + + protected override IEnumerable GetTestAppSettings() + { + yield return new() { Filename = "appsettings.json", IsOptional = false }; + } + } +} \ No newline at end of file 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/test.csproj b/test/test.csproj index 6379dff..25ecdac 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -10,9 +10,16 @@ + + + + + + - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,9 +31,8 @@ - - - + + From e4d51d0b663dee3110aa111f10e25306b05db281 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:42:48 -0300 Subject: [PATCH 025/137] test: ajusta testes do automapper --- app/Services/Mapper.cs | 3 +++ test/AutoMapperConfigTest.cs | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 421ff67..6bd0e07 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -12,6 +12,9 @@ public class AutoMapperConfig : Profile { public AutoMapperConfig() { + CreateMap() + .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); + CreateMap(); CreateMap() 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 { From 90eccacf9e42121493fa19e14788b389bfefae76 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:45:07 -0300 Subject: [PATCH 026/137] fix: arruma a referencia das configuracoes no dbContext --- app/DI/ServicesConfig.cs | 3 ++- app/Entidades/AppDbContext.cs | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index 183c77b..1b4541a 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,5 +1,6 @@ using app.Entidades; using app.Services; +using Microsoft.EntityFrameworkCore; using app.Services.Interfaces; namespace app.DI @@ -8,7 +9,7 @@ public static class ServicesConfig { public static void AddConfigServices(this IServiceCollection services, IConfiguration configuration) { - services.AddDbContext(); + services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); services.AddScoped(); services.AddScoped(); } diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 29e7f80..8b07cc9 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -4,19 +4,13 @@ namespace app.Entidades { public class AppDbContext : DbContext { - private readonly IConfiguration configuration; public DbSet Usuario { get; set; } public DbSet RedefinicaoSenha { get; set; } public DbSet Empresa { get; set; } - public AppDbContext (IConfiguration configuration) - { - this.configuration = configuration; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql")); + public AppDbContext (DbContextOptions options) : base (options) + { } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -24,6 +18,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .Property(u => u.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasIndex(u => u.Email) + .IsUnique(); modelBuilder.Entity() .Property(r => r.Id).ValueGeneratedOnAdd(); @@ -33,6 +31,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .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) From 76110b5b7a615454397fcadba9789465f3cdd738 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:46:16 -0300 Subject: [PATCH 027/137] test: ajusta DominioControlerTest --- test/DominioControllerTest.cs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/DominioControllerTest.cs b/test/DominioControllerTest.cs index eb43c3d..d505802 100644 --- a/test/DominioControllerTest.cs +++ b/test/DominioControllerTest.cs @@ -1,23 +1,24 @@ -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; namespace test { - public class DominioControllerTest + public class DominioControllerTest : TestBed, IDisposable { + DominioController dominioController; + + public DominioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dominioController = fixture.GetService(testOutputHelper)!; + } + [Fact] public void ObterLista_QuandoMetodoForChamado_DeveRetornarListaDeUFs() { - Mock usuarioFederativaRepositorioMock = new(); - - var controller = new DominioController(usuarioFederativaRepositorioMock.Object); - - var resultado = controller.ObterLista(); - - usuarioFederativaRepositorioMock.Verify(repo => repo.ObterDominio(), Times.Once); + var resultado = dominioController.ObterLista(); Assert.IsType(resultado); } } From ff57242c87e3ea55bf69b1c5e140b4316408b7d1 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:49:48 -0300 Subject: [PATCH 028/137] test: ajusta os configTest --- test/RepositorioConfigTest.cs | 4 ++-- test/ServicesConfigTest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 From 624dd884677cb36efc14052c6ec12f0f306840d8 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:53:08 -0300 Subject: [PATCH 029/137] test: ajusta testes referentes a Usuario --- .../Interfaces/IUsuarioRepositorio.cs | 2 +- app/Repositorios/UsuarioRepositorio.cs | 3 +- test/Stub/RedefinicaoSenhaStub.cs | 6 +- test/Stub/UsuarioStub.cs | 26 ++++-- test/UsuarioControllerTest.cs | 34 ++++---- test/UsuarioServiceTest.cs | 82 +++++++++++-------- 6 files changed, 90 insertions(+), 63 deletions(-) diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 07ac20e..2ef07be 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -1,5 +1,5 @@ using api.Usuarios; -using api.Senhas; +using app.Entidades; namespace app.Repositorios.Interfaces { diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index abce43e..d8d74da 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -5,6 +5,7 @@ using app.Repositorios.Interfaces; using Microsoft.EntityFrameworkCore; using AutoMapper; +using Npgsql; namespace app.Repositorios { @@ -36,7 +37,7 @@ public void CadastrarUsuarioDnit(UsuarioDnit usuario) UfLotacao = usuario.UfLotacao }; - dbContext.Add(novoUsuario); + dbContext.Add(novoUsuario); } public UsuarioModel? TrocarSenha(string email, string senha) 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..326f264 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -1,4 +1,5 @@ -using dominio; +using api.Usuarios; +using api; namespace test.Stub { @@ -11,7 +12,7 @@ public UsuarioDTO RetornarUsuarioDnitDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UF.DF }; } @@ -33,7 +34,18 @@ public UsuarioDnit RetornarUsuarioDnit() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UF.DF + }; + } + + public UsuarioDTO RetornarUsuarioSenhaErrada() + { + return new UsuarioDTO + { + Email = "usuarioteste@gmail.com", + Senha = "senha1234", + Nome = "Usuario Dnit", + UfLotacao = UF.DF }; } @@ -48,9 +60,9 @@ public UsuarioTerceiro RetornarUsuarioTerceiro() }; } - public Usuario RetornarUsuarioValidoLogin() + public UsuarioModel RetornarUsuarioValidoLogin() { - return new Usuario + return new UsuarioModel { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", @@ -58,9 +70,9 @@ public Usuario RetornarUsuarioValidoLogin() }; } - public Usuario RetornarUsuarioInvalidoLogin() + public UsuarioModel RetornarUsuarioInvalidoLogin() { - return new Usuario + return new UsuarioModel { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J68", diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 1ebed00..8b3e885 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -1,12 +1,14 @@ using app.Controllers; -using dominio; +using api.Usuarios; +using api.Senhas; using Microsoft.AspNetCore.Mvc; using Moq; -using service.Interfaces; +using app.Services.Interfaces; using System; using System.Collections.Generic; using test.Stub; using Xunit; +using System.Threading.Tasks; namespace test { @@ -66,7 +68,7 @@ public void Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + public async void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() { UsuarioStub usuarioStub = new(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -75,7 +77,7 @@ public void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated( var controller = new UsuarioController(usuarioServiceMock.Object); - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); var objeto = Assert.IsType(resultado); @@ -84,7 +86,7 @@ public void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated( } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() + public async void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() { UsuarioStub usuarioStub = new(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -96,14 +98,14 @@ public void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() var controller = new UsuarioController(usuarioServiceMock.Object); - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); var objeto = Assert.IsType(resultado); } [Fact] - public void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() + public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() { UsuarioStub usuarioStub = new(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -115,7 +117,7 @@ public void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() var controller = new UsuarioController(usuarioServiceMock.Object); - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); var objeto = Assert.IsType(resultado); @@ -182,7 +184,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern } [Fact] - public void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() + public async void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() { UsuarioStub usuarioStub = new(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -191,14 +193,14 @@ public void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() var controller = new UsuarioController(usuarioServiceMock.Object); - 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 usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -208,14 +210,14 @@ public void RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() var controller = new UsuarioController(usuarioServiceMock.Object); - 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(); @@ -224,14 +226,14 @@ public void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() var controller = new UsuarioController(usuarioServiceMock.Object); - 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(); @@ -241,7 +243,7 @@ public void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() var controller = new UsuarioController(usuarioServiceMock.Object); - 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/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 3860d23..9f07edc 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -1,21 +1,33 @@ 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; + namespace test { - public class UsuarioServiceTest + public class UsuarioServiceTest : TestBed, IDisposable { + + AppDbContext dbContext; + + public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dbContext = fixture.GetService(testOutputHelper)!; + } + [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrarUsuarioDnitComSenhaEncriptografada() + public async void CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrarUsuarioDnitComSenhaEncriptografada() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); @@ -29,9 +41,9 @@ public void CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrarUsuari mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); + await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); @@ -53,7 +65,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); @@ -63,7 +75,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_DeveLancarExececaoFalandoQueEmailJaExiste() + public async void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_DeveLancarExececaoFalandoQueEmailJaExiste() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); @@ -76,15 +88,15 @@ public void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_DeveLanc 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); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - Action cadastrarUsuario = () => usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); + Action cadastrarUsuario = async () => await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); Exception exception = Assert.Throws(cadastrarUsuario); - Assert.Equal("Email j cadastrado.", exception.Message); + Assert.Equal("Email já cadastrado.", exception.Message); } [Fact] @@ -101,15 +113,15 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroJaExistenteForPassado_ 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.")); + 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); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); Action cadastrarUsuario = () => usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); Exception exception = Assert.Throws(cadastrarUsuario); - Assert.Equal("Email j cadastrado.", exception.Message); + Assert.Equal("Email j� cadastrado.", exception.Message); } [Fact] @@ -117,7 +129,7 @@ public void ValidaLogin_QuandoUsuarioCorretoForPassado_DeveRealizarLogin() { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); + UsuarioModel usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -126,7 +138,7 @@ public void ValidaLogin_QuandoUsuarioCorretoForPassado_DeveRealizarLogin() usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioValidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); Assert.True(usuarioService.ValidaLogin(usuarioDnitDTO)); } @@ -136,7 +148,7 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); + UsuarioModel usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -145,7 +157,7 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioInvalidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); @@ -157,7 +169,7 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); + UsuarioModel usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -166,7 +178,7 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); @@ -190,7 +202,7 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); usuarioService.RecuperarSenha(usuarioDnitDTO); @@ -198,7 +210,7 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe } [Fact] - public void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() + public async void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -214,9 +226,9 @@ public void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() 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); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - Action validarLogin = () => usuarioService.RecuperarSenha(usuarioDnitDTO); + Action validarLogin = async () => await usuarioService.RecuperarSenha(usuarioDnitDTO); Assert.Throws(validarLogin); } @@ -230,7 +242,7 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - RedefinicaoSenha redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); + RedefinicaoSenhaModel redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -238,14 +250,14 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() Mock configuration = new(); 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); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); @@ -261,7 +273,7 @@ public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - RedefinicaoSenha redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); + RedefinicaoSenhaModel redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -269,14 +281,14 @@ public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() Mock configuration = new(); 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); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); Action trocarSenha = () => usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); From 568c4216ccb7ffd101924e64045276cbbcddb308 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 17 Oct 2023 18:54:23 -0300 Subject: [PATCH 030/137] test: ajusta testes referentes a UF --- .../IUnidadeFederativaRepositorio.cs | 1 - .../UnidadeFederativaRepositorio.cs | 2 +- test/UnidadeFederativaRepositorioTest.cs | 69 ++++------ test/UsuarioRepositorioTest.cs | 128 ++++++++---------- 4 files changed, 90 insertions(+), 110 deletions(-) diff --git a/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs index 5ff6219..24dcfaf 100644 --- a/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs +++ b/app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs @@ -1,5 +1,4 @@ using api; -using System.Collections.Generic; namespace app.Repositorios.Interfaces { diff --git a/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs index 46bc4d3..c6afd27 100644 --- a/app/Repositorios/UnidadeFederativaRepositorio.cs +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -16,7 +16,7 @@ public UnidadeFederativaRepositorio(IMapper mapper) public IEnumerable ObterDominio() { - return Enum.GetValues().Select(uf => mapper.Map(uf)).OrderBy(uf => uf.Sigla);; + return Enum.GetValues().Select(uf => mapper.Map(uf)).OrderBy(uf => uf.Sigla); } } } 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/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 3dabadc..0ca7507 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -1,58 +1,37 @@ -using Xunit; -using repositorio; -using repositorio.Interfaces; -using dominio; -using Microsoft.Data.Sqlite; -using Dapper; +using app.Repositorios; +using app.Repositorios.Interfaces; +using api.Usuarios; using test.Stub; -using System; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using System.Linq; +using AutoMapper; namespace test { - public class UsuarioRepositorioTest : IDisposable + public class UsuarioRepositorioTest : TestBed, IDisposable { IUsuarioRepositorio repositorio; - SqliteConnection conexao; + AppDbContext dbContext; + IMapper mapper; - 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)!; + mapper = fixture.GetService(testOutputHelper)!; } [Fact] - public void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() + public async void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario("usuarioteste@gmail.com"); @@ -62,38 +41,36 @@ public void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() + public async void CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); 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}';"; + var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault(); + UsuarioDTO usuarioObtidoDTO = mapper.Map(usuarioObtido); - UsuarioDnit? usuarioObtido = conexao.QueryFirst(sql); - - 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.Email, usuarioObtidoDTO.Email); + Assert.Equal(usuarioDNIT.Senha, usuarioObtidoDTO.Senha); + Assert.Equal(usuarioDNIT.Nome, usuarioObtidoDTO.Nome); + Assert.Equal(usuarioDNIT.UfLotacao, usuarioObtidoDTO.UfLotacao); } [Fact] - public void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() + public async void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() { UsuarioStub usuarioStub = new(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await dbContext.SaveChangesAsync(); string novaSenha = "NovaSenha"; repositorio.TrocarSenha(usuarioDNIT.Email, novaSenha); + await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); @@ -101,7 +78,7 @@ public void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() } [Fact] - public void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCorrespondente() + public async void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCorrespondente() { UsuarioStub usuarioStub = new(); RedefinicaoSenhaStub redefinicaoSenhaStub = new(); @@ -109,9 +86,12 @@ public void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCor var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); 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,7 +99,7 @@ public void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEmailCor } [Fact] - public void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBanco() + public async void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBanco() { UsuarioStub usuarioStub = new(); RedefinicaoSenhaStub redefinicaoSenhaStub = new(); @@ -127,10 +107,15 @@ public void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBa var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); 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 +124,37 @@ public void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUuidDoBa [Fact] - public void CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() + public async void CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() { UsuarioStub usuarioStub = new(); 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}';"; + repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); + await dbContext.SaveChangesAsync(); - UsuarioTerceiro? usuarioObtido = conexao.QueryFirst(sql); + var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email); + UsuarioDTO usuarioObtidoDTO = mapper.Map(usuarioObtido); - 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.Email, usuarioObtidoDTO.Email); + Assert.Equal(usuarioTerceiro.Senha, usuarioObtidoDTO.Senha); + Assert.Equal(usuarioTerceiro.Nome, usuarioObtidoDTO.Nome); + Assert.Equal(usuarioTerceiro.CNPJ, usuarioObtidoDTO.CNPJ); } + public void Dispose() { - conexao.Close(); - conexao.Dispose(); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Empresa); + dbContext.SaveChanges(); } } } From a792e12c8ae4675abfd29a229bed7cd6bc986ae8 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Tue, 17 Oct 2023 20:51:00 -0300 Subject: [PATCH 031/137] feat: adiciona perfil #64 (https://github.com/fga-eps-mds/2023.2-Dnit-DOC/issues/63) --- .editorconfig | 4 + UsuarioService.sln | 11 +- api/Enums.cs | 27 +++ api/api.csproj | 4 + app/Entidades/AppDbContext.cs | 13 + app/Entidades/Perfil.cs | 20 ++ app/Entidades/PerfilPermissao.cs | 18 ++ app/Entidades/Usuario.cs | 3 + .../20231017232136_Perfil.Designer.cs | 224 ++++++++++++++++++ app/Migrations/20231017232136_Perfil.cs | 113 +++++++++ app/Migrations/AppDbContextModelSnapshot.cs | 76 ++++++ app/app.csproj | 1 + 12 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 .editorconfig create mode 100644 app/Entidades/Perfil.cs create mode 100644 app/Entidades/PerfilPermissao.cs create mode 100644 app/Migrations/20231017232136_Perfil.Designer.cs create mode 100644 app/Migrations/20231017232136_Perfil.cs 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/UsuarioService.sln b/UsuarioService.sln index ad9d17a..8990c74 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -3,11 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "api", "api\api.csproj", "{9B9F5E33-2CDA-4F75-8338-801798AD293E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api", "api\api.csproj", "{9B9F5E33-2CDA-4F75-8338-801798AD293E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "app", "app\app.csproj", "{08942609-AAE1-4D23-B71D-E39D1A7CF566}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "app", "app\app.csproj", "{08942609-AAE1-4D23-B71D-E39D1A7CF566}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{1D4F20EE-9463-4E06-805F-F4CADF503164}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test", "test\test.csproj", "{1D4F20EE-9463-4E06-805F-F4CADF503164}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8B004C85-9684-409C-B8C8-01BD3A8F6DFA}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/api/Enums.cs b/api/Enums.cs index 90ae429..6ac7deb 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -59,4 +59,31 @@ public enum UF [Description("Distrito Federal")] DF } + + public enum Permissao + { + [Description("Sem permissões")] + None = 0, + [Description("Cadastrar Empresa")] + CadastrarEmpresa = 1, + [Description("Editar Empresa")] + EditarEmpresa = 2, + [Description("Remover Empresa")] + RemoverEmpresa = 3, + + [Description("Cadastrar Escola")] + CadastrarEscola = 4, + [Description("Editar Escola")] + EditarEscola = 5, + [Description("Remover Escola")] + RemoverEscola = 6, + + [Description("Cadastrar Perfil")] + CadastrarPerfil = 7, + [Description("Editar Perfil")] + EditarPerfil = 8, + [Description("Remover Perfil")] + RemoverPerfil = 9, + + } } \ No newline at end of file diff --git a/api/api.csproj b/api/api.csproj index 16e62dd..c91ae47 100644 --- a/api/api.csproj +++ b/api/api.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 8b07cc9..65851f9 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -9,6 +9,9 @@ public class AppDbContext : DbContext 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) { } @@ -44,6 +47,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) em.Property("EmpresasCnpj").HasColumnName("CnpjEmpresa"); em.ToTable("UsuarioEmpresa"); }); + + 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); } } } \ No newline at end of file diff --git a/app/Entidades/Perfil.cs b/app/Entidades/Perfil.cs new file mode 100644 index 0000000..7328961 --- /dev/null +++ b/app/Entidades/Perfil.cs @@ -0,0 +1,20 @@ +using api; +using System.ComponentModel.DataAnnotations; + +namespace app.Entidades +{ + public class Perfil + { + [Key] + public Guid Id { get; set; } + + [Required, MaxLength(200)] + public string Nome { get; set; } + + public List? PerfilPermissoes { get; set; } + + public IEnumerable? Permissoes => 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/Usuario.cs b/app/Entidades/Usuario.cs index 366349d..53553ea 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -25,5 +25,8 @@ public class Usuario public List RedefinicaoSenha { get; set; } public List Empresas { get; set; } + + public Guid PerfilId { get; set; } + public Perfil Perfil { get; set; } } } \ No newline at end of file diff --git a/app/Migrations/20231017232136_Perfil.Designer.cs b/app/Migrations/20231017232136_Perfil.Designer.cs new file mode 100644 index 0000000..3344094 --- /dev/null +++ b/app/Migrations/20231017232136_Perfil.Designer.cs @@ -0,0 +1,224 @@ +// +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("20231017232136_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) + .IsRequired(); + + 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/20231017232136_Perfil.cs b/app/Migrations/20231017232136_Perfil.cs new file mode 100644 index 0000000..1db63c8 --- /dev/null +++ b/app/Migrations/20231017232136_Perfil.cs @@ -0,0 +1,113 @@ +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: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 3790dba..39c8b27 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -1,4 +1,5 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -52,9 +53,47 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -96,6 +135,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(150) .HasColumnType("character varying(150)"); + b.Property("PerfilId") + .HasColumnType("uuid"); + b.Property("Senha") .IsRequired() .HasMaxLength(200) @@ -106,6 +148,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PerfilId"); + b.ToTable("Usuario"); }); @@ -124,6 +171,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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") @@ -135,6 +193,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Usuario"); }); + modelBuilder.Entity("app.Entidades.Usuario", b => + { + b.HasOne("app.Entidades.Perfil", "Perfil") + .WithMany("Usuarios") + .HasForeignKey("PerfilId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Perfil"); + }); + + modelBuilder.Entity("app.Entidades.Perfil", b => + { + b.Navigation("PerfilPermissoes"); + + b.Navigation("Usuarios"); + }); + modelBuilder.Entity("app.Entidades.Usuario", b => { b.Navigation("RedefinicaoSenha"); diff --git a/app/app.csproj b/app/app.csproj index bc551b2..42afceb 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -4,6 +4,7 @@ net6.0 enable enable + 458d8e9b-3d94-4e66-8b4d-cd89daee7157 From 30465b62244f8b1f61abfbedadd677f52cce8b53 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:07:27 -0300 Subject: [PATCH 032/137] ci: adiciona testagem no linux --- .github/workflows/build.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 793a309..ba1f0f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,15 +7,15 @@ on: pull_request: types: [opened, synchronize, reopened] jobs: - build: - name: Build and analyze + ci-windows: + name: Build, test and analyze Windows runs-on: windows-latest steps: - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: 11 - distribution: 'zulu' + java-version: 17 + distribution: 'temurin' - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis @@ -62,3 +62,18 @@ jobs: 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 From 1d6e3cb88a2e76e20c100e9cfc5ce0dda078e8b3 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:08:54 -0300 Subject: [PATCH 033/137] ci: renomeia actions --- .github/workflows/build.yml | 79 --------------------- .github/workflows/ci.yml | 134 +++++++++++++++++++++-------------- .github/workflows/deploy.yml | 55 ++++++++++++++ 3 files changed, 134 insertions(+), 134 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index ba1f0f1..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: SonarCloud -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/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/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0fa1701 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,55 @@ +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 From 2b386481d3bdfb5b841b327a5a9e3716875d0fe4 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Tue, 17 Oct 2023 22:18:33 -0300 Subject: [PATCH 034/137] fix: padroniza string de conexao do banco de dados --- app/appsettings.Development.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 8c65753..94087ca 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,12 +1,12 @@ { - "ConnectionStrings": { - "PostgreSql": "Host=localhost;Port=5433;Database=usuarioservice;Username=postgres;Password=1234" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" + "ConnectionStrings": { + "PostgreSql": "Host=localhost;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" } \ No newline at end of file From 6e60c3c04273644293d5db42ea5dda0491ac108f Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 18 Oct 2023 23:07:09 -0300 Subject: [PATCH 035/137] feat: adiciona base da autenticacao MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yudi Yamane Co-authored-by: Cássio Reis --- api/Enums.cs | 5 +- app/Controllers/UsuarioController.cs | 73 +++++++++++++++++++++++++--- app/DI/ServicesConfig.cs | 26 ++++++++++ app/Program.cs | 3 +- app/Properties/launchSettings.json | 14 +++--- app/app.csproj | 1 + app/appsettings.Development.json | 11 ++++- 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 6ac7deb..39826d8 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -65,9 +65,9 @@ public enum Permissao [Description("Sem permissões")] None = 0, [Description("Cadastrar Empresa")] - CadastrarEmpresa = 1, + EmpresaCadastrar = 1, [Description("Editar Empresa")] - EditarEmpresa = 2, + EmpresaEditar = 2, [Description("Remover Empresa")] RemoverEmpresa = 3, @@ -84,6 +84,5 @@ public enum Permissao EditarPerfil = 8, [Description("Remover Perfil")] RemoverPerfil = 9, - } } \ No newline at end of file diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 146489f..c4bba3f 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -2,6 +2,11 @@ using api.Senhas; using Microsoft.AspNetCore.Mvc; using app.Services.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.AspNetCore.Authorization; namespace app.Controllers { @@ -10,12 +15,61 @@ namespace app.Controllers public class UsuarioController : ControllerBase { private readonly IUsuarioService usuarioService; + private readonly IConfiguration configuration; - public UsuarioController(IUsuarioService usuarioService) + public UsuarioController( + IUsuarioService usuarioService, + IConfiguration configuration + ) { this.usuarioService = usuarioService; + this.configuration = configuration; } - + + [HttpGet("login/teste")] + public IResult LoginTeste(string username, string password) + { + if (username != "joydip" || password != "joydip123") + { + return Results.Unauthorized(); + } + + var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); + + var issuer = configuracaoAutenticaco["Issuer"]; + var audience = configuracaoAutenticaco["Audience"]; + var key = Encoding.ASCII.GetBytes(configuracaoAutenticaco["Key"]!); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim("Id", Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Sub, username), + new Claim(JwtRegisteredClaimNames.Email, "email@gmail.com"), + new Claim(JwtRegisteredClaimNames.Jti, + Guid.NewGuid().ToString()) + }), + Expires = DateTime.UtcNow.AddMinutes(int.Parse(configuracaoAutenticaco["ExpireMinutes"]!)), + Issuer = issuer, + Audience = audience, + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature), + }; + + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + var jwtToken = tokenHandler.WriteToken(token); + var stringToken = tokenHandler.WriteToken(token); + return Results.Ok(stringToken); + } + + [HttpGet("autenticacao/teste")] + [Authorize] + public int TesteRotaAutenticada() + { + return 42; + } + [HttpPost("login")] public IActionResult Logar([FromBody] UsuarioDTO usuarioDTO) { @@ -24,10 +78,12 @@ public IActionResult Logar([FromBody] UsuarioDTO usuarioDTO) bool verificar = usuarioService.ValidaLogin(usuarioDTO); return Ok(); } - catch(UnauthorizedAccessException){ + catch (UnauthorizedAccessException) + { return Unauthorized(); } - catch(KeyNotFoundException){ + catch (KeyNotFoundException) + { return NotFound(); } } @@ -43,7 +99,8 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua } catch (Npgsql.PostgresException ex) { - if (ex.SqlState == "23505") { + if (ex.SqlState == "23505") + { return Conflict("Usu�rio j� cadastrado."); } @@ -79,7 +136,7 @@ public async Task RecuperarSenhaAsync([FromBody] UsuarioDTO usuar await usuarioService.RecuperarSenha(usuarioDto); return Ok(); } - catch(KeyNotFoundException) + catch (KeyNotFoundException) { return NotFound(); } @@ -91,10 +148,10 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha try { await usuarioService.TrocaSenha(redefinirSenhaDto); - + return Ok(); } - catch(KeyNotFoundException) + catch (KeyNotFoundException) { return NotFound(); } diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index 1b4541a..0480aa3 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -2,6 +2,9 @@ using app.Services; using Microsoft.EntityFrameworkCore; using app.Services.Interfaces; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; namespace app.DI { @@ -12,6 +15,29 @@ public static void AddConfigServices(this IServiceCollection services, IConfigur services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); services.AddScoped(); services.AddScoped(); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(o => + { + var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); + o.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = configuracaoAutenticaco["Issuer"], + ValidAudience = configuracaoAutenticaco["Audience"], + IssuerSigningKey = new SymmetricSecurityKey + (Encoding.UTF8.GetBytes(configuracaoAutenticaco["Key"]!)), + ValidateIssuer = bool.Parse(configuracaoAutenticaco["ValidateIssuer"]!), + ValidateAudience = bool.Parse(configuracaoAutenticaco["ValidateAudience"]!), + ValidateLifetime = false, + ValidateIssuerSigningKey = bool.Parse(configuracaoAutenticaco["ValidateIssuerSigningKey"]!) + }; + }); + + services.AddAuthorization(); } } } diff --git a/app/Program.cs b/app/Program.cs index de4eda4..5144a4a 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -53,8 +53,9 @@ app.UseSwaggerUI(); -app.UseHttpsRedirection(); +//app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); 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/app.csproj b/app/app.csproj index 42afceb..0934e26 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -13,6 +13,7 @@ + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 94087ca..3ece8ad 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -8,5 +8,14 @@ "Microsoft.AspNetCore": "Warning" } }, - "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", + "Autenticacao": { + "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": 5 + } } \ No newline at end of file From 6ee5d69976f6726f2afa501047293756a6ba3631 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 18 Oct 2023 23:53:44 -0300 Subject: [PATCH 036/137] fix: arruma perfil usuario opcional --- api/Usuarios/LoginModel.cs | 16 ++ app/Entidades/Usuario.cs | 4 +- ...19025328_PerfilUsuarioOpcional.Designer.cs | 223 ++++++++++++++++++ .../20231019025328_PerfilUsuarioOpcional.cs | 37 +++ app/Migrations/AppDbContextModelSnapshot.cs | 5 +- app/Services/AutenticacaoService.cs | 51 ++++ test/UsuarioControllerTest.cs | 26 +- 7 files changed, 344 insertions(+), 18 deletions(-) create mode 100644 api/Usuarios/LoginModel.cs create mode 100644 app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs create mode 100644 app/Migrations/20231019025328_PerfilUsuarioOpcional.cs create mode 100644 app/Services/AutenticacaoService.cs diff --git a/api/Usuarios/LoginModel.cs b/api/Usuarios/LoginModel.cs new file mode 100644 index 0000000..e49ab41 --- /dev/null +++ b/api/Usuarios/LoginModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 53553ea..c6c6b5a 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -26,7 +26,7 @@ public class Usuario public List Empresas { get; set; } - public Guid PerfilId { get; set; } - public Perfil Perfil { get; set; } + public Guid? PerfilId { get; set; } + public Perfil? Perfil { get; set; } } } \ No newline at end of file diff --git a/app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs b/app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs new file mode 100644 index 0000000..506f97f --- /dev/null +++ b/app/Migrations/20231019025328_PerfilUsuarioOpcional.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("20231019025328_PerfilUsuarioOpcional")] + partial class PerfilUsuarioOpcional + { + /// + 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/20231019025328_PerfilUsuarioOpcional.cs b/app/Migrations/20231019025328_PerfilUsuarioOpcional.cs new file mode 100644 index 0000000..cfdbac4 --- /dev/null +++ b/app/Migrations/20231019025328_PerfilUsuarioOpcional.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class PerfilUsuarioOpcional : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PerfilId", + table: "Usuario", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PerfilId", + table: "Usuario", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + } + } +} diff --git a/app/Migrations/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 39c8b27..dfc0f38 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -135,7 +135,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(150) .HasColumnType("character varying(150)"); - b.Property("PerfilId") + b.Property("PerfilId") .HasColumnType("uuid"); b.Property("Senha") @@ -198,8 +198,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("app.Entidades.Perfil", "Perfil") .WithMany("Usuarios") .HasForeignKey("PerfilId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); + .OnDelete(DeleteBehavior.Restrict); b.Navigation("Perfil"); }); diff --git a/app/Services/AutenticacaoService.cs b/app/Services/AutenticacaoService.cs new file mode 100644 index 0000000..ff29023 --- /dev/null +++ b/app/Services/AutenticacaoService.cs @@ -0,0 +1,51 @@ +using app.Entidades; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace app.Services +{ + public class AutenticacaoService + { + private readonly IConfiguration configuration; + + public AutenticacaoService(IConfiguration configuration) + { + this.configuration = configuration; + } + + public (string Token, DateTime ExpiraEm) GerarToken(Usuario usuario) + { + var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); + + var issuer = configuracaoAutenticaco["Issuer"]; + var audience = configuracaoAutenticaco["Audience"]; + var key = Encoding.ASCII.GetBytes(configuracaoAutenticaco["Key"]!); + var expiraEm = DateTime.UtcNow.AddMinutes(int.Parse(configuracaoAutenticaco["ExpireMinutes"]!)); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim("Id", Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Sub, usuario.Nome), + new Claim(JwtRegisteredClaimNames.Email, usuario.Email), + new Claim(JwtRegisteredClaimNames.Jti, + Guid.NewGuid().ToString()) + }), + Expires = expiraEm, + Issuer = issuer, + Audience = audience, + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature), + }; + + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + var jwtToken = tokenHandler.WriteToken(token); + var stringToken = tokenHandler.WriteToken(token); + return (stringToken, expiraEm); + } + } +} diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 8b3e885..f0222f2 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -25,7 +25,7 @@ public void Logar_QuandoLoginForValidado_DeveRetornarOk() Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.Logar(usuarioDTO); @@ -42,7 +42,7 @@ public void Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() Mock usuarioServiceMock = new(); usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new UnauthorizedAccessException()); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.Logar(usuarioDTO); @@ -59,7 +59,7 @@ public void Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() Mock usuarioServiceMock = new(); usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new KeyNotFoundException()); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.Logar(usuarioDTO); @@ -75,7 +75,7 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCr Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); @@ -96,7 +96,7 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConfli usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); @@ -115,7 +115,7 @@ public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInte usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); @@ -133,7 +133,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioForCadastrado_DeveRetornarCrea Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = controller.CadastrarUsuarioTerceiro(usuarioDTO); @@ -154,7 +154,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); @@ -173,7 +173,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern 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); @@ -191,7 +191,7 @@ public async void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = await controller.RecuperarSenhaAsync(usuarioDTO); @@ -208,7 +208,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsy 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 = await controller.RecuperarSenhaAsync(usuarioDTO); @@ -224,7 +224,7 @@ public async void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() Mock usuarioServiceMock = new(); - var controller = new UsuarioController(usuarioServiceMock.Object); + var controller = new UsuarioController(usuarioServiceMock.Object, null); var resultado = await controller.RedefinirSenhaAsync(redefinicaoSenhaDTO); @@ -241,7 +241,7 @@ public async 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 = await controller.RedefinirSenhaAsync(redefinicaoSenhaDTO); From 60d466f57b6daafeacf03252c33817d50a2c3fe5 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 18 Oct 2023 23:59:06 -0300 Subject: [PATCH 037/137] fix: arruma migracoes --- .../20231017232136_Perfil.Designer.cs | 224 ------------------ .../20231019025328_PerfilUsuarioOpcional.cs | 37 --- ...r.cs => 20231019025721_Perfil.Designer.cs} | 4 +- ...136_Perfil.cs => 20231019025721_Perfil.cs} | 3 +- 4 files changed, 3 insertions(+), 265 deletions(-) delete mode 100644 app/Migrations/20231017232136_Perfil.Designer.cs delete mode 100644 app/Migrations/20231019025328_PerfilUsuarioOpcional.cs rename app/Migrations/{20231019025328_PerfilUsuarioOpcional.Designer.cs => 20231019025721_Perfil.Designer.cs} (98%) rename app/Migrations/{20231017232136_Perfil.cs => 20231019025721_Perfil.cs} (96%) diff --git a/app/Migrations/20231017232136_Perfil.Designer.cs b/app/Migrations/20231017232136_Perfil.Designer.cs deleted file mode 100644 index 3344094..0000000 --- a/app/Migrations/20231017232136_Perfil.Designer.cs +++ /dev/null @@ -1,224 +0,0 @@ -// -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("20231017232136_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) - .IsRequired(); - - 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/20231019025328_PerfilUsuarioOpcional.cs b/app/Migrations/20231019025328_PerfilUsuarioOpcional.cs deleted file mode 100644 index cfdbac4..0000000 --- a/app/Migrations/20231019025328_PerfilUsuarioOpcional.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace app.Migrations -{ - /// - public partial class PerfilUsuarioOpcional : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PerfilId", - table: "Usuario", - type: "uuid", - nullable: true, - oldClrType: typeof(Guid), - oldType: "uuid"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PerfilId", - table: "Usuario", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), - oldClrType: typeof(Guid), - oldType: "uuid", - oldNullable: true); - } - } -} diff --git a/app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs b/app/Migrations/20231019025721_Perfil.Designer.cs similarity index 98% rename from app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs rename to app/Migrations/20231019025721_Perfil.Designer.cs index 506f97f..3831ca9 100644 --- a/app/Migrations/20231019025328_PerfilUsuarioOpcional.Designer.cs +++ b/app/Migrations/20231019025721_Perfil.Designer.cs @@ -12,8 +12,8 @@ namespace app.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20231019025328_PerfilUsuarioOpcional")] - partial class PerfilUsuarioOpcional + [Migration("20231019025721_Perfil")] + partial class Perfil { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/app/Migrations/20231017232136_Perfil.cs b/app/Migrations/20231019025721_Perfil.cs similarity index 96% rename from app/Migrations/20231017232136_Perfil.cs rename to app/Migrations/20231019025721_Perfil.cs index 1db63c8..4fc3b97 100644 --- a/app/Migrations/20231017232136_Perfil.cs +++ b/app/Migrations/20231019025721_Perfil.cs @@ -15,8 +15,7 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "PerfilId", table: "Usuario", type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + nullable: true); migrationBuilder.CreateTable( name: "Perfis", From bcdc4fe1d192118e9e0182da25c33932e634a0c7 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 00:40:21 -0300 Subject: [PATCH 038/137] feat: adiciona jwt token no login --- api/Enums.cs | 2 + api/Usuarios/LoginModel.cs | 10 +-- app/Controllers/UsuarioController.cs | 83 ++++--------------- app/DI/ServicesConfig.cs | 7 ++ .../Interfaces/IUsuarioRepositorio.cs | 1 + app/Repositorios/UsuarioRepositorio.cs | 12 +++ app/Services/AutenticacaoService.cs | 14 ++-- app/Services/Interfaces/IUsuarioService.cs | 1 + app/Services/UsuarioService.cs | 27 +++++- test/UsuarioServiceTest.cs | 22 ++--- 10 files changed, 83 insertions(+), 96 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 39826d8..d3f99ec 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Text.Json.Serialization; namespace api { @@ -60,6 +61,7 @@ public enum UF DF } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum Permissao { [Description("Sem permissões")] diff --git a/api/Usuarios/LoginModel.cs b/api/Usuarios/LoginModel.cs index e49ab41..329f0d0 100644 --- a/api/Usuarios/LoginModel.cs +++ b/api/Usuarios/LoginModel.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace api.Usuarios +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; } + public List? Permissoes { get; set; } } } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index c4bba3f..a20543d 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -2,11 +2,7 @@ using api.Senhas; using Microsoft.AspNetCore.Mvc; using app.Services.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; namespace app.Controllers { @@ -15,68 +11,21 @@ namespace app.Controllers public class UsuarioController : ControllerBase { private readonly IUsuarioService usuarioService; - private readonly IConfiguration configuration; public UsuarioController( - IUsuarioService usuarioService, - IConfiguration configuration + IUsuarioService usuarioService ) { this.usuarioService = usuarioService; - this.configuration = configuration; - } - - [HttpGet("login/teste")] - public IResult LoginTeste(string username, string password) - { - if (username != "joydip" || password != "joydip123") - { - return Results.Unauthorized(); - } - - var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); - - var issuer = configuracaoAutenticaco["Issuer"]; - var audience = configuracaoAutenticaco["Audience"]; - var key = Encoding.ASCII.GetBytes(configuracaoAutenticaco["Key"]!); - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(new[] - { - new Claim("Id", Guid.NewGuid().ToString()), - new Claim(JwtRegisteredClaimNames.Sub, username), - new Claim(JwtRegisteredClaimNames.Email, "email@gmail.com"), - new Claim(JwtRegisteredClaimNames.Jti, - Guid.NewGuid().ToString()) - }), - Expires = DateTime.UtcNow.AddMinutes(int.Parse(configuracaoAutenticaco["ExpireMinutes"]!)), - Issuer = issuer, - Audience = audience, - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature), - }; - - var tokenHandler = new JwtSecurityTokenHandler(); - var token = tokenHandler.CreateToken(tokenDescriptor); - var jwtToken = tokenHandler.WriteToken(token); - var stringToken = tokenHandler.WriteToken(token); - return Results.Ok(stringToken); - } - - [HttpGet("autenticacao/teste")] - [Authorize] - public int TesteRotaAutenticada() - { - return 42; } [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) { @@ -97,13 +46,12 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua return StatusCode(201, new NoContentResult()); } - catch (Npgsql.PostgresException ex) + catch (DbUpdateException) + { + return Conflict("Usuário já cadastrado."); + } + catch (Exception) { - if (ex.SqlState == "23505") - { - return Conflict("Usu�rio j� cadastrado."); - } - return StatusCode(500, "Houve um erro interno no servidor."); } } @@ -117,13 +65,12 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) return StatusCode(201, new NoContentResult()); } - catch (Npgsql.PostgresException ex) + catch (DbUpdateException) + { + return Conflict("Usuário já cadastrado."); + } + catch (Exception) { - if (ex.SqlState == "23505") - { - return Conflict("Usu�rio j� cadastrado."); - } - return StatusCode(500, "Houve um erro interno no servidor."); } } diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index 0480aa3..bb80196 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; +using System.Runtime.CompilerServices; namespace app.DI { @@ -14,8 +15,14 @@ public static void AddConfigServices(this IServiceCollection services, IConfigur { services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddAppAuthorization(configuration); + } + + public static void AddAppAuthorization(this IServiceCollection services, IConfiguration configuration) + { services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 2ef07be..ff1f711 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -6,6 +6,7 @@ namespace app.Repositorios.Interfaces public interface IUsuarioRepositorio { public UsuarioModel? ObterUsuario(string email); + Task ObterUsuarioAsync(string email, bool includePerfil = false); public UsuarioModel? TrocarSenha(string senha, string email); public void InserirDadosRecuperacao(string uuid, int idUsuario); public string? ObterEmailRedefinicaoSenha(string uuid); diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index d8d74da..2406491 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -27,6 +27,18 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) return mapper.Map(query); } + public async Task ObterUsuarioAsync(string email, bool includePerfil = false) + { + var query = dbContext.Usuario.AsQueryable(); + + if (includePerfil) + { + query = query.Include(u => u.Perfil).ThenInclude(p => p.PerfilPermissoes); + } + + return await query.FirstOrDefaultAsync(u => u.Email.ToLower() == email.ToLower()); + } + public void CadastrarUsuarioDnit(UsuarioDnit usuario) { var novoUsuario = new Usuario diff --git a/app/Services/AutenticacaoService.cs b/app/Services/AutenticacaoService.cs index ff29023..4154e78 100644 --- a/app/Services/AutenticacaoService.cs +++ b/app/Services/AutenticacaoService.cs @@ -1,9 +1,12 @@ -using app.Entidades; +using api; +using app.Entidades; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; +using System.Linq.Expressions; using System.Security.Claims; using System.Text; +using System.Text.Json; namespace app.Services { @@ -24,17 +27,18 @@ public AutenticacaoService(IConfiguration configuration) var audience = configuracaoAutenticaco["Audience"]; var key = Encoding.ASCII.GetBytes(configuracaoAutenticaco["Key"]!); var expiraEm = DateTime.UtcNow.AddMinutes(int.Parse(configuracaoAutenticaco["ExpireMinutes"]!)); + var temPermissoes = usuario?.Perfil?.Permissoes?.Any() ?? false; var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { - new Claim("Id", Guid.NewGuid().ToString()), + new Claim("id", usuario.Id.ToString()), new Claim(JwtRegisteredClaimNames.Sub, usuario.Nome), new Claim(JwtRegisteredClaimNames.Email, usuario.Email), - new Claim(JwtRegisteredClaimNames.Jti, - Guid.NewGuid().ToString()) - }), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim("permissions", temPermissoes ? string.Join(',', usuario.Perfil!.Permissoes!.Select(p => p.ToString())) : "") + }), Expires = expiraEm, Issuer = issuer, Audience = audience, diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index 793d4a4..ac7925c 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -5,6 +5,7 @@ namespace app.Services.Interfaces { public interface IUsuarioService { + public Task AutenticarUsuarioAsync(string email, string senha); public bool ValidaLogin(UsuarioDTO usuarioDTO); public Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); public Task RecuperarSenha(UsuarioDTO usuarioDto); diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 7e775a9..7c6794f 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -3,11 +3,9 @@ using app.Repositorios.Interfaces; using app.Services.Interfaces; using AutoMapper; -using System.Collections.Generic; -using System; using BCryptNet = BCrypt.Net.BCrypt; -using Microsoft.Extensions.Configuration; using app.Entidades; +using api; namespace app.Services { @@ -19,6 +17,7 @@ public class UsuarioService : IUsuarioService private readonly IEmailService emailService; private readonly IConfiguration configuration; private readonly AppDbContext dbContext; + private readonly AutenticacaoService autenticacaoService; public UsuarioService ( @@ -26,7 +25,8 @@ public UsuarioService IMapper mapper, IEmailService emailService, IConfiguration configuration, - AppDbContext dbContext + AppDbContext dbContext, + AutenticacaoService autenticacaoService ) { this.usuarioRepositorio = usuarioRepositorio; @@ -34,6 +34,7 @@ AppDbContext dbContext this.emailService = emailService; this.configuration = configuration; this.dbContext = dbContext; + this.autenticacaoService = autenticacaoService; } public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) @@ -63,6 +64,24 @@ public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); } + public async Task AutenticarUsuarioAsync(string email, string senha) + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(email, includePerfil: true) + ?? throw new KeyNotFoundException(); + + ValidaSenha(senha, usuario.Senha); + + var (token, expiraEm) = autenticacaoService.GerarToken(usuario); + + return new LoginModel() + { + Token = token, + ExpiraEm = expiraEm, + TokenAtualizacao = "", + Permissoes = usuario.Perfil?.Permissoes?.ToList(), + }; + } + private UsuarioModel? Obter(string email) { UsuarioModel? usuario = usuarioRepositorio.ObterUsuario(email); diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 9f07edc..8379d2e 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -41,7 +41,7 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrar mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); @@ -65,7 +65,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); @@ -90,7 +90,7 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_De mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); 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, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action cadastrarUsuario = async () => await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); @@ -115,7 +115,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroJaExistenteForPassado_ 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, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action cadastrarUsuario = () => usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); @@ -138,7 +138,7 @@ public void ValidaLogin_QuandoUsuarioCorretoForPassado_DeveRealizarLogin() usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioValidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Assert.True(usuarioService.ValidaLogin(usuarioDnitDTO)); } @@ -157,7 +157,7 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioInvalidoLogin); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); @@ -178,7 +178,7 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); @@ -202,7 +202,7 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); usuarioService.RecuperarSenha(usuarioDnitDTO); @@ -226,7 +226,7 @@ public async void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() 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, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action validarLogin = async () => await usuarioService.RecuperarSenha(usuarioDnitDTO); @@ -257,7 +257,7 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(emailRedefinicaoSenha); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); @@ -288,7 +288,7 @@ public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); + IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); Action trocarSenha = () => usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); From f6ab4f12e87f4716117abdf27a397b788d3c9561 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 19 Oct 2023 10:18:13 -0300 Subject: [PATCH 039/137] fix: ajusta testes --- api/Usuarios/UsuarioModel.cs | 8 +----- .../Interfaces/IUsuarioRepositorio.cs | 2 +- app/Repositorios/UsuarioRepositorio.cs | 4 +-- app/Services/Mapper.cs | 6 +++++ app/Services/UsuarioService.cs | 8 +++--- app/appsettings.Development.json | 2 +- test/Stub/UsuarioStub.cs | 9 ++++--- test/UsuarioServiceTest.cs | 25 +++++++++---------- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index 555b878..78f5d13 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace api.Usuarios +namespace api.Usuarios { public class UsuarioModel { diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 2ef07be..5072ee3 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -5,7 +5,7 @@ namespace app.Repositorios.Interfaces { public interface IUsuarioRepositorio { - public UsuarioModel? ObterUsuario(string email); + public Usuario? ObterUsuario(string email); public UsuarioModel? TrocarSenha(string senha, string email); public void InserirDadosRecuperacao(string uuid, int idUsuario); public string? ObterEmailRedefinicaoSenha(string uuid); diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index d8d74da..bc321b7 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -20,11 +20,11 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) this.mapper = mapper; } - public UsuarioModel? ObterUsuario(string email) + public Usuario? ObterUsuario(string email) { var query = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); - return mapper.Map(query); + return query; } public void CadastrarUsuarioDnit(UsuarioDnit usuario) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 6bd0e07..e8c9c3d 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -14,6 +14,12 @@ public AutoMapperConfig() { CreateMap() .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); + + CreateMap(); + + CreateMap() + .ForMember(dto => dto.CNPJ, opt => opt.Ignore()) + .ForMember(dto => dto.UfLotacao, opt => opt.Ignore()); CreateMap(); diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 7e775a9..f795a22 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -63,9 +63,9 @@ public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); } - private UsuarioModel? Obter(string email) + private Usuario? Obter(string email) { - UsuarioModel? usuario = usuarioRepositorio.ObterUsuario(email); + Usuario? usuario = usuarioRepositorio.ObterUsuario(email); if (usuario == null) throw new KeyNotFoundException(); @@ -75,7 +75,7 @@ public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) public bool ValidaLogin(UsuarioDTO usuarioDTO) { - UsuarioModel? usuarioBanco = Obter(usuarioDTO.Email); + Usuario? usuarioBanco = Obter(usuarioDTO.Email); return ValidaSenha(usuarioDTO.Senha, usuarioBanco.Senha); } @@ -108,7 +108,7 @@ public async Task RecuperarSenha(UsuarioDTO usuarioDTO) { var usuarioEntrada = mapper.Map(usuarioDTO); - UsuarioModel usuarioBanco = Obter(usuarioEntrada.Email); + Usuario usuarioBanco = Obter(usuarioEntrada.Email); string UuidAutenticacao = Guid.NewGuid().ToString(); diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 8c65753..68d0c81 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "PostgreSql": "Host=localhost;Port=5433;Database=usuarioservice;Username=postgres;Password=1234" + "PostgreSql": "Host=localhost;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" }, "Logging": { "LogLevel": { diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 326f264..0af825d 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -1,4 +1,5 @@ using api.Usuarios; +using app.Entidades; using api; namespace test.Stub @@ -60,9 +61,9 @@ public UsuarioTerceiro RetornarUsuarioTerceiro() }; } - public UsuarioModel RetornarUsuarioValidoLogin() + public Usuario RetornarUsuarioValidoLogin() { - return new UsuarioModel + return new Usuario { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", @@ -70,9 +71,9 @@ public UsuarioModel RetornarUsuarioValidoLogin() }; } - public UsuarioModel RetornarUsuarioInvalidoLogin() + public Usuario RetornarUsuarioInvalidoLogin() { - return new UsuarioModel + return new Usuario { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J68", diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 9f07edc..c06e6ef 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -12,6 +12,7 @@ using app.Entidades; using Xunit.Abstractions; using Xunit.Microsoft.DependencyInjection.Abstracts; +using System.Threading.Tasks; namespace test @@ -92,11 +93,11 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_De IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - Action cadastrarUsuario = async () => await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); + //var cadastrarUsuario = - Exception exception = Assert.Throws(cadastrarUsuario); + var exception = Assert.ThrowsAsync(async () => await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO())); - Assert.Equal("Email já cadastrado.", exception.Message); + //Assert.Equal("Email já cadastrado.", exception.Message); } [Fact] @@ -129,7 +130,7 @@ public void ValidaLogin_QuandoUsuarioCorretoForPassado_DeveRealizarLogin() { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioModel usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); + Usuario usuarioValidoLogin = usuarioStub.RetornarUsuarioValidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -148,7 +149,7 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioModel usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); + Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -169,7 +170,7 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( { UsuarioStub usuarioStub = new(); UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioModel usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); + Usuario usuarioInvalidoLogin = usuarioStub.RetornarUsuarioInvalidoLogin(); Mock mapper = new(); Mock usuarioRepositorio = new(); @@ -197,10 +198,12 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe Mock emailService = new(); Mock configuration = new(); + var usuarioRetorno = mapper.Object.Map(usuarioDNIT); + 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, dbContext); @@ -228,9 +231,7 @@ public async void RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - Action validarLogin = async () => await usuarioService.RecuperarSenha(usuarioDnitDTO); - - Assert.Throws(validarLogin); + var exception = Assert.ThrowsAsync(async () => await usuarioService.RecuperarSenha(usuarioDnitDTO)); } [Fact] @@ -290,9 +291,7 @@ public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext); - Action trocarSenha = () => usuarioService.TrocaSenha(redefinicaoSenhaStub.ObterRedefinicaoSenhaDTO()); - - Assert.Throws(trocarSenha); + Assert.ThrowsAsync(async () => await usuarioService.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); From fecd70c3dae26eb34c4f0faff5733cc42ae93d50 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 19 Oct 2023 11:12:42 -0300 Subject: [PATCH 040/137] fix: arruma detalhes do mapper e stub nos testes --- app/Services/Mapper.cs | 4 +++- test/Stub/UsuarioStub.cs | 11 +++++++++++ test/UsuarioServiceTest.cs | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index e8c9c3d..8a5f57e 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -15,7 +15,9 @@ public AutoMapperConfig() CreateMap() .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); - CreateMap(); + CreateMap() + .ForMember(u => u.RedefinicaoSenha, opt => opt.Ignore()) + .ForMember(u => u.Empresas, opt => opt.Ignore()); CreateMap() .ForMember(dto => dto.CNPJ, opt => opt.Ignore()) diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 0af825d..54a0926 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -39,6 +39,17 @@ public UsuarioDnit RetornarUsuarioDnit() }; } + public Usuario RetornarUsuarioDnitBanco() + { + return new Usuario + { + Email = "usuarioteste@gmail.com", + Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", + Nome = "Usuario Dnit", + UfLotacao = UF.DF + }; + } + public UsuarioDTO RetornarUsuarioSenhaErrada() { return new UsuarioDTO diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index c06e6ef..a6c8b4b 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -198,7 +198,7 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe Mock emailService = new(); Mock configuration = new(); - var usuarioRetorno = mapper.Object.Map(usuarioDNIT); + var usuarioRetorno = usuarioStub.RetornarUsuarioDnitBanco(); mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); From dbe47e65f74b924800da5adb10c42c186a1391a4 Mon Sep 17 00:00:00 2001 From: Thiago Sampaio de Paiva Date: Thu, 19 Oct 2023 15:20:00 -0300 Subject: [PATCH 041/137] refactor: cria biblioteca de autenticacao --- UsuarioService.sln | 6 ++++ app/DI/ServicesConfig.cs | 36 +++---------------- app/Services/AutenticacaoService.cs | 55 ----------------------------- app/Services/UsuarioService.cs | 12 +++++-- app/app.csproj | 1 + app/appsettings.Development.json | 2 +- auth/AuthConfig.cs | 18 ++++++++++ auth/AuthService.cs | 54 ++++++++++++++++++++++++++++ auth/AuthStartup.cs | 45 +++++++++++++++++++++++ auth/AuthUserModel.cs | 9 +++++ auth/auth.csproj | 16 +++++++++ 11 files changed, 163 insertions(+), 91 deletions(-) delete mode 100644 app/Services/AutenticacaoService.cs create mode 100644 auth/AuthConfig.cs create mode 100644 auth/AuthService.cs create mode 100644 auth/AuthStartup.cs create mode 100644 auth/AuthUserModel.cs create mode 100644 auth/auth.csproj diff --git a/UsuarioService.sln b/UsuarioService.sln index 8990c74..ae3bfec 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "auth", "auth\auth.csproj", "{1C2F7ED1-31B7-4274-BD2A-388700259656}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,6 +34,10 @@ Global {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/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index bb80196..7f251d3 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,11 +1,8 @@ using app.Entidades; using app.Services; -using Microsoft.EntityFrameworkCore; using app.Services.Interfaces; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.IdentityModel.Tokens; -using System.Text; -using System.Runtime.CompilerServices; +using auth; +using Microsoft.EntityFrameworkCore; namespace app.DI { @@ -15,36 +12,11 @@ public static void AddConfigServices(this IServiceCollection services, IConfigur { services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); - services.AddAppAuthorization(configuration); + services.AddAuth(configuration); } - public static void AddAppAuthorization(this IServiceCollection services, IConfiguration configuration) - { - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(o => - { - var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); - o.TokenValidationParameters = new TokenValidationParameters - { - ValidIssuer = configuracaoAutenticaco["Issuer"], - ValidAudience = configuracaoAutenticaco["Audience"], - IssuerSigningKey = new SymmetricSecurityKey - (Encoding.UTF8.GetBytes(configuracaoAutenticaco["Key"]!)), - ValidateIssuer = bool.Parse(configuracaoAutenticaco["ValidateIssuer"]!), - ValidateAudience = bool.Parse(configuracaoAutenticaco["ValidateAudience"]!), - ValidateLifetime = false, - ValidateIssuerSigningKey = bool.Parse(configuracaoAutenticaco["ValidateIssuerSigningKey"]!) - }; - }); - - services.AddAuthorization(); - } } } diff --git a/app/Services/AutenticacaoService.cs b/app/Services/AutenticacaoService.cs deleted file mode 100644 index 4154e78..0000000 --- a/app/Services/AutenticacaoService.cs +++ /dev/null @@ -1,55 +0,0 @@ -using api; -using app.Entidades; -using Microsoft.Extensions.Configuration; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using System.Linq.Expressions; -using System.Security.Claims; -using System.Text; -using System.Text.Json; - -namespace app.Services -{ - public class AutenticacaoService - { - private readonly IConfiguration configuration; - - public AutenticacaoService(IConfiguration configuration) - { - this.configuration = configuration; - } - - public (string Token, DateTime ExpiraEm) GerarToken(Usuario usuario) - { - var configuracaoAutenticaco = configuration.GetSection("Autenticacao"); - - var issuer = configuracaoAutenticaco["Issuer"]; - var audience = configuracaoAutenticaco["Audience"]; - var key = Encoding.ASCII.GetBytes(configuracaoAutenticaco["Key"]!); - var expiraEm = DateTime.UtcNow.AddMinutes(int.Parse(configuracaoAutenticaco["ExpireMinutes"]!)); - var temPermissoes = usuario?.Perfil?.Permissoes?.Any() ?? false; - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(new[] - { - new Claim("id", usuario.Id.ToString()), - new Claim(JwtRegisteredClaimNames.Sub, usuario.Nome), - new Claim(JwtRegisteredClaimNames.Email, usuario.Email), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim("permissions", temPermissoes ? string.Join(',', usuario.Perfil!.Permissoes!.Select(p => p.ToString())) : "") - }), - Expires = expiraEm, - Issuer = issuer, - Audience = audience, - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature), - }; - - var tokenHandler = new JwtSecurityTokenHandler(); - var token = tokenHandler.CreateToken(tokenDescriptor); - var jwtToken = tokenHandler.WriteToken(token); - var stringToken = tokenHandler.WriteToken(token); - return (stringToken, expiraEm); - } - } -} diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 7c6794f..32352a1 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -6,6 +6,7 @@ using BCryptNet = BCrypt.Net.BCrypt; using app.Entidades; using api; +using auth; namespace app.Services { @@ -17,7 +18,7 @@ public class UsuarioService : IUsuarioService private readonly IEmailService emailService; private readonly IConfiguration configuration; private readonly AppDbContext dbContext; - private readonly AutenticacaoService autenticacaoService; + private readonly AuthService autenticacaoService; public UsuarioService ( @@ -26,7 +27,7 @@ public UsuarioService IEmailService emailService, IConfiguration configuration, AppDbContext dbContext, - AutenticacaoService autenticacaoService + AuthService autenticacaoService ) { this.usuarioRepositorio = usuarioRepositorio; @@ -71,7 +72,12 @@ public async Task AutenticarUsuarioAsync(string email, string senha) ValidaSenha(senha, usuario.Senha); - var (token, expiraEm) = autenticacaoService.GerarToken(usuario); + var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel + { + Id = usuario.Id, + Name = usuario.Nome, + Permissions = usuario.Perfil?.Permissoes?.ToList(), + }); return new LoginModel() { diff --git a/app/app.csproj b/app/app.csproj index 0934e26..d9f6155 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -33,6 +33,7 @@ + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 3ece8ad..4c208fe 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -9,7 +9,7 @@ } }, "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", - "Autenticacao": { + "Auth": { "Key": "chave secreta chave secreta chave secreta chave secreta chave secreta chave secreta", "Issuer": "https://localhost:7083/", "Audience": "https://localhost:7083/", diff --git a/auth/AuthConfig.cs b/auth/AuthConfig.cs new file mode 100644 index 0000000..95892b4 --- /dev/null +++ b/auth/AuthConfig.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace auth +{ + public class AuthConfig + { + public string Key { get; set; } + 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; } + } +} diff --git a/auth/AuthService.cs b/auth/AuthService.cs new file mode 100644 index 0000000..98f1573 --- /dev/null +++ b/auth/AuthService.cs @@ -0,0 +1,54 @@ +using auth; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace app.Services +{ + public class AuthService + { + private readonly AuthConfig authConfig; + + public AuthService(IOptions authConfig) + { + this.authConfig = authConfig.Value; + } + + public (string Token, DateTime ExpiresAt) GenerateToken(AuthUserModel user) where TEnum : 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("id", user.Id.ToString()), + new Claim(JwtRegisteredClaimNames.Sub, user.Name), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new 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); + } + + private string EncodePermissions(List? permissions) where TEnum : struct + { + var hasPermissions = permissions?.Any() ?? false; + return hasPermissions ? string.Join(',', permissions!.Select(p => p.ToLong())) : ""; + } + } +} diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs new file mode 100644 index 0000000..5b50afe --- /dev/null +++ b/auth/AuthStartup.cs @@ -0,0 +1,45 @@ +using app.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System.Text; + +namespace auth +{ + public static class AuthStartup + { + public static void AddAuth(this IServiceCollection services, IConfiguration 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 = false, + ValidateIssuerSigningKey = bool.Parse(configuracaoAutenticaco["ValidateIssuerSigningKey"] ?? "false") + }; + }); + + services.AddAuthorization(); + } + + public static long ToLong(this T x) where T : struct { + return (long)Convert.ChangeType(x, typeof(long)); + } + } +} 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..98561eb --- /dev/null +++ b/auth/auth.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + From 62fbc8e0ab9beb220ec8138ad50fc4288825daac Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 19:24:51 -0300 Subject: [PATCH 042/137] refactor: ordena permissoes --- api/Enums.cs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index d3f99ec..7749ec6 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -64,27 +64,25 @@ public enum UF [JsonConverter(typeof(JsonStringEnumConverter))] public enum Permissao { - [Description("Sem permissões")] - None = 0, - [Description("Cadastrar Empresa")] - EmpresaCadastrar = 1, - [Description("Editar Empresa")] - EmpresaEditar = 2, - [Description("Remover Empresa")] - RemoverEmpresa = 3, - [Description("Cadastrar Escola")] - CadastrarEscola = 4, + EscolaCadastrar = 1000, [Description("Editar Escola")] - EditarEscola = 5, + EscolaEditar = 1001, [Description("Remover Escola")] - RemoverEscola = 6, + EscolaRemover = 1002, - [Description("Cadastrar Perfil")] - CadastrarPerfil = 7, - [Description("Editar Perfil")] - EditarPerfil = 8, - [Description("Remover Perfil")] - RemoverPerfil = 9, + [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")] + RemoverPerfil = 3002, } } \ No newline at end of file From ddef1565008028f6aa1e13cdac70aaa3d7d56be6 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 19:25:15 -0300 Subject: [PATCH 043/137] =?UTF-8?q?feat:=20adiciona=20verifica=C3=A7=C3=A3?= =?UTF-8?q?o=20da=20permissao?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/UsuarioController.cs | 16 +++++++++- auth/AuthService.cs | 45 +++++++++++++++++++++++++--- auth/AuthStartup.cs | 4 --- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index a20543d..c27241c 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Mvc; using app.Services.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authorization; +using app.Services; +using api; namespace app.Controllers { @@ -11,12 +14,23 @@ namespace app.Controllers public class UsuarioController : ControllerBase { private readonly IUsuarioService usuarioService; + private readonly AuthService authService; public UsuarioController( - IUsuarioService usuarioService + IUsuarioService usuarioService, + AuthService authService ) { this.usuarioService = usuarioService; + this.authService = authService; + } + + [HttpGet("auth/teste")] + [Authorize] + public async Task Teste() + { + authService.Require(User, Permissao.PerfilEditar); + return 42; } [HttpPost("login")] diff --git a/auth/AuthService.cs b/auth/AuthService.cs index 98f1573..e0458d0 100644 --- a/auth/AuthService.cs +++ b/auth/AuthService.cs @@ -10,13 +10,27 @@ namespace app.Services public class AuthService { private readonly AuthConfig authConfig; + private const string CLAIM_PERMISSIONS = "permissions"; + private const char PERMISSIONS_SEPARATOR = ','; public AuthService(IOptions authConfig) { this.authConfig = authConfig.Value; } - public (string Token, DateTime ExpiresAt) GenerateToken(AuthUserModel user) where TEnum : struct + public void Require(ClaimsPrincipal user, TPermission permission) where TPermission : struct + { + if (!HasPermission(user, permission)) + throw new UnauthorizedAccessException($"O usuário não tem a permissão: {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 (string Token, DateTime ExpiresAt) GenerateToken(AuthUserModel user) where TPermission : struct { var issuer = authConfig.Issuer; var audience = authConfig.Audience; @@ -30,7 +44,7 @@ public AuthService(IOptions authConfig) new Claim("id", user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Sub, user.Name), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim("permissions", EncodePermissions(user.Permissions)) + new Claim(CLAIM_PERMISSIONS, EncodePermissions(user.Permissions)) }), Expires = expiraEm, Issuer = issuer, @@ -45,10 +59,33 @@ public AuthService(IOptions authConfig) return (stringToken, expiraEm); } - private string EncodePermissions(List? permissions) where TEnum : struct + private string EncodePermissions(List? permissions) where TPermission : struct { var hasPermissions = permissions?.Any() ?? false; - return hasPermissions ? string.Join(',', permissions!.Select(p => p.ToLong())) : ""; + 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? + .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 index 5b50afe..3d79843 100644 --- a/auth/AuthStartup.cs +++ b/auth/AuthStartup.cs @@ -37,9 +37,5 @@ public static void AddAuth(this IServiceCollection services, IConfiguration conf services.AddAuthorization(); } - - public static long ToLong(this T x) where T : struct { - return (long)Convert.ChangeType(x, typeof(long)); - } } } From 9665fb507d006b7b4809bc85f361ba9eb1964f56 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 20:11:01 -0300 Subject: [PATCH 044/137] feat: adiciona exception handler --- UsuarioService.sln | 2 +- auth/AuthExceptionHandler.cs | 21 +++++++++++++++++++++ auth/AuthExceptions.cs | 11 +++++++++++ auth/AuthService.cs | 4 +++- auth/AuthStartup.cs | 2 ++ auth/auth.csproj | 6 ++++++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 auth/AuthExceptionHandler.cs create mode 100644 auth/AuthExceptions.cs diff --git a/UsuarioService.sln b/UsuarioService.sln index ae3bfec..fa57c45 100644 --- a/UsuarioService.sln +++ b/UsuarioService.sln @@ -14,7 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "auth", "auth\auth.csproj", "{1C2F7ED1-31B7-4274-BD2A-388700259656}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "auth", "auth\auth.csproj", "{1C2F7ED1-31B7-4274-BD2A-388700259656}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/auth/AuthExceptionHandler.cs b/auth/AuthExceptionHandler.cs new file mode 100644 index 0000000..bd4b784 --- /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.Unauthorized, + }; + context.ExceptionHandled = true; + } + } + } +} diff --git a/auth/AuthExceptions.cs b/auth/AuthExceptions.cs new file mode 100644 index 0000000..62149fa --- /dev/null +++ b/auth/AuthExceptions.cs @@ -0,0 +1,11 @@ +namespace auth { + public class AuthException : Exception + { + public AuthException(string message) : base(message) { } + } + + public class AuthForbiddenException : AuthException + { + public AuthForbiddenException(string message): base(message) { } + } +} diff --git a/auth/AuthService.cs b/auth/AuthService.cs index e0458d0..64e5b88 100644 --- a/auth/AuthService.cs +++ b/auth/AuthService.cs @@ -1,4 +1,5 @@ using auth; +using EnumsNET; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; @@ -21,7 +22,7 @@ public AuthService(IOptions authConfig) public void Require(ClaimsPrincipal user, TPermission permission) where TPermission : struct { if (!HasPermission(user, permission)) - throw new UnauthorizedAccessException($"O usuário não tem a permissão: {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 @@ -72,6 +73,7 @@ private string EncodePermissions(List? permissions) wh 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 => { diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs index 3d79843..ff5689b 100644 --- a/auth/AuthStartup.cs +++ b/auth/AuthStartup.cs @@ -36,6 +36,8 @@ public static void AddAuth(this IServiceCollection services, IConfiguration conf }); services.AddAuthorization(); + + services.AddControllers(o => o.Filters.Add(typeof(AuthExceptionHandler))); } } } diff --git a/auth/auth.csproj b/auth/auth.csproj index 98561eb..fceb82a 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -4,9 +4,15 @@ 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 + True + 1.0.1 + From 7f4ae68aed6fb873e36f8910169f28a449501576 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 21:07:50 -0300 Subject: [PATCH 045/137] fix: renomeia nome do identificador do pacote de autenticacao --- app/Controllers/UsuarioController.cs | 2 +- auth/auth.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index c27241c..e6a7de6 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -27,7 +27,7 @@ AuthService authService [HttpGet("auth/teste")] [Authorize] - public async Task Teste() + public int Teste() { authService.Require(User, Permissao.PerfilEditar); return 42; diff --git a/auth/auth.csproj b/auth/auth.csproj index fceb82a..c556b49 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,8 +7,8 @@ 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 - True - 1.0.1 + 1.0.0 + DnitEpsFga.auth From 5dfc7bc79e110100e621ec4c7dadccd636b49e65 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 21:28:46 -0300 Subject: [PATCH 046/137] feat: adiciona autorizacao no swagger --- auth/AuthStartup.cs | 37 +++++++++++++++++++++++++++++++++++++ auth/auth.csproj | 1 + 2 files changed, 38 insertions(+) diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs index ff5689b..e0ae125 100644 --- a/auth/AuthStartup.cs +++ b/auth/AuthStartup.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using System.Text; namespace auth @@ -11,6 +12,8 @@ public static class AuthStartup { public static void AddAuth(this IServiceCollection services, IConfiguration configuration) { + services.AddAuthSwagger(configuration); + services.Configure(configuration.GetSection("Auth")); services.AddSingleton(); @@ -39,5 +42,39 @@ public static void AddAuth(this IServiceCollection services, IConfiguration conf services.AddControllers(o => o.Filters.Add(typeof(AuthExceptionHandler))); } + + 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() + } + }); + }); + } } } diff --git a/auth/auth.csproj b/auth/auth.csproj index c556b49..5fd508c 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -17,6 +17,7 @@ + From f4ccfba7f519c56609d47065102da0239cacd40f Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Thu, 19 Oct 2023 21:29:29 -0300 Subject: [PATCH 047/137] fix: troca erro de nao autorizado para o 403 --- auth/AuthExceptionHandler.cs | 2 +- auth/auth.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/AuthExceptionHandler.cs b/auth/AuthExceptionHandler.cs index bd4b784..d638e5f 100644 --- a/auth/AuthExceptionHandler.cs +++ b/auth/AuthExceptionHandler.cs @@ -12,7 +12,7 @@ public void OnException(ExceptionContext context) { context.Result = new JsonResult(new { Details = apiException.Message }) { - StatusCode = (int)HttpStatusCode.Unauthorized, + StatusCode = (int)HttpStatusCode.Forbidden, }; context.ExceptionHandled = true; } diff --git a/auth/auth.csproj b/auth/auth.csproj index 5fd508c..ad83432 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,7 +7,7 @@ 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.0 + 1.0.1 DnitEpsFga.auth From 5db91fd90bcefeb88063f7aae278af7dc0f8dfd2 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Thu, 19 Oct 2023 23:18:19 -0300 Subject: [PATCH 048/137] fix: ajusta a cobertura de testes no sonar --- app/app.csproj | 6 ++++++ test/test.csproj | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/app/app.csproj b/app/app.csproj index bc551b2..01dbf29 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -32,5 +32,11 @@ + + + + **\Migrations\** + + diff --git a/test/test.csproj b/test/test.csproj index 25ecdac..91e52b4 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -35,4 +35,11 @@ + + + **/** + ** + + + From e77408d7226007f558b62d3b6d2250af8812d095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Reis?= Date: Fri, 20 Oct 2023 10:52:14 -0300 Subject: [PATCH 049/137] =?UTF-8?q?refactor:=20adiciona=20permiss=C3=A3o?= =?UTF-8?q?=20de=20visualizar=20escola=20ao=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/Enums.cs b/api/Enums.cs index 7749ec6..17f4d77 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -70,6 +70,8 @@ public enum Permissao EscolaEditar = 1001, [Description("Remover Escola")] EscolaRemover = 1002, + [Description("Visualizar Escola")] + EscolaVisualizar = 1003, [Description("Cadastrar Empresa")] EmpresaCadastrar = 2000, From 2ddbc12908680919816dd5eec51f5d3979d40e9a Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Fri, 20 Oct 2023 22:37:59 -0300 Subject: [PATCH 050/137] feat: adiciona atualizacao do token expirado --- api/Usuarios/AtualizarTokenData.cs | 12 + app/Controllers/UsuarioController.cs | 7 + app/Entidades/Usuario.cs | 3 + ...0231021000639_TokenAtualizacao.Designer.cs | 229 ++++++++++++++++++ .../20231021000639_TokenAtualizacao.cs | 39 +++ app/Migrations/AppDbContextModelSnapshot.cs | 6 + .../Interfaces/IUsuarioRepositorio.cs | 16 +- app/Repositorios/UsuarioRepositorio.cs | 12 +- app/Services/Interfaces/IUsuarioService.cs | 1 + app/Services/UsuarioService.cs | 53 ++-- app/appsettings.Development.json | 4 +- auth/AuthConfig.cs | 11 +- auth/AuthExceptions.cs | 6 +- auth/AuthService.cs | 23 +- auth/AuthStartup.cs | 12 +- auth/auth.csproj | 2 +- 16 files changed, 397 insertions(+), 39 deletions(-) create mode 100644 api/Usuarios/AtualizarTokenData.cs create mode 100644 app/Migrations/20231021000639_TokenAtualizacao.Designer.cs create mode 100644 app/Migrations/20231021000639_TokenAtualizacao.cs 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/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index e6a7de6..3259bd1 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -51,6 +51,13 @@ public async Task Logar([FromBody] UsuarioDTO usuarioDTO) } } + [HttpPost("atualizarToken")] + public async Task AtualizarToken([FromBody] AtualizarTokenDto atualizarTokenDto) + { + return await usuarioService.AtualizarTokenAsync(atualizarTokenDto); + } + + [HttpPost("cadastrarUsuarioDnit")] public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) { diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index c6c6b5a..9174d24 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -28,5 +28,8 @@ public class Usuario public Guid? PerfilId { get; set; } public Perfil? Perfil { get; set; } + + public string? TokenAtualizacao { get; set; } + public DateTime? TokenAtualizacaoExpiracao { get; set; } } } \ No newline at end of file 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index dfc0f38..6920ba8 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -143,6 +143,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("TokenAtualizacao") + .HasColumnType("text"); + + b.Property("TokenAtualizacaoExpiracao") + .HasColumnType("timestamp with time zone"); + b.Property("UfLotacao") .HasColumnType("integer"); diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index ff1f711..08bfc7e 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -5,13 +5,13 @@ namespace app.Repositorios.Interfaces { public interface IUsuarioRepositorio { - public UsuarioModel? ObterUsuario(string email); - Task ObterUsuarioAsync(string email, bool includePerfil = false); - public UsuarioModel? TrocarSenha(string senha, string email); - public void 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); + UsuarioModel? 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); + void CadastrarUsuarioDnit(UsuarioDnit usuario); + void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 2406491..ffaed8b 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -27,7 +27,7 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) return mapper.Map(query); } - public async Task ObterUsuarioAsync(string email, bool includePerfil = false) + public async Task ObterUsuarioAsync(int? id = null, string? email = null, bool includePerfil = false) { var query = dbContext.Usuario.AsQueryable(); @@ -35,8 +35,16 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) { 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(u => u.Email.ToLower() == email.ToLower()); + return await query.FirstOrDefaultAsync(); } public void CadastrarUsuarioDnit(UsuarioDnit usuario) diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index ac7925c..9288225 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -11,5 +11,6 @@ public interface IUsuarioService public Task RecuperarSenha(UsuarioDTO usuarioDto); public Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); + Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 32352a1..877d625 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -67,25 +67,12 @@ public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) public async Task AutenticarUsuarioAsync(string email, string senha) { - var usuario = await usuarioRepositorio.ObterUsuarioAsync(email, includePerfil: true) + var usuario = await usuarioRepositorio.ObterUsuarioAsync(email: email, includePerfil: true) ?? throw new KeyNotFoundException(); ValidaSenha(senha, usuario.Senha); - var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel - { - Id = usuario.Id, - Name = usuario.Nome, - Permissions = usuario.Perfil?.Permissoes?.ToList(), - }); - - return new LoginModel() - { - Token = token, - ExpiraEm = expiraEm, - TokenAtualizacao = "", - Permissoes = usuario.Perfil?.Permissoes?.ToList(), - }; + return await CriarTokenAsync(usuario); } private UsuarioModel? Obter(string email) @@ -156,5 +143,41 @@ private string GerarLinkDeRecuperacao(string 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 (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel + { + Id = usuario.Id, + Name = usuario.Nome, + Permissions = usuario.Perfil?.Permissoes?.ToList(), + }); + + var (tokenAtualizacao, tokenAtualizacaoExpiracao) = autenticacaoService.GenerateRefreshToken(); + + usuario.TokenAtualizacao = tokenAtualizacao; + usuario.TokenAtualizacaoExpiracao = tokenAtualizacaoExpiracao; + await dbContext.SaveChangesAsync(); + + return new LoginModel() + { + Token = token, + ExpiraEm = expiraEm, + TokenAtualizacao = tokenAtualizacao, + Permissoes = usuario.Perfil?.Permissoes?.ToList(), + }; + } } } diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 4c208fe..134ed99 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -10,12 +10,14 @@ }, "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", "Auth": { + "Enabled": false, "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": 5 + "ExpireMinutes": 10, + "RefreshTokenExpireMinutes": 120 } } \ No newline at end of file diff --git a/auth/AuthConfig.cs b/auth/AuthConfig.cs index 95892b4..aa11cce 100644 --- a/auth/AuthConfig.cs +++ b/auth/AuthConfig.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace auth +namespace auth { public class AuthConfig { @@ -13,6 +7,7 @@ public class AuthConfig public string Audience { get; set; } public bool ValidateIssuer { get; set; } public bool ValidateIssuerSigningKey { get; set; } - public int ExpireMinutes { get; set; } + public int ExpireMinutes { get; set; } = 10; + public int RefreshTokenExpireMinutes { get; set; } = 120; } } diff --git a/auth/AuthExceptions.cs b/auth/AuthExceptions.cs index 62149fa..97f93b7 100644 --- a/auth/AuthExceptions.cs +++ b/auth/AuthExceptions.cs @@ -1,11 +1,13 @@ namespace auth { - public class AuthException : Exception + public abstract class AuthException : Exception { - public AuthException(string message) : base(message) { } + 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 index 64e5b88..98d57d3 100644 --- a/auth/AuthService.cs +++ b/auth/AuthService.cs @@ -4,6 +4,7 @@ using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using System.Security.Cryptography; using System.Text; namespace app.Services @@ -12,6 +13,7 @@ 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) @@ -31,6 +33,17 @@ public bool HasPermission(ClaimsPrincipal user, TPermission permiss 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 (string Token, DateTime ExpiresAt) GenerateToken(AuthUserModel user) where TPermission : struct { var issuer = authConfig.Issuer; @@ -42,7 +55,7 @@ public bool HasPermission(ClaimsPrincipal user, TPermission permiss { Subject = new ClaimsIdentity(new[] { - new Claim("id", user.Id.ToString()), + 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)) @@ -60,6 +73,14 @@ public bool HasPermission(ClaimsPrincipal user, TPermission permiss 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; diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs index e0ae125..9f6df35 100644 --- a/auth/AuthStartup.cs +++ b/auth/AuthStartup.cs @@ -10,6 +10,15 @@ 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); @@ -33,7 +42,8 @@ public static void AddAuth(this IServiceCollection services, IConfiguration conf IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuracaoAutenticaco["Key"]!)), ValidateIssuer = bool.Parse(configuracaoAutenticaco["ValidateIssuer"] ?? "false"), ValidateAudience = bool.Parse(configuracaoAutenticaco["ValidateAudience"] ?? "false"), - ValidateLifetime = false, + ValidateLifetime = true, + LifetimeValidator = CustomLifetimeValidator, ValidateIssuerSigningKey = bool.Parse(configuracaoAutenticaco["ValidateIssuerSigningKey"] ?? "false") }; }); diff --git a/auth/auth.csproj b/auth/auth.csproj index ad83432..2a2063b 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,7 +7,7 @@ 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.1 + 1.0.3 DnitEpsFga.auth From 7dc4304aaeb149ce2d8effaf4165da4164ef6595 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Fri, 20 Oct 2023 22:55:17 -0300 Subject: [PATCH 051/137] fix: arruma nome das permissoes --- api/Enums.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/Enums.cs b/api/Enums.cs index 17f4d77..dd0f01c 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -85,6 +85,8 @@ public enum Permissao [Description("Editar Perfil de Usuário")] PerfilEditar = 3001, [Description("Remover Perfil de Usuário")] - RemoverPerfil = 3002, + PerfilRemover = 3002, + [Description("Visualizar perfis")] + PerfilVisualizar = 3003, } } \ No newline at end of file From 090e5787b4df3c7ff0b35402c29c52c6c298eaf9 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 03:22:10 -0300 Subject: [PATCH 052/137] =?UTF-8?q?feat:=20ajusta=20recursos=20para=20impl?= =?UTF-8?q?ementa=C3=A7=C3=A3o=20do=20CRUD=20de=20perfis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 2 +- app/Entidades/AppDbContext.cs | 4 + .../20231020191746_PerfiNomeUnico.Designer.cs | 226 ++++++++++++++++++ .../20231020191746_PerfiNomeUnico.cs | 28 +++ app/Migrations/AppDbContextModelSnapshot.cs | 13 +- 5 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs create mode 100644 app/Migrations/20231020191746_PerfiNomeUnico.cs diff --git a/api/Enums.cs b/api/Enums.cs index 7749ec6..2c735e8 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -83,6 +83,6 @@ public enum Permissao [Description("Editar Perfil de Usuário")] PerfilEditar = 3001, [Description("Remover Perfil de Usuário")] - RemoverPerfil = 3002, + PerfilRemover = 3002, } } \ No newline at end of file diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index 65851f9..acfc392 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -48,6 +48,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) em.ToTable("UsuarioEmpresa"); }); + modelBuilder.Entity() + .HasIndex(p => p.Nome) + .IsUnique(); + modelBuilder.Entity() .HasMany(p => p.PerfilPermissoes) .WithOne(pp => pp.Perfil) diff --git a/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs b/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs new file mode 100644 index 0000000..38f33be --- /dev/null +++ b/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs @@ -0,0 +1,226 @@ +// +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("20231020191746_PerfiNomeUnico")] + partial class PerfiNomeUnico + { + /// + 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("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/20231020191746_PerfiNomeUnico.cs b/app/Migrations/20231020191746_PerfiNomeUnico.cs new file mode 100644 index 0000000..faea8c0 --- /dev/null +++ b/app/Migrations/20231020191746_PerfiNomeUnico.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace app.Migrations +{ + /// + public partial class PerfiNomeUnico : 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index dfc0f38..10b2d96 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -56,7 +56,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Cnpj") .IsUnique(); - b.ToTable("Empresa"); + b.ToTable("Empresa", (string)null); }); modelBuilder.Entity("app.Entidades.Perfil", b => @@ -72,7 +72,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Perfis"); + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis", (string)null); }); modelBuilder.Entity("app.Entidades.PerfilPermissao", b => @@ -91,7 +94,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PerfilId"); - b.ToTable("PerfilPermissoes"); + b.ToTable("PerfilPermissoes", (string)null); }); modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => @@ -114,7 +117,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("IdUsuario"); - b.ToTable("RedefinicaoSenha"); + b.ToTable("RedefinicaoSenha", (string)null); }); modelBuilder.Entity("app.Entidades.Usuario", b => @@ -153,7 +156,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PerfilId"); - b.ToTable("Usuario"); + b.ToTable("Usuario", (string)null); }); modelBuilder.Entity("EmpresaUsuario", b => From 14a0670fde35bdbd29dd17f8f353da6b5b74d3f5 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 03:23:50 -0300 Subject: [PATCH 053/137] feat: implementa repositorio dos perfis --- .../Interfaces/IPerfilRepositorio.cs | 16 ++++ app/Repositorios/PerfilRepositorio.cs | 74 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 app/Repositorios/Interfaces/IPerfilRepositorio.cs create mode 100644 app/Repositorios/PerfilRepositorio.cs diff --git a/app/Repositorios/Interfaces/IPerfilRepositorio.cs b/app/Repositorios/Interfaces/IPerfilRepositorio.cs new file mode 100644 index 0000000..666b60d --- /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); + } +} \ No newline at end of file diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs new file mode 100644 index 0000000..2838253 --- /dev/null +++ b/app/Repositorios/PerfilRepositorio.cs @@ -0,0 +1,74 @@ +using System.Runtime.CompilerServices; +using api; +using api.Perfis; +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) + { + dbContext.Perfis.Remove(perfil); + } + + 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) + { + var query = dbContext.Perfis.AsQueryable(); + + query = query.Include(p => p.PerfilPermissoes); + + return await query + .OrderBy(p => p.Nome) + .Skip((pageIndex - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + } + } +} \ No newline at end of file From b1b9baa17c024ba53f3a2a4ed51d0e6985e05afe Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 03:25:31 -0300 Subject: [PATCH 054/137] feat: implementa service dos perfis --- api/Perfis/PerfilDTO.cs | 8 ++ api/Perfis/PerfilModel.cs | 9 +++ app/Services/Interfaces/IPerfilService.cs | 14 ++++ app/Services/Mapper.cs | 9 +++ app/Services/PerfilService.cs | 94 +++++++++++++++++++++++ 5 files changed, 134 insertions(+) create mode 100644 api/Perfis/PerfilDTO.cs create mode 100644 api/Perfis/PerfilModel.cs create mode 100644 app/Services/Interfaces/IPerfilService.cs create mode 100644 app/Services/PerfilService.cs diff --git a/api/Perfis/PerfilDTO.cs b/api/Perfis/PerfilDTO.cs new file mode 100644 index 0000000..d9410cd --- /dev/null +++ b/api/Perfis/PerfilDTO.cs @@ -0,0 +1,8 @@ +namespace api.Perfis +{ + public class PerfilDTO + { + 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..3df1d86 --- /dev/null +++ b/api/Perfis/PerfilModel.cs @@ -0,0 +1,9 @@ +namespace api.Perfis +{ + public class PerfilModel + { + public Guid Id { get; set; } + public string Nome { get; set; } + public List Permissoes { get; set; } + } +} diff --git a/app/Services/Interfaces/IPerfilService.cs b/app/Services/Interfaces/IPerfilService.cs new file mode 100644 index 0000000..1581292 --- /dev/null +++ b/app/Services/Interfaces/IPerfilService.cs @@ -0,0 +1,14 @@ +using api.Perfis; +using app.Entidades; +using api; + +namespace app.Services.Interfaces +{ + public interface IPerfilService + { + public Perfil CriarPerfil(Perfil perfil, List permissoes); + public Task EditarPerfil(Perfil perfil, List permissoes); + public Task ExcluirPerfil(Guid id); + public Task> ListarPerfisAsync(int pageIndex, int pageSize); + } +} \ No newline at end of file diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 6bd0e07..ca66e39 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -4,6 +4,7 @@ using app.Entidades; using api; using EnumsNET; +using api.Perfis; namespace app.Services.Mapper @@ -30,6 +31,14 @@ public AutoMapperConfig() CreateMap() .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); + + CreateMap() + .ForMember(p => p.Permissoes, opt => opt.Ignore()) + .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) + .ForMember(p => p.Usuarios, opt => opt.Ignore()); + + CreateMap() + .ForMember(model => model.Permissoes, opt => opt.MapFrom(p => p.Permissoes)); } } } \ No newline at end of file diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs new file mode 100644 index 0000000..ef5a6a3 --- /dev/null +++ b/app/Services/PerfilService.cs @@ -0,0 +1,94 @@ +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 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) + { + Perfil novoPerfil = perfilRepositorio.RegistraPerfil(perfil); + + foreach(var permissao in permissoes) + { + var novoPermissaoPerfil = perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); + novoPerfil.PerfilPermissoes!.Add(novoPermissaoPerfil); + } + + dbContext.SaveChanges(); + + return novoPerfil; + } + + public async Task EditarPerfil(Perfil perfil, List permissoes) + { + Perfil perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? + throw new KeyNotFoundException("Perfil não encontrado"); + + if(perfilDb.Nome != perfil.Nome) + { + perfilDb.Nome = perfil.Nome; + } + + List permissoesRegistradas = perfilDb.Permissoes!.ToList(); + + foreach(var permissao in permissoesRegistradas) + { + if(!permissoes.Contains(permissao)) + { + var permissaoRemovida = perfilDb.PerfilPermissoes!.Where(p => p.Permissao == permissao).FirstOrDefault(); + perfilRepositorio.RemovePermissaoDoPerfil(permissaoRemovida!); + perfilDb.PerfilPermissoes!.Remove(permissaoRemovida!); + } + else + { + permissoes.Remove(permissao); + } + } + + foreach(var permissao in permissoes) + { + var novoPerfilPermissao = perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); + perfilDb.PerfilPermissoes!.Add(novoPerfilPermissao); + } + + dbContext.SaveChanges(); + + return perfilDb; + } + + public async Task ExcluirPerfil(Guid id) + { + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(id) ?? + throw new KeyNotFoundException("Perfil não encontrado"); + + foreach(var perfilPermissao in perfilDb.PerfilPermissoes!) + { + perfilRepositorio.RemovePermissaoDoPerfil(perfilPermissao); + } + + perfilRepositorio.RemovePerfil(perfilDb); + + dbContext.SaveChanges(); + } + + public async Task> ListarPerfisAsync(int pageIndex, int pageSize) + { + return await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize); + } + } +} \ No newline at end of file From 1b33fad351a0bfceb7d4f6e264f5ec3e5e63e367 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 03:27:21 -0300 Subject: [PATCH 055/137] feat: implementa controllers dos perfis --- app/Controllers/PerfilController.cs | 106 ++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/Controllers/PerfilController.cs diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs new file mode 100644 index 0000000..f1eae59 --- /dev/null +++ b/app/Controllers/PerfilController.cs @@ -0,0 +1,106 @@ +using api.Perfis; +using app.Entidades; +using app.Services; +using app.Services.Interfaces; +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace app.Controllers +{ + [ApiController] + [Route("api/perfil")] + public class PerfilController : ControllerBase + { + private readonly AuthService authService; + private readonly IPerfilService perfilService; + private readonly IMapper mapper; + + public PerfilController(IPerfilService perfilService, AuthService authService, IMapper mapper ) + { + this.perfilService = perfilService; + this.authService = authService; + this.mapper = mapper; + } + + [HttpPost()] + public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) + { + //verificar se o user tem permissão + + 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."); + } + } + + [HttpPut("{id}")] + public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perfilDTO) + { + //verificar se o user tem permissão + Perfil perfil = mapper.Map(perfilDTO); + perfil.Id = id; + + try{ + Perfil 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) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } + } + + [HttpDelete("{id}")] + public async Task ExcluirPerfil(Guid id) + { + //verificar se o user tem permissao + try{ + await perfilService.ExcluirPerfil(id); + return Ok("Perfil excluido"); + } + catch(KeyNotFoundException){ + return NotFound("Perfil não encontrado"); + } + catch(Exception) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } + } + + [HttpGet()] + public async Task ListarPerfis(int pageIndex, int pageSize) + { + try + { + var pagina = await perfilService.ListarPerfisAsync(pageIndex, pageSize); + + List paginaRetorno = pagina.Select(p => mapper.Map(p)).ToList(); + + return Ok(paginaRetorno); + } + catch(Exception) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } + } + } +} \ No newline at end of file From 410b7e6e1f39a4c5835db445680e410150e7bfb6 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 03:29:39 -0300 Subject: [PATCH 056/137] =?UTF-8?q?feat:=20insere=20servi=C3=A7os=20e=20at?= =?UTF-8?q?ualiza=20as=20configuracoes=20do=20docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- app/DI/RepositoriosConfig.cs | 1 + app/DI/ServicesConfig.cs | 9 ++++++--- app/appsettings.Development.json | 3 ++- docker-compose.yml | 8 +++++--- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index bdcf6bb..a157bfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,4 @@ WORKDIR /app COPY . . -CMD dotnet watch --project app +CMD export MODE=container && dotnet watch --project app diff --git a/app/DI/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index 3be8d57..f5c7623 100644 --- a/app/DI/RepositoriosConfig.cs +++ b/app/DI/RepositoriosConfig.cs @@ -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 7f251d3..ab1b8eb 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -9,14 +9,17 @@ namespace app.DI public static class ServicesConfig { public static void AddConfigServices(this IServiceCollection services, IConfiguration configuration) - { - services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); + { + 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.AddAuth(configuration); } - } } diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 4c208fe..b814c78 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "PostgreSql": "Host=localhost;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" + "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": { diff --git a/docker-compose.yml b/docker-compose.yml index 2457540..c21be93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,18 @@ version: "3.0" services: usuario-service: + depends_on: + - dnit-usuario-db build: context: . ports: - "7083:7083" container_name: usuario-service volumes: + - ./api:/app/api - ./app:/app/app - - ./dominio:/app/dominio - - ./repositorio:/app/repositorio - - ./service:/app/service - ./test:/app/test + - ./auth:/app/auth env_file: - .env @@ -20,6 +21,7 @@ services: image: postgres:15.4 restart: always environment: + POSTGRES_DB: usuarioservice POSTGRES_PASSWORD: 1234 ports: - "5432:5432" From 3cc0b07af714b1bac51eb16f7d33e065a840fa1b Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 11:31:22 -0300 Subject: [PATCH 057/137] feat: implementa a listagem de permissoes --- app/Controllers/DominioController.cs | 21 ++++++++++++++++++--- app/Services/Mapper.cs | 7 ++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index f13c719..2b90e2e 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -5,6 +5,7 @@ using app.Repositorios.Interfaces; using app.Services; using app.Services.Interfaces; +using AutoMapper; namespace app.Controllers { @@ -14,13 +15,14 @@ public class DominioController : ControllerBase { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; - public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio) + private readonly IMapper mapper; + + public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio, IMapper mapper) { this.unidadeFederativaRepositorio = unidadeFederativaRepositorio; + this.mapper = mapper; } - - [HttpGet("unidadeFederativa")] public IActionResult ObterLista() { @@ -29,6 +31,19 @@ public IActionResult ObterLista() return new OkObjectResult(listaUnidadeFederativa); } + [HttpGet("permissoes")] + public IActionResult ObterListaDePermissoes(int pageIndex, int pageSize) + { + var lista = Enum.GetValues() + .Select(p => mapper.Map(p)) + .OrderBy(p => p.Categoria) + .Skip((pageIndex - 1) * pageSize) + .Take(pageSize); + + return Ok(lista); + } + + } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index ca66e39..7d9a44d 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -5,7 +5,7 @@ using api; using EnumsNET; using api.Perfis; - +using System.Text.RegularExpressions; namespace app.Services.Mapper { @@ -37,6 +37,11 @@ public AutoMapperConfig() .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) .ForMember(p => p.Usuarios, opt => opt.Ignore()); + CreateMap() + .ForMember(model => model.Id, opt => opt.MapFrom(p => (int)p)) + .ForMember(model => model.Descricao, opt => opt.MapFrom(p => p.AsString(EnumFormat.Description))) + .ForMember(model => model.Categoria, opt => opt.MapFrom(p => Regex.Match(p.ToString(), @"^([A-Z][a-z]+)"))); + CreateMap() .ForMember(model => model.Permissoes, opt => opt.MapFrom(p => p.Permissoes)); } From b7b1e630ebb3f5527868874404107b8f8c998f04 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 13:06:38 -0300 Subject: [PATCH 058/137] feat: implementa repositorio para permissoes --- .../Interfaces/IPermissaoRepositorio.cs | 10 ++++++ app/Repositorios/PermissaoRepositorio.cs | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 app/Repositorios/Interfaces/IPermissaoRepositorio.cs create mode 100644 app/Repositorios/PermissaoRepositorio.cs diff --git a/app/Repositorios/Interfaces/IPermissaoRepositorio.cs b/app/Repositorios/Interfaces/IPermissaoRepositorio.cs new file mode 100644 index 0000000..b2b7136 --- /dev/null +++ b/app/Repositorios/Interfaces/IPermissaoRepositorio.cs @@ -0,0 +1,10 @@ +using api; + +namespace app.Repositorios.Interfaces +{ + public interface IPermissaoRepositorio + { + public List ObterCategorias(); + public List ObterPermissoesPortCategoria(string categoria); + } +} \ No newline at end of file diff --git a/app/Repositorios/PermissaoRepositorio.cs b/app/Repositorios/PermissaoRepositorio.cs new file mode 100644 index 0000000..b741c5e --- /dev/null +++ b/app/Repositorios/PermissaoRepositorio.cs @@ -0,0 +1,34 @@ +using System.Text.RegularExpressions; +using api; +using app.Repositorios.Interfaces; + +namespace app.Repositorios +{ + public class PermissaoRepositorio : IPermissaoRepositorio + { + private const string pattern = @"^([A-Z][a-z]+)"; + public PermissaoRepositorio() + { + + } + public List ObterCategorias() + { + var permissoesOrdenadas = Enum.GetNames().OrderBy(str => str); + + HashSet categorias = new(); + + foreach(var p in permissoesOrdenadas) + { + categorias.Add(Regex.Match(p, pattern).ToString()); + } + + return categorias.ToList(); + } + + public List ObterPermissoesPortCategoria(string categoria) + { + var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern).ToString()); + return permissoes.ToList(); + } + } +} \ No newline at end of file From 14ccda5192d0a324836bac7fd7eed2396d58f98c Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 13:07:59 -0300 Subject: [PATCH 059/137] feat: separa a listagem por categoria --- api/PermissaoModel.cs | 8 +++++++ app/Controllers/DominioController.cs | 36 +++++++++++++++++++--------- app/DI/RepositoriosConfig.cs | 1 + app/Services/Mapper.cs | 5 ---- 4 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 api/PermissaoModel.cs diff --git a/api/PermissaoModel.cs b/api/PermissaoModel.cs new file mode 100644 index 0000000..7477827 --- /dev/null +++ b/api/PermissaoModel.cs @@ -0,0 +1,8 @@ +namespace api +{ + public class PermissaoModel + { + public string Categoria { get; set; } + public List Permisoes { get; set; } + } +} \ No newline at end of file diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index 2b90e2e..3891951 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -15,11 +15,14 @@ public class DominioController : ControllerBase { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; + private readonly IPermissaoRepositorio permissaoRepositorio; + private readonly IMapper mapper; - public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio, IMapper mapper) + public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio, IMapper mapper, IPermissaoRepositorio permissaoRepositorio) { this.unidadeFederativaRepositorio = unidadeFederativaRepositorio; + this.permissaoRepositorio = permissaoRepositorio; this.mapper = mapper; } @@ -32,18 +35,29 @@ public IActionResult ObterLista() } [HttpGet("permissoes")] - public IActionResult ObterListaDePermissoes(int pageIndex, int pageSize) + public IActionResult ObterListaDePermissoes() { - var lista = Enum.GetValues() - .Select(p => mapper.Map(p)) - .OrderBy(p => p.Categoria) - .Skip((pageIndex - 1) * pageSize) - .Take(pageSize); - + var categorias = permissaoRepositorio.ObterCategorias(); + + List lista = new(); + foreach(var categoria in categorias) + { + PermissaoModel model = new () + { + Categoria = categoria, + Permisoes = permissaoRepositorio.ObterPermissoesPortCategoria(categoria) + }; + lista.Add(model); + } return Ok(lista); + /* try + { + + } + catch (Exception) + { + return StatusCode(500, "Houve um erro interno no servidor."); + } */ } - - - } } diff --git a/app/DI/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index f5c7623..8c86e99 100644 --- a/app/DI/RepositoriosConfig.cs +++ b/app/DI/RepositoriosConfig.cs @@ -10,6 +10,7 @@ public static void AddConfigRepositorios(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 7d9a44d..7239c58 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -37,11 +37,6 @@ public AutoMapperConfig() .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) .ForMember(p => p.Usuarios, opt => opt.Ignore()); - CreateMap() - .ForMember(model => model.Id, opt => opt.MapFrom(p => (int)p)) - .ForMember(model => model.Descricao, opt => opt.MapFrom(p => p.AsString(EnumFormat.Description))) - .ForMember(model => model.Categoria, opt => opt.MapFrom(p => Regex.Match(p.ToString(), @"^([A-Z][a-z]+)"))); - CreateMap() .ForMember(model => model.Permissoes, opt => opt.MapFrom(p => p.Permissoes)); } From 5d221a48c81a5fe0e3385bd586d94690fc02da31 Mon Sep 17 00:00:00 2001 From: Hunter104 <109833229+Hunter104@users.noreply.github.com> Date: Sat, 21 Oct 2023 13:53:39 -0300 Subject: [PATCH 060/137] =?UTF-8?q?refactor:=20adiciona=20permiss=C3=B5es?= =?UTF-8?q?=20relacionadas=20ao=20UPS=20service=20ao=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/Enums.cs b/api/Enums.cs index dd0f01c..a6b98bb 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -88,5 +88,14 @@ public enum Permissao PerfilRemover = 3002, [Description("Visualizar perfis")] PerfilVisualizar = 3003, + + [Description("Calcular UPS de sinistros")] + UpsCalcularSinistro = 5000, + [Description("Caluclar UPS de escolas")] + UpsCalcularEscola = 5001, + [Description("Cadastrar rodovia")] + RodoviaCadastrar = 5002, + [Description("Cadastrar sinistro")] + SinistroCadastrar = 5003, } -} \ No newline at end of file +} From 09635743749791aacacebf6079daf01b3f4b0359 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 14:58:33 -0300 Subject: [PATCH 061/137] test: insere testes para PerfilRepositorio --- app/Controllers/DominioController.cs | 9 +- app/Services/Mapper.cs | 1 + test/Fixtures/Base.cs | 2 + test/PerfilRepositorioTest.cs | 149 +++++++++++++++++++++++++++ test/Stub/PerfilStub.cs | 46 +++++++++ 5 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 test/PerfilRepositorioTest.cs create mode 100644 test/Stub/PerfilStub.cs diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index 3891951..325e56c 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -49,15 +49,8 @@ public IActionResult ObterListaDePermissoes() }; lista.Add(model); } + return Ok(lista); - /* try - { - - } - catch (Exception) - { - return StatusCode(500, "Houve um erro interno no servidor."); - } */ } } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 7239c58..f66d28b 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -33,6 +33,7 @@ public AutoMapperConfig() .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); 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()); diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs index d41588b..ba9b7b1 100644 --- a/test/Fixtures/Base.cs +++ b/test/Fixtures/Base.cs @@ -26,6 +26,8 @@ protected override void AddServices(IServiceCollection services, IConfiguration? // Repositorios services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); // Services services.AddScoped(); 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/Stub/PerfilStub.cs b/test/Stub/PerfilStub.cs new file mode 100644 index 0000000..2897f77 --- /dev/null +++ b/test/Stub/PerfilStub.cs @@ -0,0 +1,46 @@ +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") + { + return new Perfil + { + Nome = nome, + PerfilPermissoes = new List + { + RetornaPerfilPermissao(), RetornaPerfilPermissao(Permissao.PerfilEditar) + } + }; + } + + public static List RetornaListaDePerfis(int n = 4) + { + List lista = new(); + + for(int i = 0; i < 4; i++) + { + lista.Add(RetornaPerfil("PerfilTeste_" + i.ToString())); + } + + return lista; + } + } +} \ No newline at end of file From 5fb55603b15bf6857e8883b5efb2ddee012b22b1 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 15:18:41 -0300 Subject: [PATCH 062/137] chore: retorna uma migracao para atuaalizar a branch --- .../20231020191746_PerfiNomeUnico.Designer.cs | 226 ------------------ .../20231020191746_PerfiNomeUnico.cs | 28 --- app/Migrations/AppDbContextModelSnapshot.cs | 3 - 3 files changed, 257 deletions(-) delete mode 100644 app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs delete mode 100644 app/Migrations/20231020191746_PerfiNomeUnico.cs diff --git a/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs b/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs deleted file mode 100644 index 38f33be..0000000 --- a/app/Migrations/20231020191746_PerfiNomeUnico.Designer.cs +++ /dev/null @@ -1,226 +0,0 @@ -// -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("20231020191746_PerfiNomeUnico")] - partial class PerfiNomeUnico - { - /// - 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("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/20231020191746_PerfiNomeUnico.cs b/app/Migrations/20231020191746_PerfiNomeUnico.cs deleted file mode 100644 index faea8c0..0000000 --- a/app/Migrations/20231020191746_PerfiNomeUnico.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace app.Migrations -{ - /// - public partial class PerfiNomeUnico : 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 10b2d96..c2d742c 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -72,9 +72,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("Nome") - .IsUnique(); - b.ToTable("Perfis", (string)null); }); From 83856c17b8052498960664bd38ca7c92021a1569 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 16:16:11 -0300 Subject: [PATCH 063/137] feat: define o nome do perfil como campo unico --- ...20231021183203_PerfilNomeUnico.Designer.cs | 232 ++++++++++++++++++ .../20231021183203_PerfilNomeUnico.cs | 28 +++ app/Migrations/AppDbContextModelSnapshot.cs | 13 +- 3 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 app/Migrations/20231021183203_PerfilNomeUnico.Designer.cs create mode 100644 app/Migrations/20231021183203_PerfilNomeUnico.cs 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 670bc07..77c8df7 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -56,7 +56,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Cnpj") .IsUnique(); - b.ToTable("Empresa", (string)null); + b.ToTable("Empresa"); }); modelBuilder.Entity("app.Entidades.Perfil", b => @@ -72,7 +72,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Perfis", (string)null); + b.HasIndex("Nome") + .IsUnique(); + + b.ToTable("Perfis"); }); modelBuilder.Entity("app.Entidades.PerfilPermissao", b => @@ -91,7 +94,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PerfilId"); - b.ToTable("PerfilPermissoes", (string)null); + b.ToTable("PerfilPermissoes"); }); modelBuilder.Entity("app.Entidades.RedefinicaoSenha", b => @@ -114,7 +117,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("IdUsuario"); - b.ToTable("RedefinicaoSenha", (string)null); + b.ToTable("RedefinicaoSenha"); }); modelBuilder.Entity("app.Entidades.Usuario", b => @@ -159,7 +162,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PerfilId"); - b.ToTable("Usuario", (string)null); + b.ToTable("Usuario"); }); modelBuilder.Entity("EmpresaUsuario", b => From ca7a4c360970732561afd47bc399bb255dfd31aa Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 16:17:55 -0300 Subject: [PATCH 064/137] fix: arruma o retorno na criacao e edicao de perfis --- app/Services/Mapper.cs | 4 +--- app/Services/PerfilService.cs | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index f66d28b..2ba5a78 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -23,7 +23,6 @@ public AutoMapperConfig() .ForMember(model => model.Sigla, opt => opt.MapFrom(uf => uf.ToString())) .ForMember(model => model.Nome, opt => opt.MapFrom(uf => uf.AsString(EnumFormat.Description))); - CreateMap() .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); @@ -38,8 +37,7 @@ public AutoMapperConfig() .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) .ForMember(p => p.Usuarios, opt => opt.Ignore()); - CreateMap() - .ForMember(model => model.Permissoes, opt => opt.MapFrom(p => p.Permissoes)); + CreateMap(); } } } \ No newline at end of file diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index ef5a6a3..7984aa5 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -26,7 +26,6 @@ public Perfil CriarPerfil(Perfil perfil, List permissoes) foreach(var permissao in permissoes) { var novoPermissaoPerfil = perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); - novoPerfil.PerfilPermissoes!.Add(novoPermissaoPerfil); } dbContext.SaveChanges(); @@ -63,7 +62,6 @@ public async Task EditarPerfil(Perfil perfil, List permissoes foreach(var permissao in permissoes) { var novoPerfilPermissao = perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); - perfilDb.PerfilPermissoes!.Add(novoPerfilPermissao); } dbContext.SaveChanges(); From d919e33c11a7ad03de895dc443ce86a8f1f81c0f Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sat, 21 Oct 2023 18:15:37 -0300 Subject: [PATCH 065/137] feat: adiciona a descricao no retorno da listagem de permissoes --- api/PermissaoModel.cs | 2 +- .../Interfaces/IPermissaoRepositorio.cs | 2 +- app/Repositorios/PermissaoRepositorio.cs | 22 ++++++++++++++++--- app/Services/Mapper.cs | 1 - 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/api/PermissaoModel.cs b/api/PermissaoModel.cs index 7477827..6511841 100644 --- a/api/PermissaoModel.cs +++ b/api/PermissaoModel.cs @@ -3,6 +3,6 @@ namespace api public class PermissaoModel { public string Categoria { get; set; } - public List Permisoes { get; set; } + public List Permisoes { get; set; } } } \ No newline at end of file diff --git a/app/Repositorios/Interfaces/IPermissaoRepositorio.cs b/app/Repositorios/Interfaces/IPermissaoRepositorio.cs index b2b7136..f3d7ffa 100644 --- a/app/Repositorios/Interfaces/IPermissaoRepositorio.cs +++ b/app/Repositorios/Interfaces/IPermissaoRepositorio.cs @@ -5,6 +5,6 @@ namespace app.Repositorios.Interfaces public interface IPermissaoRepositorio { public List ObterCategorias(); - public List ObterPermissoesPortCategoria(string categoria); + public List ObterPermissoesPortCategoria(string categoria); } } \ No newline at end of file diff --git a/app/Repositorios/PermissaoRepositorio.cs b/app/Repositorios/PermissaoRepositorio.cs index b741c5e..6469207 100644 --- a/app/Repositorios/PermissaoRepositorio.cs +++ b/app/Repositorios/PermissaoRepositorio.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using api; using app.Repositorios.Interfaces; +using EnumsNET; namespace app.Repositorios { @@ -25,10 +26,25 @@ public List ObterCategorias() return categorias.ToList(); } - public List ObterPermissoesPortCategoria(string categoria) - { + public List ObterPermissoesPortCategoria(string categoria) + { var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern).ToString()); - return permissoes.ToList(); + + List listaDetalhada = new(); + //List> listaDetalhada = new(); + + foreach(var permissao in permissoes) + { + var par = new string[2] + { + permissao.ToString(), + permissao.AsString(EnumFormat.Description) + }; + + listaDetalhada.Add(par); + } + + return listaDetalhada; } } } \ No newline at end of file diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 2ba5a78..14f442e 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -5,7 +5,6 @@ using api; using EnumsNET; using api.Perfis; -using System.Text.RegularExpressions; namespace app.Services.Mapper { From ab57b0deaa681d2eb108c01200afe9ce40f0e00b Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 20:05:27 -0300 Subject: [PATCH 066/137] fix: arruma valores da permissao --- api/Enums.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index a6b98bb..efbcd4b 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -73,12 +73,12 @@ public enum Permissao [Description("Visualizar Escola")] EscolaVisualizar = 1003, - [Description("Cadastrar Empresa")] - EmpresaCadastrar = 2000, - [Description("Editar Empresa")] - EmpresaEditar = 2001, - [Description("Remover Empresa")] - EmpresaRemover = 2002, + //[Description("Cadastrar Empresa")] + //EmpresaCadastrar = 2000, + //[Description("Editar Empresa")] + //EmpresaEditar = 2001, + //[Description("Remover Empresa")] + //EmpresaRemover = 2002, [Description("Cadastrar Perfil de Usuário")] PerfilCadastrar = 3000, @@ -93,9 +93,11 @@ public enum Permissao UpsCalcularSinistro = 5000, [Description("Caluclar UPS de escolas")] UpsCalcularEscola = 5001, + [Description("Cadastrar rodovia")] - RodoviaCadastrar = 5002, + RodoviaCadastrar = 6000, + [Description("Cadastrar sinistro")] - SinistroCadastrar = 5003, + SinistroCadastrar = 7000, } } From 405a1082145303d64ead392debcac5ed314734a6 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 20:07:05 -0300 Subject: [PATCH 067/137] chore: adiciona configuracao para desabilitar autenticacao --- app/Services/UsuarioService.cs | 4 ++-- auth/AuthConfig.cs | 1 + auth/AuthService.cs | 3 +++ auth/AuthStartup.cs | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 877d625..eacaf85 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -162,7 +162,7 @@ private async Task CriarTokenAsync(Usuario usuario) { Id = usuario.Id, Name = usuario.Nome, - Permissions = usuario.Perfil?.Permissoes?.ToList(), + Permissions = usuario.Perfil?.Permissoes?.ToList() ?? new(), }); var (tokenAtualizacao, tokenAtualizacaoExpiracao) = autenticacaoService.GenerateRefreshToken(); @@ -173,7 +173,7 @@ private async Task CriarTokenAsync(Usuario usuario) return new LoginModel() { - Token = token, + Token = "Bearer " + token, ExpiraEm = expiraEm, TokenAtualizacao = tokenAtualizacao, Permissoes = usuario.Perfil?.Permissoes?.ToList(), diff --git a/auth/AuthConfig.cs b/auth/AuthConfig.cs index aa11cce..e964e46 100644 --- a/auth/AuthConfig.cs +++ b/auth/AuthConfig.cs @@ -2,6 +2,7 @@ { public class AuthConfig { + public bool Enabled { get; set; } = false; public string Key { get; set; } public string Issuer { get; set; } public string Audience { get; set; } diff --git a/auth/AuthService.cs b/auth/AuthService.cs index 98d57d3..55602d5 100644 --- a/auth/AuthService.cs +++ b/auth/AuthService.cs @@ -23,6 +23,9 @@ public AuthService(IOptions authConfig) 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})"); } diff --git a/auth/AuthStartup.cs b/auth/AuthStartup.cs index 9f6df35..b71ee15 100644 --- a/auth/AuthStartup.cs +++ b/auth/AuthStartup.cs @@ -1,5 +1,6 @@ using app.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -51,6 +52,11 @@ public static void AddAuth(this IServiceCollection services, IConfiguration conf 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) @@ -87,4 +93,15 @@ public static void AddAuthSwagger(this IServiceCollection services, IConfigurati }); } } + + public class AllowAnonymous : IAuthorizationHandler + { + public Task HandleAsync(AuthorizationHandlerContext context) + { + foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList()) + context.Succeed(requirement); + + return Task.CompletedTask; + } + } } From 7e5102e254ee5f0016170ec11813fc8da2a2674b Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 20:16:35 -0300 Subject: [PATCH 068/137] fix: adiciona exlusoes do sonar --- app/app.csproj | 71 ++++++++++++++++++++++++++---------------------- auth/auth.csproj | 2 +- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/app/app.csproj b/app/app.csproj index d9f6155..c36eb53 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -1,39 +1,44 @@ - - net6.0 - enable - enable - 458d8e9b-3d94-4e66-8b4d-cd89daee7157 - + + net6.0 + enable + enable + 458d8e9b-3d94-4e66-8b4d-cd89daee7157 + - - - - - - - - - - - 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 + + + + + + + - - + + - - + + + + + Migrations\**, DI\**, Program.cs + + diff --git a/auth/auth.csproj b/auth/auth.csproj index 2a2063b..b4dfccc 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,7 +7,7 @@ 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.3 + 1.0.4 DnitEpsFga.auth From bc650e58013e47b0be42957276b19f55903ac3fe Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 21:02:10 -0300 Subject: [PATCH 069/137] chore: adiciona todas as permissoes quando a autenticacao tiver desabilitada --- app/Services/UsuarioService.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index eacaf85..af2a3bc 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -7,6 +7,7 @@ using app.Entidades; using api; using auth; +using Microsoft.Extensions.Options; namespace app.Services { @@ -19,6 +20,7 @@ public class UsuarioService : IUsuarioService private readonly IConfiguration configuration; private readonly AppDbContext dbContext; private readonly AuthService autenticacaoService; + private readonly AuthConfig authConfig; public UsuarioService ( @@ -27,7 +29,8 @@ public UsuarioService IEmailService emailService, IConfiguration configuration, AppDbContext dbContext, - AuthService autenticacaoService + AuthService autenticacaoService, + IOptions authConfig ) { this.usuarioRepositorio = usuarioRepositorio; @@ -36,6 +39,7 @@ AuthService autenticacaoService this.configuration = configuration; this.dbContext = dbContext; this.autenticacaoService = autenticacaoService; + this.authConfig = authConfig.Va; } public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) @@ -158,11 +162,16 @@ public async Task AtualizarTokenAsync(AtualizarTokenDto atualizarTok private async Task CriarTokenAsync(Usuario usuario) { + var permissoes = usuario.Perfil?.Permissoes?.ToList() ?? new(); + + if (!authConfig.Enabled) // || usuario.Perfil.Tipo == TipoPerfil.Administrado + permissoes = Enum.GetValues().ToList(); + var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel { Id = usuario.Id, Name = usuario.Nome, - Permissions = usuario.Perfil?.Permissoes?.ToList() ?? new(), + Permissions = permissoes, }); var (tokenAtualizacao, tokenAtualizacaoExpiracao) = autenticacaoService.GenerateRefreshToken(); From c7901c23a4f0302b3cf0e031a1299a5f928868f4 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 22:11:15 -0300 Subject: [PATCH 070/137] feat: adiciona listagem de permissao do usuario --- api/Enums.cs | 4 +++- app/Controllers/UsuarioController.cs | 8 ++++++++ app/Services/Interfaces/IUsuarioService.cs | 2 ++ app/Services/UsuarioService.cs | 14 +++++++++++--- auth/AuthService.cs | 5 +++++ auth/auth.csproj | 2 +- 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index efbcd4b..2fcca48 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -91,8 +91,10 @@ public enum Permissao [Description("Calcular UPS de sinistros")] UpsCalcularSinistro = 5000, - [Description("Caluclar UPS de escolas")] + [Description("Calcular UPS de escolas")] UpsCalcularEscola = 5001, + [Description("Visualizar UPS")] + UpsVisualizar = 5002, [Description("Cadastrar rodovia")] RodoviaCadastrar = 6000, diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 3259bd1..b07005c 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -51,6 +51,14 @@ public async Task Logar([FromBody] UsuarioDTO usuarioDTO) } } + [HttpGet("permissoes")] + [Authorize] + public async Task> ListarPermissoes() + { + var userId = authService.GetUserId(User); + return await usuarioService.ListarPermissoesAsync(userId); + } + [HttpPost("atualizarToken")] public async Task AtualizarToken([FromBody] AtualizarTokenDto atualizarTokenDto) { diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index 9288225..bf8cd4c 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -1,5 +1,6 @@ using api.Usuarios; using api.Senhas; +using api; namespace app.Services.Interfaces { @@ -12,5 +13,6 @@ public interface IUsuarioService public Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); + Task> ListarPermissoesAsync(int userId); } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index af2a3bc..e32cb95 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -39,7 +39,7 @@ IOptions authConfig this.configuration = configuration; this.dbContext = dbContext; this.autenticacaoService = autenticacaoService; - this.authConfig = authConfig.Va; + this.authConfig = authConfig.Value; } public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) @@ -164,9 +164,11 @@ private async Task CriarTokenAsync(Usuario usuario) { var permissoes = usuario.Perfil?.Permissoes?.ToList() ?? new(); - if (!authConfig.Enabled) // || usuario.Perfil.Tipo == TipoPerfil.Administrado + if (!authConfig.Enabled) // || usuario.Perfil.Tipo == TipoPerfil.Administrador permissoes = Enum.GetValues().ToList(); + permissoes = new() { Permissao.EscolaVisualizar, Permissao.UpsVisualizar, Permissao.EscolaCadastrar }; + var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel { Id = usuario.Id, @@ -185,8 +187,14 @@ private async Task CriarTokenAsync(Usuario usuario) Token = "Bearer " + token, ExpiraEm = expiraEm, TokenAtualizacao = tokenAtualizacao, - Permissoes = usuario.Perfil?.Permissoes?.ToList(), + Permissoes = permissoes, }; } + + public async Task> ListarPermissoesAsync(int userId) + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(userId, includePerfil: true); + return usuario!.Perfil?.Permissoes?.ToList() ?? new(); + } } } diff --git a/auth/AuthService.cs b/auth/AuthService.cs index 55602d5..f30b93f 100644 --- a/auth/AuthService.cs +++ b/auth/AuthService.cs @@ -47,6 +47,11 @@ public int GetUserId(string token) 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; diff --git a/auth/auth.csproj b/auth/auth.csproj index b4dfccc..8935c8d 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,7 +7,7 @@ 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.4 + 1.0.5 DnitEpsFga.auth From 33f4bec977eab1fd9c0171d831f25bba347e38d5 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 22:57:21 -0300 Subject: [PATCH 071/137] refactor: testes --- api/Usuarios/UsuarioModel.cs | 1 + app/Controllers/UsuarioController.cs | 16 +-- app/Entidades/Usuario.cs | 2 +- app/Services/Mapper.cs | 3 +- test/UsuarioControllerTest.cs | 24 ++-- test/UsuarioRepositorioTest.cs | 42 +++--- test/UsuarioServiceTest.cs | 200 +++++++++------------------ 7 files changed, 106 insertions(+), 182 deletions(-) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index 555b878..0f6a830 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -12,5 +12,6 @@ public class UsuarioModel public string Email { get; set; } public string Senha { get; set; } public string Nome { get; set; } + public string Cnpj { get; set; } } } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index b07005c..434d2c1 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -2,10 +2,10 @@ using api.Senhas; using Microsoft.AspNetCore.Mvc; using app.Services.Interfaces; -using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authorization; using app.Services; using api; +using System.Data.Common; namespace app.Controllers { @@ -25,14 +25,6 @@ AuthService authService this.authService = authService; } - [HttpGet("auth/teste")] - [Authorize] - public int Teste() - { - authService.Require(User, Permissao.PerfilEditar); - return 42; - } - [HttpPost("login")] public async Task Logar([FromBody] UsuarioDTO usuarioDTO) { @@ -75,11 +67,11 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua return StatusCode(201, new NoContentResult()); } - catch (DbUpdateException) + catch (DbException) { return Conflict("Usuário já cadastrado."); } - catch (Exception) + catch (Exception ex) { return StatusCode(500, "Houve um erro interno no servidor."); } @@ -94,7 +86,7 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) return StatusCode(201, new NoContentResult()); } - catch (DbUpdateException) + catch (DbException) { return Conflict("Usuário já cadastrado."); } diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 9174d24..5fbb022 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -24,7 +24,7 @@ public class Usuario public List RedefinicaoSenha { get; set; } - public List Empresas { get; set; } + public List? Empresas { get; set; } public Guid? PerfilId { get; set; } public Perfil? Perfil { get; set; } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 6bd0e07..1cfe3d4 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -15,7 +15,8 @@ public AutoMapperConfig() CreateMap() .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); - CreateMap(); + CreateMap() + .ForMember(dto => dto.Cnpj, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); CreateMap() .ForMember(model => model.Id, opt => opt.MapFrom(uf => (int)uf)) diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index f0222f2..642c102 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -20,7 +20,7 @@ public class UsuarioControllerTest [Fact] public void Logar_QuandoLoginForValidado_DeveRetornarOk() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -36,7 +36,7 @@ public void Logar_QuandoLoginForValidado_DeveRetornarOk() [Fact] public void Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -53,7 +53,7 @@ public void Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() [Fact] public void Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -70,7 +70,7 @@ public void Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() [Fact] public async void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -86,9 +86,9 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCr } [Fact] - public async void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() + public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -107,7 +107,7 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConfli [Fact] public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -128,7 +128,7 @@ public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInte [Fact] public void CadastrarUsuarioTerceiro_QuandoUsuarioForCadastrado_DeveRetornarCreated() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -146,7 +146,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(); @@ -165,7 +165,7 @@ 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(); @@ -186,7 +186,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern [Fact] public async void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); @@ -202,7 +202,7 @@ public async void RecuperarSenha_QuandoRecuperacaoForValidada_DeveRetornarOk() [Fact] public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsync() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 0ca7507..4bb5919 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -8,6 +8,7 @@ using Xunit.Microsoft.DependencyInjection.Abstracts; using System.Linq; using AutoMapper; +using System.Threading.Tasks; namespace test { @@ -25,9 +26,9 @@ public UsuarioRepositorioTest(ITestOutputHelper testOutputHelper, Base fixture) } [Fact] - public async void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() + public async Task ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespondente() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); repositorio.CadastrarUsuarioDnit(usuarioDNIT); @@ -41,16 +42,16 @@ public async void ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespo } [Fact] - public async 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 dbContext.SaveChangesAsync(); var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault(); - UsuarioDTO usuarioObtidoDTO = mapper.Map(usuarioObtido); + var usuarioObtidoDTO = mapper.Map(usuarioObtido); Assert.Equal(usuarioDNIT.Email, usuarioObtidoDTO.Email); Assert.Equal(usuarioDNIT.Senha, usuarioObtidoDTO.Senha); @@ -59,9 +60,9 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsua } [Fact] - public async void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() + public async Task TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsuario() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); repositorio.CadastrarUsuarioDnit(usuarioDNIT); @@ -78,10 +79,10 @@ public async void TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsua } [Fact] - public async 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(); @@ -99,10 +100,10 @@ public async void ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEm } [Fact] - public async 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(); @@ -124,9 +125,9 @@ public async void RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUu [Fact] - public async void CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() + public async Task CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrarUsuarioComDadosPassados() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); var empresa = new Empresa @@ -142,15 +143,14 @@ public async void CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrar await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email); - UsuarioDTO usuarioObtidoDTO = mapper.Map(usuarioObtido); - Assert.Equal(usuarioTerceiro.Email, usuarioObtidoDTO.Email); - Assert.Equal(usuarioTerceiro.Senha, usuarioObtidoDTO.Senha); - Assert.Equal(usuarioTerceiro.Nome, usuarioObtidoDTO.Nome); - Assert.Equal(usuarioTerceiro.CNPJ, usuarioObtidoDTO.CNPJ); + Assert.Equal(usuarioTerceiro.Email, usuarioObtido.Email); + Assert.Equal(usuarioTerceiro.Senha, usuarioObtido.Senha); + Assert.Equal(usuarioTerceiro.Nome, usuarioObtido.Nome); + Assert.Equal(usuarioTerceiro.CNPJ, usuarioObtido.Cnpj); } - public void Dispose() + public new void Dispose() { dbContext.RemoveRange(dbContext.Usuario); dbContext.RemoveRange(dbContext.Empresa); diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 8379d2e..bd59d8c 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -12,7 +12,9 @@ using app.Entidades; using Xunit.Abstractions; using Xunit.Microsoft.DependencyInjection.Abstracts; - +using Microsoft.Extensions.Options; +using auth; +using System.Threading.Tasks; namespace test { @@ -20,30 +22,39 @@ public class UsuarioServiceTest : TestBed, IDisposable { AppDbContext dbContext; + Mock mapper; + Mock usuarioRepositorio; + Mock emailService; + Mock configuration; + AuthService authService; + Mock> authConfig; + IUsuarioService usuarioServiceMock; public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { dbContext = fixture.GetService(testOutputHelper)!; + + mapper = new Mock(); + usuarioRepositorio = new Mock(); + emailService = new Mock(); + configuration = new Mock(); + authConfig = new Mock>(); + authService = new AuthService(authConfig.Object); + + + usuarioServiceMock = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, authService, authConfig.Object); } [Fact] - public async 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, dbContext, null); - - await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); + await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); @@ -56,18 +67,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, dbContext, null); - - usuarioService.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); + usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); @@ -75,26 +79,17 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr } [Fact] - public async 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.")); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); + var cadastrarUsuario = async () => await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - Action cadastrarUsuario = async () => await usuarioService.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - - Exception exception = Assert.Throws(cadastrarUsuario); + var exception = await Assert.ThrowsAsync(cadastrarUsuario); Assert.Equal("Email já cadastrado.", exception.Message); } @@ -102,64 +97,41 @@ public async void CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_De [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, dbContext, null); + 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(); - UsuarioModel 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, dbContext, null); - - Assert.True(usuarioService.ValidaLogin(usuarioDnitDTO)); + Assert.True(usuarioServiceMock.ValidaLogin(usuarioDnitDTO)); } [Fact] public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioModel 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, dbContext, null); - - Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); + Action validarLogin = () => usuarioServiceMock.ValidaLogin(usuarioDnitDTO); Assert.Throws(validarLogin); } @@ -167,20 +139,12 @@ public void ValidaLogin_QuandoUsuarioInvalidoForPassado_NaoDeveRealizarLogin() [Fact] public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin() { - UsuarioStub usuarioStub = new(); - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioModel 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, dbContext, null); - - Action validarLogin = () => usuarioService.ValidaLogin(usuarioDnitDTO); + Action validarLogin = () => usuarioServiceMock.ValidaLogin(usuarioDnitDTO); Assert.Throws(validarLogin); } @@ -188,66 +152,44 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( [Fact] public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSenha() { - 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(usuarioDNIT); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); - - usuarioService.RecuperarSenha(usuarioDnitDTO); + usuarioServiceMock.RecuperarSenha(usuarioDnitDTO); emailService.Verify(x => x.EnviarEmail(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public async 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, dbContext, null); - - Action validarLogin = async () => await 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(); - RedefinicaoSenhaModel 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); @@ -257,28 +199,20 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(emailRedefinicaoSenha); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); - - 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(); - RedefinicaoSenhaModel 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); @@ -288,11 +222,7 @@ public void TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() usuarioRepositorio.Setup(x => x.ObterEmailRedefinicaoSenha(It.IsAny())).Returns(value: null); - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, null); - - 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); From 5f35ffbbc6a6c353f629beae9d937a86117b2775 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sat, 21 Oct 2023 23:56:02 -0300 Subject: [PATCH 072/137] test: adiciona teste do auth --- .gitignore | 4 +- test/AuthTest.cs | 141 ++++++++++++++++++++++++++++++++++++++++++ test/Fixtures/Base.cs | 3 + 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 test/AuthTest.cs diff --git a/.gitignore b/.gitignore index 2ba547e..d11c8d5 100644 --- a/.gitignore +++ b/.gitignore @@ -365,4 +365,6 @@ MigrationBackup/ FodyWeavers.xsd /dominio/UsuarioDNIT.cs -.idea/ \ No newline at end of file +.idea/ + +report/ diff --git a/test/AuthTest.cs b/test/AuthTest.cs new file mode 100644 index 0000000..f75045c --- /dev/null +++ b/test/AuthTest.cs @@ -0,0 +1,141 @@ +using api; +using app.Services; +using auth; +using Microsoft.Extensions.Options; +using Moq; +using System.Buffers.Text; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using test.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace test +{ + public class AuthTest + { + + AuthService authService; + AuthConfig authConfig; + + public AuthTest() + { + 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(permissaos: new() { Permissao.RodoviaCadastrar }); + Assert.True(authService.HasPermission(ObterClaim(token), Permissao.RodoviaCadastrar)); + } + + [Fact] + public void HasPermission_QuandoNaoTiverPermissao_DeveRetornarFalse() + { + var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + Assert.False(authService.HasPermission(ObterClaim(token), Permissao.PerfilVisualizar)); + } + + [Fact] + public void Require_QuandoTiverPermissao_DevePassar() + { + var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + authService.Require(ObterClaim(token), Permissao.RodoviaCadastrar); + Assert.True(true); + } + + [Fact] + public void Require_QuandoTiverDesabilitado_DevePassar() + { + authConfig.Enabled = false; + var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + authService.Require(ObterClaim(token), Permissao.PerfilVisualizar); + Assert.True(true); + } + + [Fact] + public void Require_QuandoNaoTiverPermissao_DeveLancarExcecao() + { + var (token, _) = ObterTokenValido(permissaos: 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? permissaos = null) + { + var usuario = new AuthUserModel() + { + Id = id, + Name = "Test", + Permissions = permissaos ?? new() { Permissao.EscolaCadastrar }, + }; + return authService.GenerateToken(usuario); + } + } +} diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs index d41588b..0303ae3 100644 --- a/test/Fixtures/Base.cs +++ b/test/Fixtures/Base.cs @@ -5,6 +5,7 @@ using app.Services; using app.Services.Interfaces; using app.Services.Mapper; +using auth; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -35,6 +36,8 @@ protected override void AddServices(IServiceCollection services, IConfiguration? // Controllers services.AddScoped(); services.AddScoped(); + + services.AddAuth(configuration); } protected override ValueTask DisposeAsyncCore() => new(); From a9e375bb41e867e27122f7d5fc4e4d96d98e09a6 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:03:04 -0300 Subject: [PATCH 073/137] feat: adiciona o campo tipo no registro de perfil --- api/Enums.cs | 7 + app/Entidades/Perfil.cs | 3 + .../20231022000223_PerfilTipo.Designer.cs | 235 ++++++++++++++++++ app/Migrations/20231022000223_PerfilTipo.cs | 83 +++++++ app/Migrations/AppDbContextModelSnapshot.cs | 3 + 5 files changed, 331 insertions(+) create mode 100644 app/Migrations/20231022000223_PerfilTipo.Designer.cs create mode 100644 app/Migrations/20231022000223_PerfilTipo.cs diff --git a/api/Enums.cs b/api/Enums.cs index a6b98bb..112c099 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -98,4 +98,11 @@ public enum Permissao [Description("Cadastrar sinistro")] SinistroCadastrar = 5003, } + + public enum TipoPerfil + { + Basico = 1, + Administrador, + Customizavel + } } diff --git a/app/Entidades/Perfil.cs b/app/Entidades/Perfil.cs index 7328961..1db5b6a 100644 --- a/app/Entidades/Perfil.cs +++ b/app/Entidades/Perfil.cs @@ -11,6 +11,9 @@ public class Perfil [Required, MaxLength(200)] public string Nome { get; set; } + [Required] + public TipoPerfil Tipo { get; set; } = TipoPerfil.Customizavel; + public List? PerfilPermissoes { get; set; } public IEnumerable? Permissoes => PerfilPermissoes?.Select(p => p.Permissao); 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs index 77c8df7..6181e9a 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -70,6 +70,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(200) .HasColumnType("character varying(200)"); + b.Property("Tipo") + .HasColumnType("integer"); + b.HasKey("Id"); b.HasIndex("Nome") From fc51bc3bce28767aca150d84d2115ae01c581056 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:04:34 -0300 Subject: [PATCH 074/137] feat: padroniza as models --- api/Perfis/PerfilModel.cs | 5 ++++- api/PermissaoModel.cs | 8 -------- api/Permissoes/CategoriaPermissaoModel.cs | 10 ++++++++++ api/Permissoes/PermissaoModel.cs | 8 ++++++++ 4 files changed, 22 insertions(+), 9 deletions(-) delete mode 100644 api/PermissaoModel.cs create mode 100644 api/Permissoes/CategoriaPermissaoModel.cs create mode 100644 api/Permissoes/PermissaoModel.cs diff --git a/api/Perfis/PerfilModel.cs b/api/Perfis/PerfilModel.cs index 3df1d86..8e1d07e 100644 --- a/api/Perfis/PerfilModel.cs +++ b/api/Perfis/PerfilModel.cs @@ -1,9 +1,12 @@ +using api.Permissoes; + namespace api.Perfis { public class PerfilModel { public Guid Id { get; set; } public string Nome { get; set; } - public List Permissoes { get; set; } + public TipoPerfil Tipo { get; set; } + public List Permissoes { get; set; } } } diff --git a/api/PermissaoModel.cs b/api/PermissaoModel.cs deleted file mode 100644 index 6511841..0000000 --- a/api/PermissaoModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace api -{ - public class PermissaoModel - { - public string Categoria { get; set; } - public List Permisoes { get; set; } - } -} \ No newline at end of file diff --git a/api/Permissoes/CategoriaPermissaoModel.cs b/api/Permissoes/CategoriaPermissaoModel.cs new file mode 100644 index 0000000..4065fb9 --- /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 Permisoes { 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 From 70d69f902b7385ce792d141677dbb3155a182c39 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:26:09 -0300 Subject: [PATCH 075/137] fix: troca PermissaoRepositorio para PermissaoService --- app/DI/RepositoriosConfig.cs | 1 - app/DI/ServicesConfig.cs | 1 + .../Interfaces/IPermissaoRepositorio.cs | 10 ------ app/Services/Interfaces/IPermissaoService.cs | 10 ++++++ .../PermissaoService.cs} | 31 +++++++------------ test/Fixtures/Base.cs | 2 +- 6 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 app/Repositorios/Interfaces/IPermissaoRepositorio.cs create mode 100644 app/Services/Interfaces/IPermissaoService.cs rename app/{Repositorios/PermissaoRepositorio.cs => Services/PermissaoService.cs} (51%) diff --git a/app/DI/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index 8c86e99..f5c7623 100644 --- a/app/DI/RepositoriosConfig.cs +++ b/app/DI/RepositoriosConfig.cs @@ -10,7 +10,6 @@ public static void AddConfigRepositorios(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); } } } diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index ab1b8eb..af8fda0 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -18,6 +18,7 @@ public static void AddConfigServices(this IServiceCollection services, IConfigur services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddAuth(configuration); } diff --git a/app/Repositorios/Interfaces/IPermissaoRepositorio.cs b/app/Repositorios/Interfaces/IPermissaoRepositorio.cs deleted file mode 100644 index f3d7ffa..0000000 --- a/app/Repositorios/Interfaces/IPermissaoRepositorio.cs +++ /dev/null @@ -1,10 +0,0 @@ -using api; - -namespace app.Repositorios.Interfaces -{ - public interface IPermissaoRepositorio - { - public List ObterCategorias(); - public List ObterPermissoesPortCategoria(string categoria); - } -} \ 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..ce42cc5 --- /dev/null +++ b/app/Services/Interfaces/IPermissaoService.cs @@ -0,0 +1,10 @@ +using api.Permissoes; + +namespace app.Services.Interfaces +{ + public interface IPermissaoService + { + public List ObterCategorias(); + public List ObterPermissoesPortCategoria(string categoria); + } +} \ No newline at end of file diff --git a/app/Repositorios/PermissaoRepositorio.cs b/app/Services/PermissaoService.cs similarity index 51% rename from app/Repositorios/PermissaoRepositorio.cs rename to app/Services/PermissaoService.cs index 6469207..0340e5e 100644 --- a/app/Repositorios/PermissaoRepositorio.cs +++ b/app/Services/PermissaoService.cs @@ -1,14 +1,15 @@ using System.Text.RegularExpressions; using api; -using app.Repositorios.Interfaces; +using api.Permissoes; +using app.Services.Interfaces; using EnumsNET; -namespace app.Repositorios +namespace app.Services { - public class PermissaoRepositorio : IPermissaoRepositorio + public class PermissaoService : IPermissaoService { private const string pattern = @"^([A-Z][a-z]+)"; - public PermissaoRepositorio() + public PermissaoService() { } @@ -26,25 +27,15 @@ public List ObterCategorias() return categorias.ToList(); } - public List ObterPermissoesPortCategoria(string categoria) + public List ObterPermissoesPortCategoria(string categoria) { - var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern).ToString()); - - List listaDetalhada = new(); - //List> listaDetalhada = new(); + var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern).ToString()); - foreach(var permissao in permissoes) - { - var par = new string[2] + return permissoes.Select(p => new PermissaoModel { - permissao.ToString(), - permissao.AsString(EnumFormat.Description) - }; - - listaDetalhada.Add(par); - } - - return listaDetalhada; + Codigo = p, + Descricao = p.AsString(EnumFormat.Description)! + }).ToList(); } } } \ No newline at end of file diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs index ba9b7b1..58950ca 100644 --- a/test/Fixtures/Base.cs +++ b/test/Fixtures/Base.cs @@ -27,12 +27,12 @@ protected override void AddServices(IServiceCollection services, IConfiguration? services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); // Services services.AddScoped(); services.AddScoped(); services.AddAutoMapper(typeof(AutoMapperConfig)); + services.AddScoped(); // Controllers services.AddScoped(); From 28961a2d22c16b292ee3dfa74fb8de4d1a9fda37 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:27:55 -0300 Subject: [PATCH 076/137] fix: ajusta o mapper para a nova padronizacao --- app/Services/Mapper.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 14f442e..cc99ed0 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -5,6 +5,7 @@ using api; using EnumsNET; using api.Perfis; +using api.Permissoes; namespace app.Services.Mapper { @@ -36,7 +37,16 @@ public AutoMapperConfig() .ForMember(p => p.PerfilPermissoes, opt => opt.Ignore()) .ForMember(p => p.Usuarios, opt => opt.Ignore()); - CreateMap(); + CreateMap() + .ForMember(model => model.Permissoes, opt => opt.MapFrom + ( + perf => perf.Permissoes.Select(p => new PermissaoModel + { + Codigo = p, + Descricao = p.AsString(EnumFormat.Description)! + }).ToList() + ) + ); } } } \ No newline at end of file From d4795b7370edc8ef8546f3d76a776a55826b74a9 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:29:45 -0300 Subject: [PATCH 077/137] feat: simplifica o codigo e autentica a rota de listar permissoes --- app/Controllers/DominioController.cs | 45 +++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index 325e56c..52c4832 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -1,11 +1,10 @@ -using app.Entidades; -using api; +using api; using Microsoft.AspNetCore.Mvc; -using app.Repositorios; using app.Repositorios.Interfaces; -using app.Services; using app.Services.Interfaces; +using app.Services; using AutoMapper; +using Microsoft.AspNetCore.Authorization; namespace app.Controllers { @@ -14,15 +13,22 @@ namespace app.Controllers public class DominioController : ControllerBase { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; - - private readonly IPermissaoRepositorio permissaoRepositorio; - + private readonly IPermissaoService PermissaoService; private readonly IMapper mapper; + private readonly AuthService authService; + - public DominioController(IUnidadeFederativaRepositorio unidadeFederativaRepositorio, IMapper mapper, IPermissaoRepositorio permissaoRepositorio) + public DominioController + ( + IUnidadeFederativaRepositorio unidadeFederativaRepositorio, + IMapper mapper, + IPermissaoService PermissaoService, + AuthService authService + ) { this.unidadeFederativaRepositorio = unidadeFederativaRepositorio; - this.permissaoRepositorio = permissaoRepositorio; + this.PermissaoService = PermissaoService; + this.authService = authService; this.mapper = mapper; } @@ -34,21 +40,20 @@ public IActionResult ObterLista() return new OkObjectResult(listaUnidadeFederativa); } + [Authorize] [HttpGet("permissoes")] public IActionResult ObterListaDePermissoes() { - var categorias = permissaoRepositorio.ObterCategorias(); - - List lista = new(); - foreach(var categoria in categorias) + authService.Require(User, Permissao.PerfilVisualizar); + + var categorias = PermissaoService.ObterCategorias(); + + var lista = categorias.ConvertAll(c => new CategoriaPermissaoModel { - PermissaoModel model = new () - { - Categoria = categoria, - Permisoes = permissaoRepositorio.ObterPermissoesPortCategoria(categoria) - }; - lista.Add(model); - } + Categoria = c, + Permisoes = PermissaoService.ObterPermissoesPortCategoria(c) + }); + return Ok(lista); } From 13dbf08dc5c33728cd0a14a602021b4211ced88c Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:31:43 -0300 Subject: [PATCH 078/137] feat: autentica as rotas de perfil --- app/Controllers/PerfilController.cs | 25 ++++++++++++++++++++----- app/Repositorios/PerfilRepositorio.cs | 7 +++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index f1eae59..3fe5d99 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -1,8 +1,10 @@ +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; @@ -23,10 +25,11 @@ public PerfilController(IPerfilService perfilService, AuthService authService, I this.mapper = mapper; } + [Authorize] [HttpPost()] public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) { - //verificar se o user tem permissão + authService.Require(User, Permissao.PerfilCadastrar); var perfil = mapper.Map(perfilDTO); @@ -44,10 +47,13 @@ public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) } } + [Authorize] [HttpPut("{id}")] public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perfilDTO) { - //verificar se o user tem permissão + + authService.Require(User, Permissao.PerfilEditar); + Perfil perfil = mapper.Map(perfilDTO); perfil.Id = id; @@ -69,10 +75,12 @@ public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perf } } + [Authorize] [HttpDelete("{id}")] public async Task ExcluirPerfil(Guid id) { - //verificar se o user tem permissao + authService.Require(User, Permissao.PerfilRemover); + try{ await perfilService.ExcluirPerfil(id); return Ok("Perfil excluido"); @@ -80,15 +88,22 @@ public async Task ExcluirPerfil(Guid id) catch(KeyNotFoundException){ return NotFound("Perfil não encontrado"); } + catch(InvalidOperationException e) + { + return StatusCode(400, e.Message); + } catch(Exception) { return StatusCode(500, "Houve um erro interno no servidor."); } } + [Authorize] [HttpGet()] public async Task ListarPerfis(int pageIndex, int pageSize) { + authService.Require(User, Permissao.PerfilVisualizar); + try { var pagina = await perfilService.ListarPerfisAsync(pageIndex, pageSize); @@ -97,9 +112,9 @@ public async Task ListarPerfis(int pageIndex, int pageSize) return Ok(paginaRetorno); } - catch(Exception) + catch(Exception e) { - return StatusCode(500, "Houve um erro interno no servidor."); + return StatusCode(500, e.Message + "\n" + e.StackTrace + "\nHouve um erro interno no servidor."); } } } diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index 2838253..e2a0dbe 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -1,6 +1,4 @@ -using System.Runtime.CompilerServices; using api; -using api.Perfis; using app.Entidades; using app.Repositorios.Interfaces; using Microsoft.EntityFrameworkCore; @@ -41,6 +39,11 @@ public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permis public void RemovePerfil(Perfil perfil) { + if(perfil.Tipo == TipoPerfil.Basico || perfil.Tipo == TipoPerfil.Administrador) + { + throw new InvalidOperationException("Esse Perfil não pode ser excluido."); + } + dbContext.Perfis.Remove(perfil); } From 9622fcabc3c410c1e2bc6ee693b1547e6bef972f Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 00:33:37 -0300 Subject: [PATCH 079/137] feat: ajusta as rotinas de usuario paro os tipos de perfil --- .../Interfaces/IUsuarioRepositorio.cs | 4 ++-- app/Repositorios/UsuarioRepositorio.cs | 21 ++++++++++++------- app/Services/UsuarioService.cs | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 08bfc7e..e08bd3d 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -11,7 +11,7 @@ public interface IUsuarioRepositorio void InserirDadosRecuperacao(string uuid, int idUsuario); string? ObterEmailRedefinicaoSenha(string uuid); void RemoverUuidRedefinicaoSenha(string uuid); - void CadastrarUsuarioDnit(UsuarioDnit usuario); - void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); + Task CadastrarUsuarioDnit(UsuarioDnit usuario); + Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index ffaed8b..ccd83e5 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -1,11 +1,9 @@ using app.Entidades; -using Dapper; using api.Usuarios; -using api.Senhas; +using api; using app.Repositorios.Interfaces; using Microsoft.EntityFrameworkCore; using AutoMapper; -using Npgsql; namespace app.Repositorios { @@ -47,14 +45,16 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) return await query.FirstOrDefaultAsync(); } - public void CadastrarUsuarioDnit(UsuarioDnit usuario) + public async Task CadastrarUsuarioDnit(UsuarioDnit usuario) { + var novoUsuario = new Usuario { Nome = usuario.Nome, Email = usuario.Email, Senha = usuario.Senha, - UfLotacao = usuario.UfLotacao + UfLotacao = usuario.UfLotacao, + Perfil = await RecuperaPerfilBasicoAsync() }; dbContext.Add(novoUsuario); @@ -100,7 +100,7 @@ public void InserirDadosRecuperacao(string uuid, int idUsuario) dbContext.RedefinicaoSenha.Add(newRs); } - public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) + public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) { var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); @@ -111,10 +111,17 @@ public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) Nome = usuarioTerceiro.Nome, Email = usuarioTerceiro.Email, Senha = usuarioTerceiro.Senha, - Empresas = empresas + Empresas = empresas, + Perfil = await RecuperaPerfilBasicoAsync() }; dbContext.Usuario.Add(novoUsuarioTerceiro); } + + private async Task RecuperaPerfilBasicoAsync() + { + return await dbContext.Perfis.Where(p => p.Tipo == TipoPerfil.Basico) + .FirstOrDefaultAsync(); + } } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 877d625..bfa909c 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -44,7 +44,7 @@ public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) usuario.Senha = EncriptarSenha(usuario.Senha); - usuarioRepositorio.CadastrarUsuarioDnit(usuario); + await usuarioRepositorio.CadastrarUsuarioDnit(usuario); await dbContext.SaveChangesAsync(); } From 9ca97565a6b870ecdf3f79d53bbc2d7671d981bd Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sun, 22 Oct 2023 01:25:01 -0300 Subject: [PATCH 080/137] test: adiciona teste do usuario controller --- app/Configuracoes/SenhaConfig.cs | 7 + app/Controllers/UsuarioController.cs | 7 +- app/DI/ConfiguracaoConfig.cs | 14 ++ app/Entidades/AppControllerBase.cs | 12 ++ app/Program.cs | 2 + .../UnidadeFederativaRepositorio.cs | 4 +- app/Repositorios/UsuarioRepositorio.cs | 3 - app/Services/Interfaces/IUsuarioService.cs | 12 +- app/Services/UsuarioService.cs | 9 +- app/app.csproj | 1 - app/appsettings.Development.json | 4 +- app/appsettings.json | 22 +-- auth/AuthConfig.cs | 2 +- auth/auth.csproj | 8 +- test/Stub/AppDbContextExtensions.cs | 57 +++++++ test/Stub/UsuarioStub.cs | 22 +++ test/UsuarioControllerTest.cs | 152 ++++++++++++------ test/UsuarioServiceTest.cs | 6 +- test/test.csproj | 63 ++++---- 19 files changed, 287 insertions(+), 120 deletions(-) create mode 100644 app/Configuracoes/SenhaConfig.cs create mode 100644 app/DI/ConfiguracaoConfig.cs create mode 100644 app/Entidades/AppControllerBase.cs create mode 100644 test/Stub/AppDbContextExtensions.cs 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/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 434d2c1..94565ab 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -6,12 +6,13 @@ using app.Services; using api; using System.Data.Common; +using app.Entidades; namespace app.Controllers { [ApiController] [Route("api/usuario")] - public class UsuarioController : ControllerBase + public class UsuarioController : AppControllerBase { private readonly IUsuarioService usuarioService; private readonly AuthService authService; @@ -47,7 +48,7 @@ public async Task Logar([FromBody] UsuarioDTO usuarioDTO) [Authorize] public async Task> ListarPermissoes() { - var userId = authService.GetUserId(User); + var userId = authService.GetUserId(Usuario); return await usuarioService.ListarPermissoesAsync(userId); } @@ -71,7 +72,7 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua { return Conflict("Usuário já cadastrado."); } - catch (Exception ex) + catch (Exception) { return StatusCode(500, "Houve um erro interno no servidor."); } 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/Entidades/AppControllerBase.cs b/app/Entidades/AppControllerBase.cs new file mode 100644 index 0000000..b666fa8 --- /dev/null +++ b/app/Entidades/AppControllerBase.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace app.Entidades +{ + public class AppControllerBase : ControllerBase + { + public ClaimsPrincipal? AppUsuario { get; set; } + + public ClaimsPrincipal Usuario => AppUsuario ?? User; + } +} diff --git a/app/Program.cs b/app/Program.cs index 5144a4a..793c30c 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -28,6 +28,8 @@ }); }); +builder.Services.AddConfiguracoes(builder.Configuration); + builder.Services.AddConfigServices(builder.Configuration); builder.Services.AddConfigRepositorios(); diff --git a/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs index c6afd27..9cddeea 100644 --- a/app/Repositorios/UnidadeFederativaRepositorio.cs +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -1,6 +1,4 @@ -using Dapper; -using app.Entidades; -using api; +using api; using app.Repositorios.Interfaces; using AutoMapper; diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index ffaed8b..f4fe861 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -1,11 +1,8 @@ using app.Entidades; -using Dapper; using api.Usuarios; -using api.Senhas; using app.Repositorios.Interfaces; using Microsoft.EntityFrameworkCore; using AutoMapper; -using Npgsql; namespace app.Repositorios { diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index bf8cd4c..08222d4 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -6,12 +6,12 @@ namespace app.Services.Interfaces { public interface IUsuarioService { - public Task AutenticarUsuarioAsync(string email, string senha); - public bool ValidaLogin(UsuarioDTO usuarioDTO); - public Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); - public Task RecuperarSenha(UsuarioDTO usuarioDto); - public Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); - public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); + 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); } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index e32cb95..7ba9a09 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -8,6 +8,7 @@ using api; using auth; using Microsoft.Extensions.Options; +using app.Configuracoes; namespace app.Services { @@ -17,7 +18,7 @@ public class UsuarioService : IUsuarioService private readonly IUsuarioRepositorio usuarioRepositorio; private readonly IMapper mapper; private readonly IEmailService emailService; - private readonly IConfiguration configuration; + private readonly SenhaConfig senhaConfig; private readonly AppDbContext dbContext; private readonly AuthService autenticacaoService; private readonly AuthConfig authConfig; @@ -27,7 +28,7 @@ public UsuarioService IUsuarioRepositorio usuarioRepositorio, IMapper mapper, IEmailService emailService, - IConfiguration configuration, + IOptions senhaConfig, AppDbContext dbContext, AuthService autenticacaoService, IOptions authConfig @@ -36,7 +37,7 @@ IOptions authConfig this.usuarioRepositorio = usuarioRepositorio; this.mapper = mapper; this.emailService = emailService; - this.configuration = configuration; + this.senhaConfig = senhaConfig.Value; this.dbContext = dbContext; this.autenticacaoService = autenticacaoService; this.authConfig = authConfig.Value; @@ -141,7 +142,7 @@ public async Task RecuperarSenha(UsuarioDTO usuarioDTO) } private string GerarLinkDeRecuperacao(string UuidAutenticacao) { - var baseUrl = configuration["RedefinirSenhaUrl"]; + var baseUrl = senhaConfig.RedefinirSenhaUrl; string link = $"{baseUrl}?token={UuidAutenticacao}"; diff --git a/app/app.csproj b/app/app.csproj index c36eb53..3288264 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -9,7 +9,6 @@ - diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 134ed99..934d6ff 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -8,7 +8,9 @@ "Microsoft.AspNetCore": "Warning" } }, - "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", + "Senha": { + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", + }, "Auth": { "Enabled": false, "Key": "chave secreta chave secreta chave secreta chave secreta chave secreta chave secreta", diff --git a/app/appsettings.json b/app/appsettings.json index 8ea10a2..c4a6e30 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": "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" + } + }, + "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 index e964e46..1159376 100644 --- a/auth/AuthConfig.cs +++ b/auth/AuthConfig.cs @@ -3,7 +3,7 @@ public class AuthConfig { public bool Enabled { get; set; } = false; - public string Key { get; set; } + 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; } diff --git a/auth/auth.csproj b/auth/auth.csproj index 8935c8d..dbaa2e6 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,7 +7,7 @@ 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.5 + 1.0.6 DnitEpsFga.auth @@ -20,4 +20,10 @@ + + + AuthExceptionHandler.cs + + + diff --git a/test/Stub/AppDbContextExtensions.cs b/test/Stub/AppDbContextExtensions.cs new file mode 100644 index 0000000..2e69a42 --- /dev/null +++ b/test/Stub/AppDbContextExtensions.cs @@ -0,0 +1,57 @@ +using app.Entidades; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 = api.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 = api.Permissao.EscolaCadastrar, + } + } + }; + usuario.Perfil = perfil; + context.Add(perfil); + } + + context.Add(usuario); + } + context.SaveChanges(); + return usuariosTeste; + } + } +} diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 326f264..8b22c68 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -1,10 +1,32 @@ using api.Usuarios; 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 { + public static IEnumerable Listar() + { + while (true) + { + yield return new TesteUsuarioStub() + { + Nome = "teste " + Random.Shared.Next().ToString(), + CNPJ = string.Join("", Enumerable.Range(0, 11).Select(_ => Random.Shared.Next() % 10)), + Email = $"teste{Random.Shared.Next()}@email.com", + Senha = $"teste_senha_{Random.Shared.Next()}", + }; + } + } + public UsuarioDTO RetornarUsuarioDnitDTO() { return new UsuarioDTO diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 642c102..e89185c 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -9,109 +9,149 @@ using test.Stub; using Xunit; using System.Threading.Tasks; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using app.Services; +using Microsoft.Extensions.Configuration; +using System.Linq; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using auth; namespace test { - public class UsuarioControllerTest + public class UsuarioControllerTest : TestBed, IDisposable { const int CREATED = 201; const int INTERNAL_SERVER_ERROR = 500; - [Fact] - public void Logar_QuandoLoginForValidado_DeveRetornarOk() + UsuarioController controller; + AppDbContext dbContext; + + public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { - var usuarioStub = new UsuarioStub(); - 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, null); + 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() { - var usuarioStub = new UsuarioStub(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - - Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new UnauthorizedAccessException()); + var usuario = dbContext.PopulaUsuarios(1).First(); - var controller = new UsuarioController(usuarioServiceMock.Object, null); + 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() { - var usuarioStub = new UsuarioStub(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - - Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.ValidaLogin(It.IsAny())).Throws(new KeyNotFoundException()); - - var controller = new UsuarioController(usuarioServiceMock.Object, null); + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var resultado = controller.Logar(usuarioDTO); + await AutenticarUsuario(usuario); + var permissoes = await controller.ListarPermissoes(); + Assert.NotEmpty(permissoes); + } - usuarioServiceMock.Verify(service => service.ValidaLogin(usuarioDTO), Times.Once); - Assert.IsType(resultado); + [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); } [Fact] - public async void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + public async Task AtualizarToken_QuandoTiverInvalido_DeveRetornarNovoToken() { - var usuarioStub = new UsuarioStub(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - Mock usuarioServiceMock = new(); + var login = await AutenticarUsuario(usuario); + var atualizarTokenDto = new AtualizarTokenDto + { + Token = login.Token, + TokenAtualizacao = login.TokenAtualizacao + "aaaa", + }; - var controller = new UsuarioController(usuarioServiceMock.Object, null); + await Assert.ThrowsAsync(async () => await controller.AtualizarToken(atualizarTokenDto)); + } - var resultado = await controller.CadastrarUsuarioDnit(usuarioDTO); + [Fact] + public async Task Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() + { + var usuario = dbContext.PopulaUsuarios(1).First(); + usuario.Senha = "teste"; - usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); - var objeto = Assert.IsType(resultado); + var resultado = await controller.Logar(usuario); - Assert.Equal(CREATED, objeto.StatusCode); + Assert.IsType(resultado); } [Fact] - public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() + public async Task Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() { var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Mock usuarioServiceMock = new(); - var excecao = new Npgsql.PostgresException("", "", "", "23505"); + var resultado = await controller.Logar(usuarioDTO); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); + Assert.IsType(resultado); + } - var controller = new UsuarioController(usuarioServiceMock.Object, null); + [Fact] + public async Task CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + { + var usuarioStub = new UsuarioStub(); + var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); 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(CREATED, objeto.StatusCode); } [Fact] - public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInterno() + public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() { 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); @@ -120,9 +160,7 @@ public async void CadastrarUsuarioDnit_QuandoCadastroFalhar_DeveRetornarErroInte 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] @@ -169,7 +207,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern 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); @@ -248,5 +286,13 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() usuarioServiceMock.Verify(service => service.TrocaSenha(redefinicaoSenhaDTO), Times.Once); Assert.IsType(resultado); } + + public new void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Empresa); + } } } diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index bd59d8c..6773f8f 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Options; using auth; using System.Threading.Tasks; +using app.Configuracoes; namespace test { @@ -25,7 +26,6 @@ public class UsuarioServiceTest : TestBed, IDisposable Mock mapper; Mock usuarioRepositorio; Mock emailService; - Mock configuration; AuthService authService; Mock> authConfig; IUsuarioService usuarioServiceMock; @@ -37,12 +37,12 @@ public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : ba mapper = new Mock(); usuarioRepositorio = new Mock(); emailService = new Mock(); - configuration = new Mock(); + var senhaConfig = new SenhaConfig(); authConfig = new Mock>(); authService = new AuthService(authConfig.Object); - usuarioServiceMock = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object, dbContext, authService, authConfig.Object); + usuarioServiceMock = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, Options.Create(senhaConfig), dbContext, authService, authConfig.Object); } [Fact] diff --git a/test/test.csproj b/test/test.csproj index 25ecdac..d4385a4 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -1,38 +1,39 @@  - - 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 + + - - - - + + + + From 7b8d7829c869dc8044f17f2e3e7903ab29c9c503 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 09:28:48 -0300 Subject: [PATCH 081/137] chore: remove dapper --- app/Repositorios/UnidadeFederativaRepositorio.cs | 4 +--- app/app.csproj | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Repositorios/UnidadeFederativaRepositorio.cs b/app/Repositorios/UnidadeFederativaRepositorio.cs index c6afd27..9cddeea 100644 --- a/app/Repositorios/UnidadeFederativaRepositorio.cs +++ b/app/Repositorios/UnidadeFederativaRepositorio.cs @@ -1,6 +1,4 @@ -using Dapper; -using app.Entidades; -using api; +using api; using app.Repositorios.Interfaces; using AutoMapper; diff --git a/app/app.csproj b/app/app.csproj index d9f6155..02f7d26 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -9,7 +9,6 @@ - From d3a4e39a7e9917ff64e38071439d1897f0a8dfac Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 18:09:40 -0300 Subject: [PATCH 082/137] test: implementa testes para PerfilService --- test/Fixtures/Base.cs | 2 + test/PerfilServiceTest.cs | 131 ++++++++++++++++++++++++++++++++++++++ test/Stub/PerfilStub.cs | 11 +++- 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 test/PerfilServiceTest.cs diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs index 58950ca..9bc3921 100644 --- a/test/Fixtures/Base.cs +++ b/test/Fixtures/Base.cs @@ -33,10 +33,12 @@ protected override void AddServices(IServiceCollection services, IConfiguration? services.AddScoped(); services.AddAutoMapper(typeof(AutoMapperConfig)); services.AddScoped(); + services.AddScoped(); // Controllers services.AddScoped(); services.AddScoped(); + services.AddScoped(); } protected override ValueTask DisposeAsyncCore() => new(); diff --git a/test/PerfilServiceTest.cs b/test/PerfilServiceTest.cs new file mode 100644 index 0000000..10fc018 --- /dev/null +++ b/test/PerfilServiceTest.cs @@ -0,0 +1,131 @@ +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(2, 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_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.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/test/Stub/PerfilStub.cs b/test/Stub/PerfilStub.cs index 2897f77..c181bfe 100644 --- a/test/Stub/PerfilStub.cs +++ b/test/Stub/PerfilStub.cs @@ -35,12 +35,21 @@ public static List RetornaListaDePerfis(int n = 4) { List lista = new(); - for(int i = 0; i < 4; i++) + 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.EmpresaCadastrar, Permissao.PerfilCadastrar} + }; + } } } \ No newline at end of file From 2c0e56eda9deee1128408704f0f94489c598c247 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sun, 22 Oct 2023 18:16:57 -0300 Subject: [PATCH 083/137] chore: atualiza versao do auth --- app/Controllers/DominioController.cs | 7 ++----- app/Controllers/UsuarioController.cs | 3 +-- .../AppControllerBase.cs => Services/AppController.cs} | 5 ++--- auth/auth.csproj | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) rename app/{Entidades/AppControllerBase.cs => Services/AppController.cs} (72%) diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index f13c719..03f72e2 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -1,16 +1,13 @@ -using app.Entidades; -using api; +using api; using Microsoft.AspNetCore.Mvc; -using app.Repositorios; using app.Repositorios.Interfaces; using app.Services; -using app.Services.Interfaces; namespace app.Controllers { [ApiController] [Route("api/dominio")] - public class DominioController : ControllerBase + public class DominioController : AppController { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 94565ab..a589f4a 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -6,13 +6,12 @@ using app.Services; using api; using System.Data.Common; -using app.Entidades; namespace app.Controllers { [ApiController] [Route("api/usuario")] - public class UsuarioController : AppControllerBase + public class UsuarioController : AppController { private readonly IUsuarioService usuarioService; private readonly AuthService authService; diff --git a/app/Entidades/AppControllerBase.cs b/app/Services/AppController.cs similarity index 72% rename from app/Entidades/AppControllerBase.cs rename to app/Services/AppController.cs index b666fa8..e008575 100644 --- a/app/Entidades/AppControllerBase.cs +++ b/app/Services/AppController.cs @@ -1,12 +1,11 @@ using Microsoft.AspNetCore.Mvc; using System.Security.Claims; -namespace app.Entidades +namespace app.Services { - public class AppControllerBase : ControllerBase + public class AppController : ControllerBase { public ClaimsPrincipal? AppUsuario { get; set; } - public ClaimsPrincipal Usuario => AppUsuario ?? User; } } diff --git a/auth/auth.csproj b/auth/auth.csproj index dbaa2e6..08ffa50 100644 --- a/auth/auth.csproj +++ b/auth/auth.csproj @@ -7,7 +7,7 @@ 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.6 + 1.0.7 DnitEpsFga.auth From d5a1fa0c822a610adb13dbe9573a4b0be3306903 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sun, 22 Oct 2023 19:08:30 -0300 Subject: [PATCH 084/137] feat: adiciona todas as permissoes ao administrador --- api/Enums.cs | 1 + app/Controllers/PerfilController.cs | 2 +- app/Entidades/Perfil.cs | 7 ++++++- app/Services/Mapper.cs | 3 ++- app/Services/PerfilService.cs | 15 ++++++++++++++- app/Services/UsuarioService.cs | 4 +--- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 925e9f4..8ec4135 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -103,6 +103,7 @@ public enum Permissao SinistroCadastrar = 7000, } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum TipoPerfil { Basico = 1, diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 3fe5d99..8e9d8e2 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -99,7 +99,7 @@ public async Task ExcluirPerfil(Guid id) } [Authorize] - [HttpGet()] + [HttpGet] public async Task ListarPerfis(int pageIndex, int pageSize) { authService.Require(User, Permissao.PerfilVisualizar); diff --git a/app/Entidades/Perfil.cs b/app/Entidades/Perfil.cs index 1db5b6a..a0c1cf3 100644 --- a/app/Entidades/Perfil.cs +++ b/app/Entidades/Perfil.cs @@ -1,5 +1,6 @@ using api; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace app.Entidades { @@ -16,7 +17,11 @@ public class Perfil public List? PerfilPermissoes { get; set; } - public IEnumerable? Permissoes => PerfilPermissoes?.Select(p => p.Permissao); + [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/Services/Mapper.cs b/app/Services/Mapper.cs index 5bff6b0..bed34e4 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -49,7 +49,8 @@ public AutoMapperConfig() .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.Tipo, opt => opt.Ignore()) + .ForMember(p => p.PermissoesSessao, opt => opt.Ignore()); CreateMap() .ForMember(model => model.Permissoes, opt => opt.MapFrom diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index 7984aa5..02dee31 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -3,6 +3,7 @@ using app.Repositorios.Interfaces; using app.Services.Interfaces; using AutoMapper; +using Microsoft.EntityFrameworkCore; namespace app.Services { @@ -86,7 +87,19 @@ public async Task ExcluirPerfil(Guid id) public async Task> ListarPerfisAsync(int pageIndex, int pageSize) { - return await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize); + var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize); + var administrador = perfis.FirstOrDefault(p => p.Tipo == TipoPerfil.Administrador); + + if (administrador != null) + { + PreencherPermissoesAdministrador(administrador); + } + return perfis; + } + + private void PreencherPermissoesAdministrador(Perfil perfil) + { + perfil.PermissoesSessao = Enum.GetValues().ToList(); } } } \ No newline at end of file diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index ece1f95..84a9f79 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -165,11 +165,9 @@ private async Task CriarTokenAsync(Usuario usuario) { var permissoes = usuario.Perfil?.Permissoes?.ToList() ?? new(); - if (!authConfig.Enabled) // || usuario.Perfil.Tipo == TipoPerfil.Administrador + if (!authConfig.Enabled || usuario.Perfil.Tipo == TipoPerfil.Administrador) permissoes = Enum.GetValues().ToList(); - permissoes = new() { Permissao.EscolaVisualizar, Permissao.UpsVisualizar, Permissao.EscolaCadastrar }; - var (token, expiraEm) = autenticacaoService.GenerateToken(new AuthUserModel { Id = usuario.Id, From d495496fbd4407410fe56d0ca96b7aa769caa329 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 22 Oct 2023 19:20:32 -0300 Subject: [PATCH 085/137] =?UTF-8?q?chore:=20conserta=20erro=20de=20portugu?= =?UTF-8?q?=C3=AAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/AuthTest.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/AuthTest.cs b/test/AuthTest.cs index f75045c..d3e064c 100644 --- a/test/AuthTest.cs +++ b/test/AuthTest.cs @@ -86,21 +86,21 @@ public void GetUserId_QuandoForInvalido_DeveLancarExcecao() [Fact] public void HasPermission_QuandoTiverPermissao_DeveRetornarTrue() { - var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); Assert.True(authService.HasPermission(ObterClaim(token), Permissao.RodoviaCadastrar)); } [Fact] public void HasPermission_QuandoNaoTiverPermissao_DeveRetornarFalse() { - var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); Assert.False(authService.HasPermission(ObterClaim(token), Permissao.PerfilVisualizar)); } [Fact] public void Require_QuandoTiverPermissao_DevePassar() { - var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); authService.Require(ObterClaim(token), Permissao.RodoviaCadastrar); Assert.True(true); } @@ -109,7 +109,7 @@ public void Require_QuandoTiverPermissao_DevePassar() public void Require_QuandoTiverDesabilitado_DevePassar() { authConfig.Enabled = false; - var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); authService.Require(ObterClaim(token), Permissao.PerfilVisualizar); Assert.True(true); } @@ -117,7 +117,7 @@ public void Require_QuandoTiverDesabilitado_DevePassar() [Fact] public void Require_QuandoNaoTiverPermissao_DeveLancarExcecao() { - var (token, _) = ObterTokenValido(permissaos: new() { Permissao.RodoviaCadastrar }); + var (token, _) = ObterTokenValido(permissoes: new() { Permissao.RodoviaCadastrar }); Assert.Throws(() => authService.Require(ObterClaim(token), Permissao.PerfilVisualizar)); } @@ -127,13 +127,13 @@ private ClaimsPrincipal ObterClaim(string token) return new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); } - private (string, DateTime) ObterTokenValido(int id = 1, List? permissaos = null) + private (string, DateTime) ObterTokenValido(int id = 1, List? permissoes = null) { var usuario = new AuthUserModel() { Id = id, Name = "Test", - Permissions = permissaos ?? new() { Permissao.EscolaCadastrar }, + Permissions = permissoes ?? new() { Permissao.EscolaCadastrar }, }; return authService.GenerateToken(usuario); } From bd09ed25296a02387c14fc415c29d17d6561c44f Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Sun, 22 Oct 2023 19:24:11 -0300 Subject: [PATCH 086/137] test: adiciona tete para permissaoService --- test/PermissaoServiceTest.cs | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 test/PermissaoServiceTest.cs diff --git a/test/PermissaoServiceTest.cs b/test/PermissaoServiceTest.cs new file mode 100644 index 0000000..9d6ea5e --- /dev/null +++ b/test/PermissaoServiceTest.cs @@ -0,0 +1,43 @@ +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); + } + + [Fact] + public void ObterPermissoesPortCategoria_DeveRetornarTodasPermissoesVigentes() + { + var permissoes = Enum.GetValues().ToList(); + + var categorias = permissaoService.ObterCategorias(); + + var lista = new List(); + + categorias.ForEach(c => lista.AddRange(permissaoService.ObterPermissoesPortCategoria(c).Select(l => l.Codigo))); + + Assert.Equal(permissoes.Count(), lista.Count()); + } + } + +} \ No newline at end of file From aed0c57909d54be68e4669d488ffff4646ea941e Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Sun, 22 Oct 2023 23:56:49 -0300 Subject: [PATCH 087/137] fix: arruma nome e permissoes do administrador --- api/Permissoes/CategoriaPermissaoModel.cs | 2 +- app/Controllers/DominioController.cs | 2 +- app/Services/UsuarioService.cs | 4 ++++ app/appsettings.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/Permissoes/CategoriaPermissaoModel.cs b/api/Permissoes/CategoriaPermissaoModel.cs index 4065fb9..f777c86 100644 --- a/api/Permissoes/CategoriaPermissaoModel.cs +++ b/api/Permissoes/CategoriaPermissaoModel.cs @@ -5,6 +5,6 @@ namespace api public class CategoriaPermissaoModel { public string Categoria { get; set; } - public List Permisoes { get; set; } + public List Permissoes { get; set; } } } \ No newline at end of file diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index 5f7b24e..9793e1f 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -52,7 +52,7 @@ public IActionResult ObterListaDePermissoes() var lista = categorias.ConvertAll(c => new CategoriaPermissaoModel { Categoria = c, - Permisoes = PermissaoService.ObterPermissoesPortCategoria(c) + Permissoes = PermissaoService.ObterPermissoesPortCategoria(c) }); diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 84a9f79..bbcb997 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -193,6 +193,10 @@ private async Task CriarTokenAsync(Usuario usuario) public async Task> ListarPermissoesAsync(int userId) { var usuario = await usuarioRepositorio.ObterUsuarioAsync(userId, includePerfil: true); + if (usuario!.Perfil?.Tipo == TipoPerfil.Administrador) + { + usuario.Perfil.PermissoesSessao = Enum.GetValues().ToList(); + } return usuario!.Perfil?.Permissoes?.ToList() ?? new(); } } diff --git a/app/appsettings.json b/app/appsettings.json index c4a6e30..9c98fd8 100644 --- a/app/appsettings.json +++ b/app/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "PostgreSql": "Host=database-dnit-eps-mds.coteugcvtnid.us-east-1.rds.amazonaws.com;Port=5432;Database=postgres;Username=epsmds;Password=epsmds2023" + "PostgreSql": "" }, "Logging": { "LogLevel": { From b29e29cc210a9c57ca72a430603e34e25852a306 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Mon, 23 Oct 2023 00:16:29 -0300 Subject: [PATCH 088/137] Release v1.0.0 (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Atualizando docker-compose - retira temporariamente o build do app - adiciona as configurações do compose para rodar a base de dados postgres e a ferramenta pgadmin4 * Dockerfile modificado para ambiente de dev - Dockerfile para deploy movido para a pasta ci - Dockerfile de dev configurado para funcionar com hot reload - Docker-compose configurado para o ambiente de dev * adiciona pasta publish no .dockerignore * Update docker-compose.yml Co-authored-by: Yudi Yamane * atualiza senha do postgres de desenvolvimento Co-authored-by: Yudi Yamane * Apply suggestions from code review Co-authored-by: Yudi Yamane * chore: Atualiza analise do sonar para usar a branch develop * chore: Cria sonar-project.properties * fix: sonarcloud build.yml * fix: remove sonar-project.properties file * refactor: Adiciona o EntityFramework Refs: #2 * refactor: adiciona migrações iniciais * refactor: adiciona entidade Usuario * refactor: adiciona entidade RedefinicaoSenha * refactor: adiciona entidade Empresa. * refactor: adiciona a tabela de relacionamento UsuarioEmpresa * refactor: adiciona enum par unidades federativas * refactor: reorganiza a disposição de pastas * refactor: ajusta as referencias * fix: release workflow repository * chore: Renomeia secret GITHUB_TOKEN para GIT_TOKEN GITHUB_TOKEN é um secret criado automaticamente pelo próprio github actions * fix: arruma formato do release.yml * refactor: ajusta rotinas para EF * refactor: ajusta a rotina de testes para o EF * test: ajusta testes do automapper * fix: arruma a referencia das configuracoes no dbContext * test: ajusta DominioControlerTest * test: ajusta os configTest * test: ajusta testes referentes a Usuario * test: ajusta testes referentes a UF * feat: adiciona perfil #64 (https://github.com/fga-eps-mds/2023.2-Dnit-DOC/issues/63) * ci: adiciona testagem no linux * ci: renomeia actions * fix: padroniza string de conexao do banco de dados * feat: adiciona base da autenticacao Co-authored-by: Yudi Yamane Co-authored-by: Cássio Reis * fix: arruma perfil usuario opcional * fix: arruma migracoes * feat: adiciona jwt token no login * fix: ajusta testes * fix: arruma detalhes do mapper e stub nos testes * refactor: cria biblioteca de autenticacao * refactor: ordena permissoes * feat: adiciona verificação da permissao * feat: adiciona exception handler * fix: renomeia nome do identificador do pacote de autenticacao * feat: adiciona autorizacao no swagger * fix: troca erro de nao autorizado para o 403 * fix: ajusta a cobertura de testes no sonar * refactor: adiciona permissão de visualizar escola ao enum * feat: adiciona atualizacao do token expirado * fix: arruma nome das permissoes * refactor: adiciona permissões relacionadas ao UPS service ao enum * fix: arruma valores da permissao * chore: adiciona configuracao para desabilitar autenticacao * fix: adiciona exlusoes do sonar * chore: adiciona todas as permissoes quando a autenticacao tiver desabilitada * feat: adiciona listagem de permissao do usuario * refactor: testes * test: adiciona teste do auth * test: adiciona teste do usuario controller * chore: atualiza versao do auth * chore: conserta erro de português --------- Co-authored-by: Wagner M Cunha Co-authored-by: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Co-authored-by: Yudi Yamane Co-authored-by: Daniel Porto Co-authored-by: Cássio Reis Co-authored-by: Thiago Sampaio de Paiva Co-authored-by: Hunter104 <109833229+Hunter104@users.noreply.github.com> --- .dockerignore | 4 + .editorconfig | 4 + .github/workflows/build.yml | 64 ----- .github/workflows/ci.yml | 134 ++++++---- .github/workflows/deploy.yml | 55 ++++ .github/workflows/release.yml | 12 +- .gitignore | 6 + Dockerfile | 27 +- UsuarioService.sln | 49 ++-- api/Enums.cs | 105 ++++++++ api/Senhas/RedefinicaoSenhaDTO.cs | 8 + api/Senhas/RedefinicaoSenhaModel.cs | 8 + api/UfModel.cs | 9 + api/Usuarios/AtualizarTokenData.cs | 12 + api/Usuarios/LoginModel.cs | 10 + {dominio => api/Usuarios}/UsuarioDTO.cs | 10 +- api/Usuarios/UsuarioDnit.cs | 7 + .../Usuarios/UsuarioModel.cs | 11 +- api/Usuarios/UsuarioTerceiro.cs | 7 + dominio/dominio.csproj => api/api.csproj | 4 + app/.env | 2 + app/Configuracoes/SenhaConfig.cs | 7 + app/Controllers/DominioController.cs | 12 +- app/Controllers/UsuarioController.cs | 85 ++++--- app/DI/ConfiguracaoConfig.cs | 14 ++ app/DI/ContextoConfig.cs | 38 --- app/DI/RepositoriosConfig.cs | 4 +- app/DI/ServicesConfig.cs | 12 +- app/Entidades/AppDbContext.cs | 62 +++++ app/Entidades/Empresa.cs | 17 ++ app/Entidades/Perfil.cs | 20 ++ app/Entidades/PerfilPermissao.cs | 18 ++ app/Entidades/RedefinicaoSenha.cs | 19 ++ app/Entidades/Usuario.cs | 35 +++ .../20231012122128_Initial.Designer.cs | 29 +++ app/Migrations/20231012122128_Initial.cs | 22 ++ .../20231012123152_Usuario.Designer.cs | 57 +++++ app/Migrations/20231012123152_Usuario.cs | 37 +++ ...0231012142410_RedefinicaoSenha.Designer.cs | 96 +++++++ .../20231012142410_RedefinicaoSenha.cs | 77 ++++++ .../20231012145354_Empresa.Designer.cs | 113 +++++++++ app/Migrations/20231012145354_Empresa.cs | 33 +++ .../20231012200221_UsuarioEmpresa.Designer.cs | 145 +++++++++++ .../20231012200221_UsuarioEmpresa.cs | 50 ++++ .../20231012202644_UfEnum.Designer.cs | 148 +++++++++++ app/Migrations/20231012202644_UfEnum.cs | 29 +++ .../20231019025721_Perfil.Designer.cs | 223 +++++++++++++++++ app/Migrations/20231019025721_Perfil.cs | 112 +++++++++ ...0231021000639_TokenAtualizacao.Designer.cs | 229 +++++++++++++++++ .../20231021000639_TokenAtualizacao.cs | 39 +++ app/Migrations/AppDbContextModelSnapshot.cs | 226 +++++++++++++++++ app/Program.cs | 22 +- app/Properties/launchSettings.json | 14 +- .../IUnidadeFederativaRepositorio.cs | 9 + .../Interfaces/IUsuarioRepositorio.cs | 17 ++ .../UnidadeFederativaRepositorio.cs | 20 ++ app/Repositorios/UsuarioRepositorio.cs | 115 +++++++++ app/Services/AppController.cs | 11 + {service => app/Services}/EmailService.cs | 10 +- .../Services}/Interfaces/IEmailService.cs | 2 +- app/Services/Interfaces/IUsuarioService.cs | 18 ++ app/Services/Mapper.cs | 48 ++++ app/Services/UsuarioService.cs | 201 +++++++++++++++ app/app.csproj | 52 ++-- app/appsettings.Development.json | 32 ++- app/appsettings.json | 22 +- auth/AuthConfig.cs | 14 ++ auth/AuthExceptionHandler.cs | 21 ++ auth/AuthExceptions.cs | 13 + auth/AuthService.cs | 122 +++++++++ auth/AuthStartup.cs | 107 ++++++++ auth/AuthUserModel.cs | 9 + auth/auth.csproj | 29 +++ ci/Dockerfile | 30 +++ docker-compose.yml | 34 ++- dominio/Enums/ContextoBancoDeDados.cs | 7 - dominio/RedefinicaoSenha.cs | 8 - dominio/RedefinicaoSenhaDTO.cs | 8 - dominio/UnidadeFederativa.cs | 9 - dominio/UsuarioDnit.cs | 7 - dominio/UsuarioTerceiro.cs | 13 - repositorio/Contexto/ContextoPostgresql.cs | 13 - repositorio/Contexto/IContexto.cs | 9 - repositorio/Contexto/ResolverContexto.cs | 9 - .../IUnidadeFederativaRepositorio.cs | 10 - repositorio/Interfaces/IUsuarioRepositorio.cs | 16 -- repositorio/UnidadeFederativaRepositorio.cs | 28 --- repositorio/UsuarioRepositorio.cs | 138 ----------- repositorio/repositorio.csproj | 17 -- service/Interfaces/IUsuarioService.cs | 19 -- service/Mapper/UsuarioProfile.cs | 18 -- service/UsuarioService.cs | 117 --------- service/service.csproj | 20 -- test/AuthTest.cs | 141 +++++++++++ test/AutoMapperConfigTest.cs | 3 +- test/Contexto.cs | 21 -- test/ContextoConfigTest.cs | 46 ---- test/ContextoPostgresqlTest.cs | 23 -- test/DominioControllerTest.cs | 25 +- test/Fixtures/Base.cs | 50 ++++ test/RepositorioConfigTest.cs | 4 +- test/ServicesConfigTest.cs | 4 +- test/Stub/AppDbContextExtensions.cs | 57 +++++ test/Stub/RedefinicaoSenhaStub.cs | 6 +- test/Stub/UsuarioStub.cs | 52 +++- test/UnidadeFederativaRepositorioTest.cs | 69 +++--- test/Usings.cs | 2 + test/UsuarioControllerTest.cs | 208 ++++++++++------ test/UsuarioRepositorioTest.cs | 140 +++++------ test/UsuarioServiceTest.cs | 234 +++++++----------- test/test.csproj | 62 +++-- 111 files changed, 3875 insertions(+), 1266 deletions(-) create mode 100644 .dockerignore create mode 100644 .editorconfig delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 api/Enums.cs create mode 100644 api/Senhas/RedefinicaoSenhaDTO.cs create mode 100644 api/Senhas/RedefinicaoSenhaModel.cs create mode 100644 api/UfModel.cs create mode 100644 api/Usuarios/AtualizarTokenData.cs create mode 100644 api/Usuarios/LoginModel.cs rename {dominio => api/Usuarios}/UsuarioDTO.cs (54%) create mode 100644 api/Usuarios/UsuarioDnit.cs rename dominio/Usuario.cs => api/Usuarios/UsuarioModel.cs (52%) create mode 100644 api/Usuarios/UsuarioTerceiro.cs rename dominio/dominio.csproj => api/api.csproj (72%) create mode 100644 app/.env create mode 100644 app/Configuracoes/SenhaConfig.cs create mode 100644 app/DI/ConfiguracaoConfig.cs delete mode 100644 app/DI/ContextoConfig.cs create mode 100644 app/Entidades/AppDbContext.cs create mode 100644 app/Entidades/Empresa.cs create mode 100644 app/Entidades/Perfil.cs create mode 100644 app/Entidades/PerfilPermissao.cs create mode 100644 app/Entidades/RedefinicaoSenha.cs create mode 100644 app/Entidades/Usuario.cs create mode 100644 app/Migrations/20231012122128_Initial.Designer.cs create mode 100644 app/Migrations/20231012122128_Initial.cs create mode 100644 app/Migrations/20231012123152_Usuario.Designer.cs create mode 100644 app/Migrations/20231012123152_Usuario.cs create mode 100644 app/Migrations/20231012142410_RedefinicaoSenha.Designer.cs create mode 100644 app/Migrations/20231012142410_RedefinicaoSenha.cs create mode 100644 app/Migrations/20231012145354_Empresa.Designer.cs create mode 100644 app/Migrations/20231012145354_Empresa.cs create mode 100644 app/Migrations/20231012200221_UsuarioEmpresa.Designer.cs create mode 100644 app/Migrations/20231012200221_UsuarioEmpresa.cs create mode 100644 app/Migrations/20231012202644_UfEnum.Designer.cs create mode 100644 app/Migrations/20231012202644_UfEnum.cs create mode 100644 app/Migrations/20231019025721_Perfil.Designer.cs create mode 100644 app/Migrations/20231019025721_Perfil.cs create mode 100644 app/Migrations/20231021000639_TokenAtualizacao.Designer.cs create mode 100644 app/Migrations/20231021000639_TokenAtualizacao.cs create mode 100644 app/Migrations/AppDbContextModelSnapshot.cs create mode 100644 app/Repositorios/Interfaces/IUnidadeFederativaRepositorio.cs create mode 100644 app/Repositorios/Interfaces/IUsuarioRepositorio.cs create mode 100644 app/Repositorios/UnidadeFederativaRepositorio.cs create mode 100644 app/Repositorios/UsuarioRepositorio.cs create mode 100644 app/Services/AppController.cs rename {service => app/Services}/EmailService.cs (76%) rename {service => app/Services}/Interfaces/IEmailService.cs (79%) create mode 100644 app/Services/Interfaces/IUsuarioService.cs create mode 100644 app/Services/Mapper.cs create mode 100644 app/Services/UsuarioService.cs create mode 100644 auth/AuthConfig.cs create mode 100644 auth/AuthExceptionHandler.cs create mode 100644 auth/AuthExceptions.cs create mode 100644 auth/AuthService.cs create mode 100644 auth/AuthStartup.cs create mode 100644 auth/AuthUserModel.cs create mode 100644 auth/auth.csproj create mode 100644 ci/Dockerfile delete mode 100644 dominio/Enums/ContextoBancoDeDados.cs delete mode 100644 dominio/RedefinicaoSenha.cs delete mode 100644 dominio/RedefinicaoSenhaDTO.cs delete mode 100644 dominio/UnidadeFederativa.cs delete mode 100644 dominio/UsuarioDnit.cs delete mode 100644 dominio/UsuarioTerceiro.cs delete mode 100644 repositorio/Contexto/ContextoPostgresql.cs delete mode 100644 repositorio/Contexto/IContexto.cs delete mode 100644 repositorio/Contexto/ResolverContexto.cs delete mode 100644 repositorio/Interfaces/IUnidadeFederativaRepositorio.cs delete mode 100644 repositorio/Interfaces/IUsuarioRepositorio.cs delete mode 100644 repositorio/UnidadeFederativaRepositorio.cs delete mode 100644 repositorio/UsuarioRepositorio.cs delete mode 100644 repositorio/repositorio.csproj delete mode 100644 service/Interfaces/IUsuarioService.cs delete mode 100644 service/Mapper/UsuarioProfile.cs delete mode 100644 service/UsuarioService.cs delete mode 100644 service/service.csproj create mode 100644 test/AuthTest.cs delete mode 100644 test/Contexto.cs delete mode 100644 test/ContextoConfigTest.cs delete mode 100644 test/ContextoPostgresqlTest.cs create mode 100644 test/Fixtures/Base.cs create mode 100644 test/Stub/AppDbContextExtensions.cs create mode 100644 test/Usings.cs 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/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0fa1701 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,55 @@ +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 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..d11c8d5 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 @@ -362,3 +364,7 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd /dominio/UsuarioDNIT.cs + +.idea/ + +report/ 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..2fcca48 --- /dev/null +++ b/api/Enums.cs @@ -0,0 +1,105 @@ +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, + } +} 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/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/dominio/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs similarity index 54% rename from dominio/UsuarioDTO.cs rename to api/Usuarios/UsuarioDTO.cs index ffd3940..a622b46 100644 --- a/dominio/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -1,17 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace dominio +namespace api.Usuarios { public class UsuarioDTO { public string Email { get; set; } public string Senha { get; set; } public string Nome { get; set; } - public int? UF { get; set; } + public UF UfLotacao { get; set; } public string? CNPJ { get; set; } } } diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs new file mode 100644 index 0000000..3ce3909 --- /dev/null +++ b/api/Usuarios/UsuarioDnit.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioDnit : UsuarioModel + { + public UF UfLotacao { get; set; } + } +} \ No newline at end of file diff --git a/dominio/Usuario.cs b/api/Usuarios/UsuarioModel.cs similarity index 52% rename from dominio/Usuario.cs rename to api/Usuarios/UsuarioModel.cs index 36b3432..9c83b2c 100644 --- a/dominio/Usuario.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace dominio +namespace api.Usuarios { - public class Usuario + public class UsuarioModel { public int Id { get; set; } public string Email { get; set; } public string Senha { get; set; } public string Nome { get; set; } + public string Cnpj { get; set; } } } diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs new file mode 100644 index 0000000..1ac8d6c --- /dev/null +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -0,0 +1,7 @@ +namespace api.Usuarios +{ + public class UsuarioTerceiro : UsuarioModel + { + 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..03f72e2 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -1,15 +1,13 @@ -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; namespace app.Controllers { [ApiController] [Route("api/dominio")] - public class DominioController : ControllerBase + public class DominioController : AppController { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; @@ -23,7 +21,7 @@ public DominioController(IUnidadeFederativaRepositorio unidadeFederativaReposito [HttpGet("unidadeFederativa")] public IActionResult ObterLista() { - IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); + IEnumerable listaUnidadeFederativa = unidadeFederativaRepositorio.ObterDominio(); return new OkObjectResult(listaUnidadeFederativa); } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 93b954c..a589f4a 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -1,51 +1,78 @@ -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 (Exception) { - if (ex.SqlState == "23505") { - return Conflict("Usurio j cadastrado."); - } - return StatusCode(500, "Houve um erro interno no servidor."); } } @@ -59,40 +86,40 @@ 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(); } 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/RepositoriosConfig.cs b/app/DI/RepositoriosConfig.cs index eedac06..3be8d57 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 { diff --git a/app/DI/ServicesConfig.cs b/app/DI/ServicesConfig.cs index f78d753..7f251d3 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -1,5 +1,8 @@ -using service; -using service.Interfaces; +using app.Entidades; +using app.Services; +using app.Services.Interfaces; +using auth; +using Microsoft.EntityFrameworkCore; namespace app.DI { @@ -7,8 +10,13 @@ public static class ServicesConfig { public static void AddConfigServices(this IServiceCollection services, IConfiguration configuration) { + services.AddDbContext(optionsBuilder => optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSql"))); services.AddScoped(); + services.AddScoped(); services.AddScoped(); + + services.AddAuth(configuration); } + } } diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs new file mode 100644 index 0000000..65851f9 --- /dev/null +++ b/app/Entidades/AppDbContext.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore; + +namespace app.Entidades +{ + public class AppDbContext : DbContext + { + + 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() + .HasMany(p => p.PerfilPermissoes) + .WithOne(pp => pp.Perfil) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(u => u.Perfil) + .WithMany(p => p.Usuarios) + .OnDelete(DeleteBehavior.Restrict); + } + } +} \ 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/Perfil.cs b/app/Entidades/Perfil.cs new file mode 100644 index 0000000..7328961 --- /dev/null +++ b/app/Entidades/Perfil.cs @@ -0,0 +1,20 @@ +using api; +using System.ComponentModel.DataAnnotations; + +namespace app.Entidades +{ + public class Perfil + { + [Key] + public Guid Id { get; set; } + + [Required, MaxLength(200)] + public string Nome { get; set; } + + public List? PerfilPermissoes { get; set; } + + public IEnumerable? Permissoes => 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..5fbb022 --- /dev/null +++ b/app/Entidades/Usuario.cs @@ -0,0 +1,35 @@ +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; } + } +} \ 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/AppDbContextModelSnapshot.cs b/app/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..6920ba8 --- /dev/null +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,226 @@ +// +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.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/Program.cs b/app/Program.cs index 70cce3f..793c30c 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,16 +24,16 @@ { 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 => { options.AddPolicy("AllowAllOrigins", @@ -54,10 +55,19 @@ 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(); +} + 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/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..d324237 --- /dev/null +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -0,0 +1,17 @@ +using api.Usuarios; +using app.Entidades; + +namespace app.Repositorios.Interfaces +{ + public interface IUsuarioRepositorio + { + 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); + void CadastrarUsuarioDnit(UsuarioDnit usuario); + void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); + } +} 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..087b801 --- /dev/null +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -0,0 +1,115 @@ +using app.Entidades; +using api.Usuarios; +using app.Repositorios.Interfaces; +using Microsoft.EntityFrameworkCore; +using AutoMapper; + +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 void CadastrarUsuarioDnit(UsuarioDnit usuario) + { + var novoUsuario = new Usuario + { + Nome = usuario.Nome, + Email = usuario.Email, + Senha = usuario.Senha, + UfLotacao = usuario.UfLotacao + }; + + dbContext.Add(novoUsuario); + } + + 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); + } + + public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) + { + var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); + + List empresas = new List{ empresa }; + + var novoUsuarioTerceiro = new Usuario + { + Nome = usuarioTerceiro.Nome, + Email = usuarioTerceiro.Email, + Senha = usuarioTerceiro.Senha, + Empresas = empresas + }; + + dbContext.Usuario.Add(novoUsuarioTerceiro); + } + } +} 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 76% rename from service/EmailService.cs rename to app/Services/EmailService.cs index 2d21b72..793888c 100644 --- a/service/EmailService.cs +++ b/app/Services/EmailService.cs @@ -1,19 +1,18 @@ -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; @@ -27,6 +26,7 @@ public void EnviarEmail(string emailDestinatario, string assunto, string corpo) 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/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs new file mode 100644 index 0000000..08222d4 --- /dev/null +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -0,0 +1,18 @@ +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); + } +} diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs new file mode 100644 index 0000000..69b81f3 --- /dev/null +++ b/app/Services/Mapper.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using api.Senhas; +using api.Usuarios; +using app.Entidades; +using api; +using EnumsNET; + + +namespace app.Services.Mapper +{ + public class AutoMapperConfig : Profile + { + public AutoMapperConfig() + { + CreateMap() + .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); + + CreateMap() + .ForMember(u => u.RedefinicaoSenha, opt => opt.Ignore()) + .ForMember(u => u.Empresas, opt => opt.Ignore()) + .ForMember(u => u.Perfil, opt => opt.Ignore()) + .ForMember(u => u.PerfilId, opt => opt.Ignore()) + .ForMember(u => u.TokenAtualizacao, opt => opt.Ignore()) + .ForMember(u => u.TokenAtualizacaoExpiracao, opt => opt.Ignore()); + + CreateMap() + .ForMember(dto => dto.CNPJ, opt => opt.Ignore()) + .ForMember(dto => dto.UfLotacao, opt => opt.Ignore()); + + CreateMap() + .ForMember(dto => dto.Cnpj, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); + + 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() + .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); + } + } +} \ No newline at end of file diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs new file mode 100644 index 0000000..2c13147 --- /dev/null +++ b/app/Services/UsuarioService.cs @@ -0,0 +1,201 @@ +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 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, + IMapper mapper, + IEmailService emailService, + IOptions senhaConfig, + AppDbContext dbContext, + AuthService autenticacaoService, + IOptions authConfig + ) + { + 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) + { + var usuario = mapper.Map(usuarioDTO); + + usuario.Senha = EncriptarSenha(usuario.Senha); + + 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(); + + permissoes = new() { Permissao.EscolaVisualizar, Permissao.UpsVisualizar, Permissao.EscolaCadastrar }; + + 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); + return usuario!.Perfil?.Permissoes?.ToList() ?? new(); + } + } +} diff --git a/app/app.csproj b/app/app.csproj index 4953896..3288264 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -1,21 +1,43 @@ - - 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 + + diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 70b8722..934d6ff 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,11 +1,25 @@ { - "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" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Senha": { + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", + }, + "Auth": { + "Enabled": false, + "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.json b/app/appsettings.json index 8ea10a2..c4a6e30 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": "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" + } + }, + "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..2457540 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,38 @@ services: context: . ports: - "7083:7083" - container_name: usuarioService + container_name: usuario-service + volumes: + - ./app:/app/app + - ./dominio:/app/dominio + - ./repositorio:/app/repositorio + - ./service:/app/service + - ./test:/app/test env_file: - .env + + dnit-usuario-db: + container_name: dnit-usuario-db + image: postgres:15.4 + restart: always + environment: + POSTGRES_PASSWORD: 1234 + ports: + - "5432:5432" + volumes: + - pg-data-volume:/var/lib/postgresql/data + + 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 + +volumes: + pg-data-volume: + pg-admin-volume: \ 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/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..d3e064c --- /dev/null +++ b/test/AuthTest.cs @@ -0,0 +1,141 @@ +using api; +using app.Services; +using auth; +using Microsoft.Extensions.Options; +using Moq; +using System.Buffers.Text; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using test.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace test +{ + public class AuthTest + { + + AuthService authService; + AuthConfig authConfig; + + public AuthTest() + { + 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..d505802 100644 --- a/test/DominioControllerTest.cs +++ b/test/DominioControllerTest.cs @@ -1,23 +1,24 @@ -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; namespace test { - public class DominioControllerTest + public class DominioControllerTest : TestBed, IDisposable { + DominioController dominioController; + + public DominioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dominioController = fixture.GetService(testOutputHelper)!; + } + [Fact] public void ObterLista_QuandoMetodoForChamado_DeveRetornarListaDeUFs() { - Mock usuarioFederativaRepositorioMock = new(); - - var controller = new DominioController(usuarioFederativaRepositorioMock.Object); - - var resultado = controller.ObterLista(); - - usuarioFederativaRepositorioMock.Verify(repo => repo.ObterDominio(), Times.Once); + var resultado = dominioController.ObterLista(); Assert.IsType(resultado); } } diff --git a/test/Fixtures/Base.cs b/test/Fixtures/Base.cs new file mode 100644 index 0000000..0303ae3 --- /dev/null +++ b/test/Fixtures/Base.cs @@ -0,0 +1,50 @@ +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 + services.AddScoped(); + services.AddScoped(); + services.AddAutoMapper(typeof(AutoMapperConfig)); + + // Controllers + services.AddScoped(); + services.AddScoped(); + + services.AddAuth(configuration); + } + + protected override ValueTask DisposeAsyncCore() => new(); + + protected override IEnumerable GetTestAppSettings() + { + yield return new() { Filename = "appsettings.json", IsOptional = false }; + } + } +} \ 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..2e69a42 --- /dev/null +++ b/test/Stub/AppDbContextExtensions.cs @@ -0,0 +1,57 @@ +using app.Entidades; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 = api.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 = api.Permissao.EscolaCadastrar, + } + } + }; + usuario.Perfil = perfil; + context.Add(perfil); + } + + context.Add(usuario); + } + context.SaveChanges(); + return usuariosTeste; + } + } +} 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..48dad95 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -1,9 +1,33 @@ -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 { + public static IEnumerable Listar() + { + while (true) + { + yield return new TesteUsuarioStub() + { + Nome = "teste " + Random.Shared.Next().ToString(), + CNPJ = string.Join("", Enumerable.Range(0, 11).Select(_ => Random.Shared.Next() % 10)), + Email = $"teste{Random.Shared.Next()}@email.com", + Senha = $"teste_senha_{Random.Shared.Next()}", + }; + } + } + public UsuarioDTO RetornarUsuarioDnitDTO() { return new UsuarioDTO @@ -11,7 +35,7 @@ public UsuarioDTO RetornarUsuarioDnitDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UF.DF }; } @@ -33,7 +57,29 @@ public UsuarioDnit RetornarUsuarioDnit() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UF = 27 + UfLotacao = UF.DF + }; + } + + public Usuario RetornarUsuarioDnitBanco() + { + return new Usuario + { + Email = "usuarioteste@gmail.com", + Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", + Nome = "Usuario Dnit", + UfLotacao = UF.DF + }; + } + + public UsuarioDTO RetornarUsuarioSenhaErrada() + { + return new UsuarioDTO + { + Email = "usuarioteste@gmail.com", + Senha = "senha1234", + Nome = "Usuario Dnit", + UfLotacao = UF.DF }; } 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..e89185c 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -1,137 +1,177 @@ using app.Controllers; -using dominio; +using api.Usuarios; +using api.Senhas; using Microsoft.AspNetCore.Mvc; using Moq; -using service.Interfaces; +using app.Services.Interfaces; using System; using System.Collections.Generic; using test.Stub; using Xunit; +using System.Threading.Tasks; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using app.Services; +using Microsoft.Extensions.Configuration; +using System.Linq; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using auth; namespace test { - public class UsuarioControllerTest + public class UsuarioControllerTest : TestBed, IDisposable { const int CREATED = 201; const int INTERNAL_SERVER_ERROR = 500; - [Fact] - public void Logar_QuandoLoginForValidado_DeveRetornarOk() + UsuarioController controller; + AppDbContext dbContext; + + 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).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()); - - var controller = new UsuarioController(usuarioServiceMock.Object); - - var resultado = controller.Logar(usuarioDTO); + await AutenticarUsuario(usuario); + var permissoes = await controller.ListarPermissoes(); + Assert.NotEmpty(permissoes); + } - usuarioServiceMock.Verify(service => service.ValidaLogin(usuarioDTO), Times.Once); - Assert.IsType(resultado); + [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); } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + public async Task AtualizarToken_QuandoTiverInvalido_DeveRetornarNovoToken() { - UsuarioStub usuarioStub = new(); - var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); + var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - Mock usuarioServiceMock = new(); + var login = await AutenticarUsuario(usuario); + var atualizarTokenDto = new AtualizarTokenDto + { + Token = login.Token, + TokenAtualizacao = login.TokenAtualizacao + "aaaa", + }; - var controller = new UsuarioController(usuarioServiceMock.Object); + await Assert.ThrowsAsync(async () => await controller.AtualizarToken(atualizarTokenDto)); + } - var resultado = controller.CadastrarUsuarioDnit(usuarioDTO); + [Fact] + public async Task Logar_QuandoCredenciaisForemInvalidas_DeveRetornarUnauthorized() + { + var usuario = dbContext.PopulaUsuarios(1).First(); + usuario.Senha = "teste"; - usuarioServiceMock.Verify(service => service.CadastrarUsuarioDnit(usuarioDTO), Times.Once); - var objeto = Assert.IsType(resultado); + var resultado = await controller.Logar(usuario); - Assert.Equal(CREATED, objeto.StatusCode); + Assert.IsType(resultado); } [Fact] - public void CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConflict() + public async Task Logar_QuandoUsuarioNaoExistir_DeveRetornarNotFound() { - UsuarioStub usuarioStub = new(); + var usuarioStub = new UsuarioStub(); var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - Mock usuarioServiceMock = new(); - var excecao = new Npgsql.PostgresException("", "", "", "23505"); + var resultado = await controller.Logar(usuarioDTO); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); + Assert.IsType(resultado); + } - var controller = new UsuarioController(usuarioServiceMock.Object); + [Fact] + public async Task CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCreated() + { + var usuarioStub = new UsuarioStub(); + var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); - 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(CREATED, objeto.StatusCode); } [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 +184,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 +192,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 +203,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 +222,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,12 +279,20 @@ 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); } + + public new void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Empresa); + } } } diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 3dabadc..68ffbed 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -1,58 +1,38 @@ -using Xunit; -using repositorio; -using repositorio.Interfaces; -using dominio; -using Microsoft.Data.Sqlite; -using Dapper; +using app.Repositorios; +using app.Repositorios.Interfaces; +using api.Usuarios; using test.Stub; -using System; +using test.Fixtures; +using app.Entidades; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; +using System.Linq; +using AutoMapper; +using System.Threading.Tasks; namespace test { - public class UsuarioRepositorioTest : IDisposable + public class UsuarioRepositorioTest : TestBed, IDisposable { IUsuarioRepositorio repositorio; - SqliteConnection conexao; + AppDbContext dbContext; + IMapper mapper; - 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)!; + mapper = 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 dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario("usuarioteste@gmail.com"); @@ -62,38 +42,36 @@ 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 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}';"; + var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault(); + var usuarioObtidoDTO = mapper.Map(usuarioObtido); - UsuarioDnit? usuarioObtido = conexao.QueryFirst(sql); - - 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.Email, usuarioObtidoDTO.Email); + Assert.Equal(usuarioDNIT.Senha, usuarioObtidoDTO.Senha); + Assert.Equal(usuarioDNIT.Nome, usuarioObtidoDTO.Nome); + Assert.Equal(usuarioDNIT.UfLotacao, usuarioObtidoDTO.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 dbContext.SaveChangesAsync(); string novaSenha = "NovaSenha"; repositorio.TrocarSenha(usuarioDNIT.Email, novaSenha); + await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); @@ -101,17 +79,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 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 +100,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 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 +125,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}';"; + 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..27ea1e0 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -1,37 +1,60 @@ 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 { + + AppDbContext dbContext; + Mock mapper; + Mock usuarioRepositorio; + Mock emailService; + AuthService authService; + Mock> authConfig; + IUsuarioService usuarioServiceMock; + + public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) + { + dbContext = fixture.GetService(testOutputHelper)!; + + mapper = new Mock(); + usuarioRepositorio = new Mock(); + emailService = new Mock(); + var senhaConfig = new SenhaConfig(); + authConfig = new Mock>(); + authService = new AuthService(authConfig.Object); + + + usuarioServiceMock = new UsuarioService(usuarioRepositorio.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 +67,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 +79,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,20 +137,12 @@ 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); } @@ -176,111 +150,79 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( [Fact] public 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); - - IUsuarioService usuarioService = new UsuarioService(usuarioRepositorio.Object, mapper.Object, emailService.Object, configuration.Object); + usuarioRepositorio.Setup(x => x.ObterUsuario(It.IsAny())).Returns(usuarioRetorno); - usuarioService.RecuperarSenha(usuarioDnitDTO); + 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"; - - UsuarioDTO usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); - UsuarioDnit usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - RedefinicaoSenha redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); + var usuarioStub = new UsuarioStub(); + var redefinicaoSenhaStub = new RedefinicaoSenhaStub(); + var emailRedefinicaoSenha = "usuarioTeste@gmail.com"; - 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..010321a 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 + + + + + + + - - - + + **/** + ** + - + From 668800046e0a8939165dd802adf69bc7ed882845 Mon Sep 17 00:00:00 2001 From: DiceRunner714 Date: Mon, 23 Oct 2023 23:16:21 -0300 Subject: [PATCH 089/137] feat: adiciona quantidade de usuarios no retorno da listagem de perfis. --- api/Perfis/PerfilModel.cs | 1 + app/Repositorios/PerfilRepositorio.cs | 1 + app/Services/Mapper.cs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/api/Perfis/PerfilModel.cs b/api/Perfis/PerfilModel.cs index 8e1d07e..e5c0eef 100644 --- a/api/Perfis/PerfilModel.cs +++ b/api/Perfis/PerfilModel.cs @@ -6,6 +6,7 @@ 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; } } diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index e2a0dbe..384ff12 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -66,6 +66,7 @@ public async Task> ListarPerfisAsync(int pageIndex, int pageSize) var query = dbContext.Perfis.AsQueryable(); query = query.Include(p => p.PerfilPermissoes); + query = query.Include(p => p.Usuarios); return await query .OrderBy(p => p.Nome) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index bed34e4..e490054 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -61,7 +61,8 @@ public AutoMapperConfig() Descricao = p.AsString(EnumFormat.Description)! }).ToList() ) - ); + ) + .ForMember(model => model.QuantidadeUsuarios, opt => opt.MapFrom(p => p.Usuarios.Count())); } } } \ No newline at end of file From d2cc546c789226df72a23f8aeb489b3aca60f25a Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 24 Oct 2023 12:36:36 -0300 Subject: [PATCH 090/137] test: configura tetes para usar autenticacao --- app/appsettings.Test.json | 12 ++++++++++ test/AuthTest.cs | 4 ++-- test/Fixtures/AuthTest.cs | 47 +++++++++++++++++++++++++++++++++++++++ test/Fixtures/Base.cs | 2 +- 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 app/appsettings.Test.json create mode 100644 test/Fixtures/AuthTest.cs 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/test/AuthTest.cs b/test/AuthTest.cs index f75045c..64632be 100644 --- a/test/AuthTest.cs +++ b/test/AuthTest.cs @@ -14,13 +14,13 @@ namespace test { - public class AuthTest + public class AuthenticationTest { AuthService authService; AuthConfig authConfig; - public AuthTest() + public AuthenticationTest() { authConfig = new AuthConfig() { diff --git a/test/Fixtures/AuthTest.cs b/test/Fixtures/AuthTest.cs new file mode 100644 index 0000000..c6ee854 --- /dev/null +++ b/test/Fixtures/AuthTest.cs @@ -0,0 +1,47 @@ +using api; +using app.Services; +using auth; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +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 index ee750e0..3665217 100644 --- a/test/Fixtures/Base.cs +++ b/test/Fixtures/Base.cs @@ -48,7 +48,7 @@ protected override void AddServices(IServiceCollection services, IConfiguration? protected override IEnumerable GetTestAppSettings() { - yield return new() { Filename = "appsettings.json", IsOptional = false }; + yield return new() { Filename = "appsettings.Test.json", IsOptional = false }; } } } \ No newline at end of file From 8deda9b9fc2c05931490011308a18157a4d8d6d7 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 24 Oct 2023 12:42:13 -0300 Subject: [PATCH 091/137] test: adiciona testes para PerfilController --- api/Perfis/PerfilDTO.cs | 3 + app/Controllers/PerfilController.cs | 10 +- app/Services/AppController.cs | 2 +- test/PerfilControllerTest.cs | 197 ++++++++++++++++++++++++++++ test/Stub/PerfilStub.cs | 19 ++- 5 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 test/PerfilControllerTest.cs diff --git a/api/Perfis/PerfilDTO.cs b/api/Perfis/PerfilDTO.cs index d9410cd..f92ed0d 100644 --- a/api/Perfis/PerfilDTO.cs +++ b/api/Perfis/PerfilDTO.cs @@ -1,7 +1,10 @@ +using System.ComponentModel.DataAnnotations; + namespace api.Perfis { public class PerfilDTO { + [MinLength(1)] public string Nome { get; set; } public List Permissoes { get; set; } } diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 8e9d8e2..1bcd933 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -12,7 +12,7 @@ namespace app.Controllers { [ApiController] [Route("api/perfil")] - public class PerfilController : ControllerBase + public class PerfilController : AppController { private readonly AuthService authService; private readonly IPerfilService perfilService; @@ -29,7 +29,7 @@ public PerfilController(IPerfilService perfilService, AuthService authService, I [HttpPost()] public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) { - authService.Require(User, Permissao.PerfilCadastrar); + authService.Require(Usuario, Permissao.PerfilCadastrar); var perfil = mapper.Map(perfilDTO); @@ -52,7 +52,7 @@ public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perfilDTO) { - authService.Require(User, Permissao.PerfilEditar); + authService.Require(Usuario, Permissao.PerfilEditar); Perfil perfil = mapper.Map(perfilDTO); perfil.Id = id; @@ -79,7 +79,7 @@ public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perf [HttpDelete("{id}")] public async Task ExcluirPerfil(Guid id) { - authService.Require(User, Permissao.PerfilRemover); + authService.Require(Usuario, Permissao.PerfilRemover); try{ await perfilService.ExcluirPerfil(id); @@ -102,7 +102,7 @@ public async Task ExcluirPerfil(Guid id) [HttpGet] public async Task ListarPerfis(int pageIndex, int pageSize) { - authService.Require(User, Permissao.PerfilVisualizar); + authService.Require(Usuario, Permissao.PerfilVisualizar); try { diff --git a/app/Services/AppController.cs b/app/Services/AppController.cs index e008575..921290a 100644 --- a/app/Services/AppController.cs +++ b/app/Services/AppController.cs @@ -6,6 +6,6 @@ namespace app.Services public class AppController : ControllerBase { public ClaimsPrincipal? AppUsuario { get; set; } - public ClaimsPrincipal Usuario => AppUsuario ?? User; + public ClaimsPrincipal Usuario => AppUsuario ?? Usuario; } } diff --git a/test/PerfilControllerTest.cs b/test/PerfilControllerTest.cs new file mode 100644 index 0000000..8b640ea --- /dev/null +++ b/test/PerfilControllerTest.cs @@ -0,0 +1,197 @@ +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(); + + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar}); + + 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_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(); + + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar, Permissao.PerfilEditar}); + + 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() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); + + var resposta = await perfilController.ExcluirPerfil(Guid.NewGuid()); + + Assert.IsType(resposta); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilBasico_DeveRetornarStatusCode400() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); + + 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 excluido.", retorno.Value.ToString()); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilAdministrador_DeveRetornarStatusCode400() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); + + 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 excluido.", retorno.Value.ToString()); + } + + [Fact] + public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilCustomizavel_DeveRetornarOk() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); + + 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() + { + AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar, Permissao.PerfilVisualizar}); + + var lista = PerfilStub.RetornaListaPerfilDTO(5); + + 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(5, listaRetorno.Count); + } + + public void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/test/Stub/PerfilStub.cs b/test/Stub/PerfilStub.cs index f7c3918..95dc599 100644 --- a/test/Stub/PerfilStub.cs +++ b/test/Stub/PerfilStub.cs @@ -19,7 +19,7 @@ public static PerfilPermissao RetornaPerfilPermissao(Permissao permissao = Permi }; } - public static Perfil RetornaPerfil(string nome = "PerfilTeste") + public static Perfil RetornaPerfil(string nome = "PerfilTeste", TipoPerfil tipo = TipoPerfil.Customizavel) { return new Perfil { @@ -27,13 +27,14 @@ public static Perfil RetornaPerfil(string nome = "PerfilTeste") PerfilPermissoes = new List { RetornaPerfilPermissao(), RetornaPerfilPermissao(Permissao.PerfilEditar) - } + }, + Tipo = tipo }; } public static List RetornaListaDePerfis(int n = 4) { - List lista = new(); + var lista = new List(); for(int i = 0; i < n; i++) { @@ -51,5 +52,17 @@ public static PerfilDTO RetornaPerfilDTO(string nome = "Perfil Teste") 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 From f118585fc9072053dfef0411c77a5217ee390121 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 24 Oct 2023 12:43:55 -0300 Subject: [PATCH 092/137] test: adiciona testes para listagem de permissoes --- app/Controllers/DominioController.cs | 5 ++--- test/DominioControllerTest.cs | 28 +++++++++++++++++++++++++++- test/UsuarioControllerTest.cs | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index 9793e1f..ece22d4 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -45,7 +45,7 @@ public IActionResult ObterLista() [HttpGet("permissoes")] public IActionResult ObterListaDePermissoes() { - authService.Require(User, Permissao.PerfilVisualizar); + authService.Require(Usuario, Permissao.PerfilVisualizar); var categorias = PermissaoService.ObterCategorias(); @@ -54,8 +54,7 @@ public IActionResult ObterListaDePermissoes() Categoria = c, Permissoes = PermissaoService.ObterPermissoesPortCategoria(c) }); - - + return Ok(lista); } } diff --git a/test/DominioControllerTest.cs b/test/DominioControllerTest.cs index d505802..a6b0fb2 100644 --- a/test/DominioControllerTest.cs +++ b/test/DominioControllerTest.cs @@ -3,16 +3,21 @@ using Microsoft.AspNetCore.Mvc; using Xunit.Abstractions; using Xunit.Microsoft.DependencyInjection.Abstracts; +using auth; +using api; +using System.Collections.Generic; namespace test { - public class DominioControllerTest : TestBed, IDisposable + public class DominioControllerTest : AuthTest { DominioController dominioController; public DominioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { dominioController = fixture.GetService(testOutputHelper)!; + + AutenticarUsuario(dominioController); } [Fact] @@ -21,5 +26,26 @@ public void ObterLista_QuandoMetodoForChamado_DeveRetornarListaDeUFs() var resultado = dominioController.ObterLista(); Assert.IsType(resultado); } + + [Fact] + public void ObterListaDePermissoes_QuandoNaoTemPermissao_DeveBloquear() + { + AutenticarUsuario(dominioController, permissoes: new()); + Assert.Throws(() => dominioController.ObterListaDePermissoes()); + } + + [Fact] + public void ObterListaDePermissoes_QuandoNaoPermissao_DeveRetornarOk() + { + AutenticarUsuario(dominioController, permissoes: new(){Permissao.PerfilVisualizar}); + + var resposta = dominioController.ObterListaDePermissoes(); + + Assert.IsType(resposta); + + var retorno = (resposta as OkObjectResult)!.Value as List; + + Assert.NotEmpty(retorno); + } } } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index e89185c..143fc06 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -54,7 +54,7 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : [Fact] public async Task Logar_QuandoLoginForValidado_DeveRetornarOk() { - var usuario = dbContext.PopulaUsuarios(1).First(); + var usuario = dbContext.PopulaUsuarios(1, true).First(); var resultado = await controller.Logar(usuario); From 99ba01ad20b1f543d2dc1e941c02d78722b21563 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 24 Oct 2023 14:30:58 -0300 Subject: [PATCH 093/137] fix: arruma a referencia de usuario --- app/Services/AppController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/AppController.cs b/app/Services/AppController.cs index 921290a..e008575 100644 --- a/app/Services/AppController.cs +++ b/app/Services/AppController.cs @@ -6,6 +6,6 @@ namespace app.Services public class AppController : ControllerBase { public ClaimsPrincipal? AppUsuario { get; set; } - public ClaimsPrincipal Usuario => AppUsuario ?? Usuario; + public ClaimsPrincipal Usuario => AppUsuario ?? User; } } From eae5cf672319c6a21a0421cc51ac69974fb7721a Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Tue, 24 Oct 2023 14:59:49 -0300 Subject: [PATCH 094/137] fix: adiciona timeout na selecao de categoria --- app/Services/PermissaoService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/PermissaoService.cs b/app/Services/PermissaoService.cs index 0340e5e..1625467 100644 --- a/app/Services/PermissaoService.cs +++ b/app/Services/PermissaoService.cs @@ -21,7 +21,7 @@ public List ObterCategorias() foreach(var p in permissoesOrdenadas) { - categorias.Add(Regex.Match(p, pattern).ToString()); + categorias.Add(Regex.Match(p, pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()); } return categorias.ToList(); @@ -29,7 +29,7 @@ public List ObterCategorias() public List ObterPermissoesPortCategoria(string categoria) { - var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern).ToString()); + var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()); return permissoes.Select(p => new PermissaoModel { From d413df05e892472d11d45282c434e75397a79c02 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Tue, 24 Oct 2023 21:58:51 -0300 Subject: [PATCH 095/137] =?UTF-8?q?feat:=20lista=20usu=C3=A1rios=20e=20tes?= =?UTF-8?q?ta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/ListaPaginada.cs | 20 ++++++++ api/Usuarios/PesquisaUsuarioFiltro.cs | 13 +++++ app/Controllers/UsuarioController.cs | 8 +++ .../Interfaces/IUsuarioRepositorio.cs | 2 + app/Repositorios/UsuarioRepositorio.cs | 26 +++++++--- app/Services/Interfaces/IUsuarioService.cs | 5 ++ app/Services/UsuarioService.cs | 5 ++ app/appsettings.Development.json | 2 +- test/UsuarioControllerTest.cs | 50 +++++++++++++------ test/UsuarioRepositorioTest.cs | 4 +- 10 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 api/ListaPaginada.cs create mode 100644 api/Usuarios/PesquisaUsuarioFiltro.cs 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/Usuarios/PesquisaUsuarioFiltro.cs b/api/Usuarios/PesquisaUsuarioFiltro.cs new file mode 100644 index 0000000..5ec308b --- /dev/null +++ b/api/Usuarios/PesquisaUsuarioFiltro.cs @@ -0,0 +1,13 @@ +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; } + + // string? perfilNome { get; set; } + // id? perfilId { get; set; } + } +} \ No newline at end of file diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index a589f4a..98182c3 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -6,6 +6,7 @@ using app.Services; using api; using System.Data.Common; +using app.Entidades; namespace app.Controllers { @@ -25,6 +26,13 @@ AuthService authService this.authService = authService; } + [HttpGet("listar")] + public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) + { + var usuarios = await usuarioService.ObterUsuariosAsync(filtro); + return usuarios; + } + [HttpPost("login")] public async Task Logar([FromBody] UsuarioDTO usuarioDTO) { diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index d324237..b40dcf9 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -1,3 +1,4 @@ +using api; using api.Usuarios; using app.Entidades; @@ -5,6 +6,7 @@ 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); diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 087b801..b16d085 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -1,8 +1,10 @@ +using Microsoft.EntityFrameworkCore; +using AutoMapper; + using app.Entidades; using api.Usuarios; using app.Repositorios.Interfaces; -using Microsoft.EntityFrameworkCore; -using AutoMapper; +using api; namespace app.Repositorios { @@ -16,9 +18,9 @@ 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(); } @@ -52,7 +54,7 @@ public void CadastrarUsuarioDnit(UsuarioDnit usuario) UfLotacao = usuario.UfLotacao }; - dbContext.Add(novoUsuario); + dbContext.Add(novoUsuario); } public UsuarioModel? TrocarSenha(string email, string senha) @@ -87,7 +89,7 @@ public void RemoverUuidRedefinicaoSenha(string uuid) public void InserirDadosRecuperacao(string uuid, int idUsuario) { var newRs = new RedefinicaoSenha - { + { Uuid = uuid, IdUsuario = idUsuario, }; @@ -99,7 +101,7 @@ public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) { var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); - List empresas = new List{ empresa }; + List empresas = new List { empresa }; var novoUsuarioTerceiro = new Usuario { @@ -111,5 +113,15 @@ public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) dbContext.Usuario.Add(novoUsuarioTerceiro); } + + public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + { + var total = await dbContext.Usuario.CountAsync(); + var usuarios = await dbContext.Usuario + .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) + .Take(filtro.ItemsPorPagina) + .ToListAsync(); + return new ListaPaginada(usuarios, filtro.Pagina, filtro.ItemsPorPagina, total); + } } } diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index 08222d4..c6600f0 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -1,6 +1,7 @@ using api.Usuarios; using api.Senhas; using api; +using app.Entidades; namespace app.Services.Interfaces { @@ -14,5 +15,9 @@ public interface IUsuarioService void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); Task> ListarPermissoesAsync(int userId); + + + // ou usuário DTO? + Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 2c13147..63d010e 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -197,5 +197,10 @@ public async Task> ListarPermissoesAsync(int userId) var usuario = await usuarioRepositorio.ObterUsuarioAsync(userId, includePerfil: true); return usuario!.Perfil?.Permissoes?.ToList() ?? new(); } + + public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + { + return await usuarioRepositorio.ObterUsuariosAsync(filtro); + } } } diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index 934d6ff..1d4cacf 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -9,7 +9,7 @@ } }, "Senha": { - "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha", + "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" }, "Auth": { "Enabled": false, diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index e89185c..7820a97 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -1,24 +1,22 @@ -using app.Controllers; -using api.Usuarios; -using api.Senhas; +using Moq; using Microsoft.AspNetCore.Mvc; -using Moq; -using app.Services.Interfaces; -using System; using System.Collections.Generic; -using test.Stub; -using Xunit; using System.Threading.Tasks; using Xunit.Microsoft.DependencyInjection.Abstracts; -using test.Fixtures; -using app.Entidades; using Xunit.Abstractions; -using app.Services; -using Microsoft.Extensions.Configuration; 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; namespace test { @@ -26,9 +24,8 @@ public class UsuarioControllerTest : TestBed, IDisposable { const int CREATED = 201; const int INTERNAL_SERVER_ERROR = 500; - - UsuarioController controller; - AppDbContext dbContext; + readonly UsuarioController controller; + readonly AppDbContext dbContext; public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { @@ -37,6 +34,29 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } + [Fact] + public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() + { + var filtro = new PesquisaUsuarioFiltro + { + ItemsPorPagina = 10, + }; + var usuarios = await controller.ListarAsync(filtro); + Assert.Equal(5, usuarios.Total); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUf() + { + var filtro = new PesquisaUsuarioFiltro + { + ItemsPorPagina = 100, + UfLotacao = UF.DF, + }; + + var usuarios = await controller.ListarAsync(filtro); + } + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioDTO usuario) { var resultado = await controller.Logar(usuario); diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 68ffbed..584d67a 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -133,7 +133,7 @@ public async Task CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrar var empresa = new Empresa { Cnpj = usuarioTerceiro.CNPJ, - RazaoSocial = "Empresa1" + RazaoSocial = "Empresa1" }; dbContext.Empresa.Add(empresa); @@ -142,7 +142,7 @@ public async Task CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrar repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); await dbContext.SaveChangesAsync(); - var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email); + var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email)!; Assert.Equal(usuarioTerceiro.Email, usuarioObtido.Email); Assert.Equal(usuarioTerceiro.Senha, usuarioObtido.Senha); From 67c16abd8d6e1cf411a920396e05f0322b95fa27 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:30:43 -0300 Subject: [PATCH 096/137] fix: arruma volumes e banco de dados --- docker-compose.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2457540..301f0ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,18 @@ version: "3.0" services: usuario-service: + depends_on: + - dnit-usuario-db build: context: . ports: - "7083:7083" container_name: usuario-service volumes: + - ./api:/app/api - ./app:/app/app - - ./dominio:/app/dominio - - ./repositorio:/app/repositorio - - ./service:/app/service - ./test:/app/test + - ./auth:/app/auth env_file: - .env @@ -20,6 +21,7 @@ services: image: postgres:15.4 restart: always environment: + POSTGRES_DB: usuarioservice POSTGRES_PASSWORD: 1234 ports: - "5432:5432" @@ -39,4 +41,4 @@ services: volumes: pg-data-volume: - pg-admin-volume: \ No newline at end of file + pg-admin-volume: From ad3345126808171f3bb2c1c136ed8ee9f8f66fa2 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:36:16 -0300 Subject: [PATCH 097/137] fix: adiciona modo do container --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 301f0ae..d7d9db7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,9 +13,11 @@ services: - ./app:/app/app - ./test:/app/test - ./auth:/app/auth + environment: + - MODE=container env_file: - .env - + dnit-usuario-db: container_name: dnit-usuario-db image: postgres:15.4 From a1b8b6f82fc602374ca69de66542d0519f100dd1 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Tue, 24 Oct 2023 23:36:57 -0300 Subject: [PATCH 098/137] feat: filtra por nome --- app/Repositorios/UsuarioRepositorio.cs | 10 ++++++++-- app/Services/UsuarioService.cs | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index b16d085..8d2a2f0 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -117,11 +117,17 @@ public void CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { var total = await dbContext.Usuario.CountAsync(); - var usuarios = await dbContext.Usuario + var query = dbContext.Usuario.AsQueryable(); + + if (filtro.Nome != null) { + query = dbContext.Usuario.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); + } + + var items = await query .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) .Take(filtro.ItemsPorPagina) .ToListAsync(); - return new ListaPaginada(usuarios, filtro.Pagina, filtro.ItemsPorPagina, total); + return new ListaPaginada(items, filtro.Pagina, filtro.ItemsPorPagina, total); } } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 63d010e..0271bd1 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -200,7 +200,8 @@ public async Task> ListarPermissoesAsync(int userId) public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { - return await usuarioRepositorio.ObterUsuariosAsync(filtro); + var usuarios = await usuarioRepositorio.ObterUsuariosAsync(filtro); + return usuarios; } } } From e27a59e883a7dd47f7d8d085921f7dcd840cc1b0 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Tue, 24 Oct 2023 23:59:55 -0300 Subject: [PATCH 099/137] feat: filtro por ID do perfil --- api/Usuarios/PesquisaUsuarioFiltro.cs | 4 +--- app/Repositorios/UsuarioRepositorio.cs | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/Usuarios/PesquisaUsuarioFiltro.cs b/api/Usuarios/PesquisaUsuarioFiltro.cs index 5ec308b..5bd870d 100644 --- a/api/Usuarios/PesquisaUsuarioFiltro.cs +++ b/api/Usuarios/PesquisaUsuarioFiltro.cs @@ -6,8 +6,6 @@ public class PesquisaUsuarioFiltro public int ItemsPorPagina { get; set; } = 50; public string? Nome { get; set; } public UF? UfLotacao { get; set; } - - // string? perfilNome { get; set; } - // id? perfilId { get; set; } + public Guid? PerfilId { get; set; } } } \ No newline at end of file diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 8d2a2f0..c27e89f 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -120,9 +120,12 @@ public async Task> ObterUsuariosAsync(PesquisaUsuarioFilt var query = dbContext.Usuario.AsQueryable(); if (filtro.Nome != null) { - query = dbContext.Usuario.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); + query = query.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); } + if (filtro.PerfilId != null) + query = query.Where(u => u.PerfilId == filtro.PerfilId); + var items = await query .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) .Take(filtro.ItemsPorPagina) From b0e920b55804286d84c5e966810918bd8dcfa1e9 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 25 Oct 2023 01:17:27 -0300 Subject: [PATCH 100/137] feat: adiciona rota para detalhes do perfil --- api/Perfis/PerfilModel.cs | 1 + app/Controllers/DominioController.cs | 18 +++----- app/Controllers/PerfilController.cs | 43 ++++++++++++++---- app/Services/Interfaces/IPerfilService.cs | 10 ++--- app/Services/Interfaces/IPermissaoService.cs | 6 +-- app/Services/PerfilService.cs | 46 ++++++++++---------- app/Services/PermissaoService.cs | 28 +++++++----- test/DominioControllerTest.cs | 6 +-- test/PerfilControllerTest.cs | 16 +------ test/PermissaoServiceTest.cs | 14 ------ 10 files changed, 91 insertions(+), 97 deletions(-) diff --git a/api/Perfis/PerfilModel.cs b/api/Perfis/PerfilModel.cs index e5c0eef..0eb028f 100644 --- a/api/Perfis/PerfilModel.cs +++ b/api/Perfis/PerfilModel.cs @@ -9,5 +9,6 @@ public class PerfilModel public int QuantidadeUsuarios { get; set; } public TipoPerfil Tipo { get; set; } public List Permissoes { get; set; } + public List? CategoriasPermissao { get; set; } } } diff --git a/app/Controllers/DominioController.cs b/app/Controllers/DominioController.cs index ece22d4..d3b427f 100644 --- a/app/Controllers/DominioController.cs +++ b/app/Controllers/DominioController.cs @@ -14,7 +14,7 @@ namespace app.Controllers public class DominioController : AppController { private readonly IUnidadeFederativaRepositorio unidadeFederativaRepositorio; - private readonly IPermissaoService PermissaoService; + private readonly IPermissaoService permissaoService; private readonly IMapper mapper; private readonly AuthService authService; @@ -23,12 +23,12 @@ public DominioController ( IUnidadeFederativaRepositorio unidadeFederativaRepositorio, IMapper mapper, - IPermissaoService PermissaoService, + IPermissaoService permissaoService, AuthService authService ) { this.unidadeFederativaRepositorio = unidadeFederativaRepositorio; - this.PermissaoService = PermissaoService; + this.permissaoService = permissaoService; this.authService = authService; this.mapper = mapper; } @@ -43,19 +43,11 @@ public IActionResult ObterLista() [Authorize] [HttpGet("permissoes")] - public IActionResult ObterListaDePermissoes() + public List ObterListaDePermissoes() { authService.Require(Usuario, Permissao.PerfilVisualizar); - var categorias = PermissaoService.ObterCategorias(); - - var lista = categorias.ConvertAll(c => new CategoriaPermissaoModel - { - Categoria = c, - Permissoes = PermissaoService.ObterPermissoesPortCategoria(c) - }); - - return Ok(lista); + return permissaoService.CategorizarPermissoes(Enum.GetValues().ToList()); } } } diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 1bcd933..117b4d2 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -17,12 +17,19 @@ 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 ) + public PerfilController( + IPerfilService perfilService, + AuthService authService, + IMapper mapper, + IPermissaoService permissaoService + ) { this.perfilService = perfilService; this.authService = authService; this.mapper = mapper; + this.permissaoService = permissaoService; } [Authorize] @@ -51,14 +58,13 @@ public IActionResult CriarPerfil([FromBody] PerfilDTO perfilDTO) [HttpPut("{id}")] public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perfilDTO) { - authService.Require(Usuario, Permissao.PerfilEditar); - Perfil perfil = mapper.Map(perfilDTO); + var perfil = mapper.Map(perfilDTO); perfil.Id = id; try{ - Perfil novoPerfil = await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes); + var novoPerfil = await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes); return Ok(mapper.Map(novoPerfil)); } catch(KeyNotFoundException) @@ -69,9 +75,9 @@ public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perf { return UnprocessableEntity("Este Perfil já existe"); } - catch(Exception) + catch(Exception ex) { - return StatusCode(500, "Houve um erro interno no servidor."); + return StatusCode(500, $"Houve um erro interno no servidor. {ex.Message}"); } } @@ -85,8 +91,8 @@ public async Task ExcluirPerfil(Guid id) await perfilService.ExcluirPerfil(id); return Ok("Perfil excluido"); } - catch(KeyNotFoundException){ - return NotFound("Perfil não encontrado"); + catch(KeyNotFoundException ex){ + return NotFound(ex.Message); } catch(InvalidOperationException e) { @@ -98,8 +104,8 @@ public async Task ExcluirPerfil(Guid id) } } - [Authorize] [HttpGet] + [Authorize] public async Task ListarPerfis(int pageIndex, int pageSize) { authService.Require(Usuario, Permissao.PerfilVisualizar); @@ -117,5 +123,24 @@ public async Task ListarPerfis(int pageIndex, int pageSize) 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 perfilModel; + } } } \ No newline at end of file diff --git a/app/Services/Interfaces/IPerfilService.cs b/app/Services/Interfaces/IPerfilService.cs index 1581292..4992fc0 100644 --- a/app/Services/Interfaces/IPerfilService.cs +++ b/app/Services/Interfaces/IPerfilService.cs @@ -1,4 +1,3 @@ -using api.Perfis; using app.Entidades; using api; @@ -6,9 +5,10 @@ namespace app.Services.Interfaces { public interface IPerfilService { - public Perfil CriarPerfil(Perfil perfil, List permissoes); - public Task EditarPerfil(Perfil perfil, List permissoes); - public Task ExcluirPerfil(Guid id); - public Task> ListarPerfisAsync(int pageIndex, int pageSize); + Perfil CriarPerfil(Perfil perfil, List permissoes); + Task EditarPerfil(Perfil perfil, List permissoes); + Task ExcluirPerfil(Guid id); + Task> ListarPerfisAsync(int pageIndex, int pageSize); + Task ObterPorIdAsync(Guid id); } } \ No newline at end of file diff --git a/app/Services/Interfaces/IPermissaoService.cs b/app/Services/Interfaces/IPermissaoService.cs index ce42cc5..bb03f05 100644 --- a/app/Services/Interfaces/IPermissaoService.cs +++ b/app/Services/Interfaces/IPermissaoService.cs @@ -1,10 +1,10 @@ -using api.Permissoes; +using api; namespace app.Services.Interfaces { public interface IPermissaoService { - public List ObterCategorias(); - public List ObterPermissoesPortCategoria(string categoria); + List CategorizarPermissoes(List permissaos); + List ObterCategorias(); } } \ No newline at end of file diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index 02dee31..bcfdbe9 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -1,9 +1,9 @@ using api; +using api.Perfis; using app.Entidades; using app.Repositorios.Interfaces; using app.Services.Interfaces; using AutoMapper; -using Microsoft.EntityFrameworkCore; namespace app.Services { @@ -22,11 +22,11 @@ public PerfilService(IPerfilRepositorio perfilRepositorio, AppDbContext dbContex public Perfil CriarPerfil(Perfil perfil, List permissoes) { - Perfil novoPerfil = perfilRepositorio.RegistraPerfil(perfil); + var novoPerfil = perfilRepositorio.RegistraPerfil(perfil); foreach(var permissao in permissoes) { - var novoPermissaoPerfil = perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); + perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); } dbContext.SaveChanges(); @@ -36,36 +36,26 @@ public Perfil CriarPerfil(Perfil perfil, List permissoes) public async Task EditarPerfil(Perfil perfil, List permissoes) { - Perfil perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? throw new KeyNotFoundException("Perfil não encontrado"); - if(perfilDb.Nome != perfil.Nome) - { - perfilDb.Nome = perfil.Nome; - } + perfilDb.Nome = perfil.Nome; - List permissoesRegistradas = perfilDb.Permissoes!.ToList(); + 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 permissoesRegistradas) + foreach (var permissao in permissoesDeletadas) { - if(!permissoes.Contains(permissao)) - { - var permissaoRemovida = perfilDb.PerfilPermissoes!.Where(p => p.Permissao == permissao).FirstOrDefault(); - perfilRepositorio.RemovePermissaoDoPerfil(permissaoRemovida!); - perfilDb.PerfilPermissoes!.Remove(permissaoRemovida!); - } - else - { - permissoes.Remove(permissao); - } + perfilRepositorio.RemovePermissaoDoPerfil(permissao); + perfilDb.PerfilPermissoes!.Remove(permissao); } - foreach(var permissao in permissoes) + foreach(var permissao in permissoesNovas) { - var novoPerfilPermissao = perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); + perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); } - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync(); return perfilDb; } @@ -75,6 +65,11 @@ public async Task ExcluirPerfil(Guid id) var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(id) ?? throw new KeyNotFoundException("Perfil não encontrado"); + if (perfilDb.Tipo == TipoPerfil.Basico || perfilDb.Tipo == TipoPerfil.Administrador) + { + throw new KeyNotFoundException("Não é possível excluir o administrador ou o básico"); + } + foreach(var perfilPermissao in perfilDb.PerfilPermissoes!) { perfilRepositorio.RemovePermissaoDoPerfil(perfilPermissao); @@ -97,6 +92,11 @@ public async Task> ListarPerfisAsync(int pageIndex, int pageSize) return perfis; } + public async Task ObterPorIdAsync(Guid id) + { + return await perfilRepositorio.ObterPerfilPorIdAsync(id); + } + private void PreencherPermissoesAdministrador(Perfil perfil) { perfil.PermissoesSessao = Enum.GetValues().ToList(); diff --git a/app/Services/PermissaoService.cs b/app/Services/PermissaoService.cs index 1625467..e1cc719 100644 --- a/app/Services/PermissaoService.cs +++ b/app/Services/PermissaoService.cs @@ -7,19 +7,27 @@ namespace app.Services { public class PermissaoService : IPermissaoService - { + { private const string pattern = @"^([A-Z][a-z]+)"; - public PermissaoService() + + 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); - - HashSet categorias = new(); - foreach(var p in permissoesOrdenadas) + var categorias = new HashSet(); + + foreach (var p in permissoesOrdenadas) { categorias.Add(Regex.Match(p, pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()); } @@ -27,11 +35,11 @@ public List ObterCategorias() return categorias.ToList(); } - public List ObterPermissoesPortCategoria(string categoria) - { - var permissoes = Enum.GetValues().Where(p => categoria == Regex.Match(p.ToString(), pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToString()); - - return permissoes.Select(p => new PermissaoModel + 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)! diff --git a/test/DominioControllerTest.cs b/test/DominioControllerTest.cs index a6b0fb2..c80bd4b 100644 --- a/test/DominioControllerTest.cs +++ b/test/DominioControllerTest.cs @@ -41,11 +41,7 @@ public void ObterListaDePermissoes_QuandoNaoPermissao_DeveRetornarOk() var resposta = dominioController.ObterListaDePermissoes(); - Assert.IsType(resposta); - - var retorno = (resposta as OkObjectResult)!.Value as List; - - Assert.NotEmpty(retorno); + Assert.NotEmpty(resposta); } } } diff --git a/test/PerfilControllerTest.cs b/test/PerfilControllerTest.cs index 8b640ea..f0f119d 100644 --- a/test/PerfilControllerTest.cs +++ b/test/PerfilControllerTest.cs @@ -44,8 +44,6 @@ public void CriarPerfil_QuandoTemPermissao_DeveRetornarOk() { var perfil = PerfilStub.RetornaPerfilDTO(); - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar}); - var resposta = perfilController.CriarPerfil(perfil); var retorno = (resposta as OkObjectResult)!.Value as PerfilModel; @@ -75,8 +73,6 @@ public async Task EditarPerfil_QuandoTemPermissao_DeveRetornarOk() { var perfil = PerfilStub.RetornaPerfilDTO(); - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar, Permissao.PerfilEditar}); - var resposta = perfilController.CriarPerfil(perfil); var retorno = (resposta as OkObjectResult)!.Value as PerfilModel; @@ -100,8 +96,6 @@ public async Task ExcluirPerfil_QuandoNaoTemPermissao_DeveBloquear() [Fact] public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilNaoExiste_DeveLancarNotFound() { - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); - var resposta = await perfilController.ExcluirPerfil(Guid.NewGuid()); Assert.IsType(resposta); @@ -110,8 +104,6 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilNaoExiste_DeveLancarNot [Fact] public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilBasico_DeveRetornarStatusCode400() { - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); - var perfil = PerfilStub.RetornaPerfil(tipo: TipoPerfil.Basico); perfil.Id = Guid.NewGuid(); @@ -129,8 +121,6 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilBasico_DeveRetornarStat [Fact] public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilAdministrador_DeveRetornarStatusCode400() { - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); - var perfil = PerfilStub.RetornaPerfil(tipo: TipoPerfil.Administrador); perfil.Id = Guid.NewGuid(); @@ -148,8 +138,6 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilAdministrador_DeveRetor [Fact] public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilCustomizavel_DeveRetornarOk() { - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilRemover}); - var perfil = PerfilStub.RetornaPerfil(); perfil.Id = Guid.NewGuid(); @@ -164,15 +152,13 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilCustomizavel_DeveRetorn [Fact] public async Task ListarPerfis_QuandoNaoTemPermissao_DeveBloquear() { - AutenticarUsuario(perfilController, permissoes: new(){}); + AutenticarUsuario(perfilController, permissoes: new()); await Assert.ThrowsAsync(async () => await perfilController.ListarPerfis(1, 20)); } [Fact] public async Task ListarPerfis_QuandoTemPermissao_DeveRetornarOk() { - AutenticarUsuario(perfilController, permissoes: new(){Permissao.PerfilCadastrar, Permissao.PerfilVisualizar}); - var lista = PerfilStub.RetornaListaPerfilDTO(5); lista.ForEach(p => perfilController.CriarPerfil(p)); diff --git a/test/PermissaoServiceTest.cs b/test/PermissaoServiceTest.cs index 9d6ea5e..1c56757 100644 --- a/test/PermissaoServiceTest.cs +++ b/test/PermissaoServiceTest.cs @@ -24,20 +24,6 @@ public void ObterCategorias_DeveRetornarListaComCategoriasDePermissoes() var categorias = permissaoService.ObterCategorias(); Assert.NotEmpty(categorias); } - - [Fact] - public void ObterPermissoesPortCategoria_DeveRetornarTodasPermissoesVigentes() - { - var permissoes = Enum.GetValues().ToList(); - - var categorias = permissaoService.ObterCategorias(); - - var lista = new List(); - - categorias.ForEach(c => lista.AddRange(permissaoService.ObterPermissoesPortCategoria(c).Select(l => l.Codigo))); - - Assert.Equal(permissoes.Count(), lista.Count()); - } } } \ No newline at end of file From d6319b92f3a95a2aacf0617f742b9e44bd6a8c31 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 25 Oct 2023 02:45:46 -0300 Subject: [PATCH 101/137] feat: adiciona filtro de perfil pelo nome --- app/Controllers/PerfilController.cs | 4 ++-- app/Repositorios/Interfaces/IPerfilRepositorio.cs | 2 +- app/Repositorios/PerfilRepositorio.cs | 11 ++++++++--- app/Services/Interfaces/IPerfilService.cs | 2 +- app/Services/PerfilService.cs | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 117b4d2..564e052 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -106,13 +106,13 @@ public async Task ExcluirPerfil(Guid id) [HttpGet] [Authorize] - public async Task ListarPerfis(int pageIndex, int pageSize) + public async Task ListarPerfis(int pageIndex, int pageSize, string? nome = null) { authService.Require(Usuario, Permissao.PerfilVisualizar); try { - var pagina = await perfilService.ListarPerfisAsync(pageIndex, pageSize); + var pagina = await perfilService.ListarPerfisAsync(pageIndex, pageSize, nome); List paginaRetorno = pagina.Select(p => mapper.Map(p)).ToList(); diff --git a/app/Repositorios/Interfaces/IPerfilRepositorio.cs b/app/Repositorios/Interfaces/IPerfilRepositorio.cs index 666b60d..9cd80fb 100644 --- a/app/Repositorios/Interfaces/IPerfilRepositorio.cs +++ b/app/Repositorios/Interfaces/IPerfilRepositorio.cs @@ -11,6 +11,6 @@ public interface IPerfilRepositorio public void RemovePerfil(Perfil perfil); public void RemovePermissaoDoPerfil(PerfilPermissao perfilPermissao); public Task ObterPerfilPorIdAsync(Guid id); - public Task> ListarPerfisAsync(int pageIndex, int pageSize); + public Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null); } } \ No newline at end of file diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index 384ff12..54386a8 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -61,12 +61,17 @@ public void RemovePermissaoDoPerfil(PerfilPermissao perfilPermissao) return await query.FirstOrDefaultAsync(p => p.Id == id); } - public async Task> ListarPerfisAsync(int pageIndex, int pageSize) + public async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) { var query = dbContext.Perfis.AsQueryable(); - query = query.Include(p => p.PerfilPermissoes); - query = query.Include(p => p.Usuarios); + 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) diff --git a/app/Services/Interfaces/IPerfilService.cs b/app/Services/Interfaces/IPerfilService.cs index 4992fc0..96a3089 100644 --- a/app/Services/Interfaces/IPerfilService.cs +++ b/app/Services/Interfaces/IPerfilService.cs @@ -8,7 +8,7 @@ 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); + Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome); Task ObterPorIdAsync(Guid id); } } \ No newline at end of file diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index bcfdbe9..76be568 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -80,9 +80,9 @@ public async Task ExcluirPerfil(Guid id) dbContext.SaveChanges(); } - public async Task> ListarPerfisAsync(int pageIndex, int pageSize) + public async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) { - var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize); + var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize, nome); var administrador = perfis.FirstOrDefault(p => p.Tipo == TipoPerfil.Administrador); if (administrador != null) From b2824b565cfae68aed06beac9bc7a54ac72d50df Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 05:27:05 -0300 Subject: [PATCH 102/137] test: ajusta testes --- app/Repositorios/PerfilRepositorio.cs | 7 +------ app/Services/Mapper.cs | 3 ++- app/Services/PerfilService.cs | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index 384ff12..50e5638 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -38,12 +38,7 @@ public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permis } public void RemovePerfil(Perfil perfil) - { - if(perfil.Tipo == TipoPerfil.Basico || perfil.Tipo == TipoPerfil.Administrador) - { - throw new InvalidOperationException("Esse Perfil não pode ser excluido."); - } - + { dbContext.Perfis.Remove(perfil); } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index e490054..63bbd20 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -62,7 +62,8 @@ public AutoMapperConfig() }).ToList() ) ) - .ForMember(model => model.QuantidadeUsuarios, opt => opt.MapFrom(p => p.Usuarios.Count())); + .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 index bcfdbe9..d941511 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -67,7 +67,7 @@ public async Task ExcluirPerfil(Guid id) if (perfilDb.Tipo == TipoPerfil.Basico || perfilDb.Tipo == TipoPerfil.Administrador) { - throw new KeyNotFoundException("Não é possível excluir o administrador ou o básico"); + throw new InvalidOperationException("Esse Perfil não pode ser excluído."); } foreach(var perfilPermissao in perfilDb.PerfilPermissoes!) From fec54841978502f311fe3dc999e0b2362e507e6f Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 05:28:15 -0300 Subject: [PATCH 103/137] teste: Insere mais cobertura de testes --- app/Controllers/PerfilController.cs | 4 +- test/PerfilControllerTest.cs | 64 +++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 117b4d2..5f980c5 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -126,7 +126,7 @@ public async Task ListarPerfis(int pageIndex, int pageSize) [HttpGet("{id}")] [Authorize] - public async Task> ObterPorId(Guid id) + public async Task ObterPorId(Guid id) { authService.Require(Usuario, Permissao.PerfilVisualizar); @@ -140,7 +140,7 @@ public async Task> ObterPorId(Guid id) var perfilModel = mapper.Map(perfil); perfilModel.CategoriasPermissao = permissaoService.CategorizarPermissoes(perfil.Permissoes!.ToList()); - return perfilModel; + return Ok(perfilModel); } } } \ No newline at end of file diff --git a/test/PerfilControllerTest.cs b/test/PerfilControllerTest.cs index f0f119d..9c82e77 100644 --- a/test/PerfilControllerTest.cs +++ b/test/PerfilControllerTest.cs @@ -52,6 +52,19 @@ public void CriarPerfil_QuandoTemPermissao_DeveRetornarOk() 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() { @@ -115,7 +128,7 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilBasico_DeveRetornarStat Assert.IsType(resposta); Assert.Equal(400, retorno.StatusCode); - Assert.Equal("Esse Perfil não pode ser excluido.", retorno.Value.ToString()); + Assert.Equal("Esse Perfil não pode ser excluído.", retorno.Value.ToString()); } [Fact] @@ -132,7 +145,7 @@ public async Task ExcluirPerfil_QuandoTemPermissaoEPerfilAdministrador_DeveRetor Assert.IsType(resposta); Assert.Equal(400, retorno.StatusCode); - Assert.Equal("Esse Perfil não pode ser excluido.", retorno.Value.ToString()); + Assert.Equal("Esse Perfil não pode ser excluído.", retorno.Value.ToString()); } [Fact] @@ -160,6 +173,10 @@ public async Task ListarPerfis_QuandoNaoTemPermissao_DeveBloquear() 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)); @@ -170,7 +187,48 @@ public async Task ListarPerfis_QuandoTemPermissao_DeveRetornarOk() var listaRetorno = (resposta as OkObjectResult)!.Value as List; Assert.NotEmpty(listaRetorno); - Assert.Equal(5, listaRetorno.Count); + 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() From e73e620dfac8dea5bf4000bbab67800ce3715f85 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 05:43:11 -0300 Subject: [PATCH 104/137] fix: corrige assinatura da funcao na interface do perfilService --- app/Services/Interfaces/IPerfilService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Interfaces/IPerfilService.cs b/app/Services/Interfaces/IPerfilService.cs index 96a3089..592254e 100644 --- a/app/Services/Interfaces/IPerfilService.cs +++ b/app/Services/Interfaces/IPerfilService.cs @@ -8,7 +8,7 @@ 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); + Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null); Task ObterPorIdAsync(Guid id); } } \ No newline at end of file From 990e4ed79b80462f3cfb0b669a2bcbc3d098fc4a Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 11:44:24 -0300 Subject: [PATCH 105/137] =?UTF-8?q?fix:=20adiciona=20todas=20as=20permiss?= =?UTF-8?q?=C3=B5es=20ao=20perfim=20administrador=20na=20busca=20por=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/PerfilService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index 2c88528..b047878 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -94,7 +94,14 @@ public async Task> ListarPerfisAsync(int pageIndex, int pageSize, s public async Task ObterPorIdAsync(Guid id) { - return await perfilRepositorio.ObterPerfilPorIdAsync(id); + var perfil = await perfilRepositorio.ObterPerfilPorIdAsync(id); + + if(perfil != null && perfil.Tipo == TipoPerfil.Administrador) + { + PreencherPermissoesAdministrador(perfil); + } + + return perfil; } private void PreencherPermissoesAdministrador(Perfil perfil) From 7a7464f75b4bd77fb0111000111ee1068ddb3c49 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 25 Oct 2023 13:52:54 -0300 Subject: [PATCH 106/137] =?UTF-8?q?fix:=20permissoes=20quando=20a=20autori?= =?UTF-8?q?zacao=20est=C3=A1=20desabilitada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/UsuarioService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index bbcb997..c044913 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -193,7 +193,7 @@ private async Task CriarTokenAsync(Usuario usuario) public async Task> ListarPermissoesAsync(int userId) { var usuario = await usuarioRepositorio.ObterUsuarioAsync(userId, includePerfil: true); - if (usuario!.Perfil?.Tipo == TipoPerfil.Administrador) + if (usuario!.Perfil?.Tipo == TipoPerfil.Administrador || !authConfig.Enabled) { usuario.Perfil.PermissoesSessao = Enum.GetValues().ToList(); } From 1ccaceca2c43ee86629928c841267608386d2516 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 25 Oct 2023 14:06:41 -0300 Subject: [PATCH 107/137] feat: adiciona mensagem de erro ao remover permissao de perfil do perfil basico --- app/Services/PerfilService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index b047878..a169383 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -46,6 +46,9 @@ public async Task EditarPerfil(Perfil perfil, List permissoes foreach (var permissao in permissoesDeletadas) { + if (permissao.Perfil.Tipo == TipoPerfil.Basico && permissao.Permissao.ToString().StartsWith("Perfil")) { + throw new UnauthorizedAccessException("A edição das permissões sobre o perfil básico esta desabilitada no momento. Isso será possível após a implementação da atribuição dos usuários aos perfis. Isso está desabilitado porque se nenhum usuário tiver acesso a edição de perfil, não será possível ativá-los depois."); + } perfilRepositorio.RemovePermissaoDoPerfil(permissao); perfilDb.PerfilPermissoes!.Remove(permissao); } From 7e002a1b290a93508426b0fb0237aba7722f0693 Mon Sep 17 00:00:00 2001 From: Thiago Paiva Date: Wed, 25 Oct 2023 14:20:26 -0300 Subject: [PATCH 108/137] Revert "feat: adiciona mensagem de erro ao remover permissao de perfil do perfil basico" This reverts commit 1ccaceca2c43ee86629928c841267608386d2516. --- app/Services/PerfilService.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index a169383..b047878 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -46,9 +46,6 @@ public async Task EditarPerfil(Perfil perfil, List permissoes foreach (var permissao in permissoesDeletadas) { - if (permissao.Perfil.Tipo == TipoPerfil.Basico && permissao.Permissao.ToString().StartsWith("Perfil")) { - throw new UnauthorizedAccessException("A edição das permissões sobre o perfil básico esta desabilitada no momento. Isso será possível após a implementação da atribuição dos usuários aos perfis. Isso está desabilitado porque se nenhum usuário tiver acesso a edição de perfil, não será possível ativá-los depois."); - } perfilRepositorio.RemovePermissaoDoPerfil(permissao); perfilDb.PerfilPermissoes!.Remove(permissao); } From 8833a77f36a69d544721b873a371a31f7fb84915 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 17:47:29 -0300 Subject: [PATCH 109/137] ci: remove o deploy da aws --- .github/workflows/deploy.yml | 55 ------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 0fa1701..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,55 +0,0 @@ -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 From e6053c73dc2957284ae9063435ce286927ecd7c3 Mon Sep 17 00:00:00 2001 From: Daniel Porto Date: Wed, 25 Oct 2023 18:19:28 -0300 Subject: [PATCH 110/137] =?UTF-8?q?chore:=20adiciona=20coment=C3=A1rio=20c?= =?UTF-8?q?om=20instru=C3=A7=C3=B5es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/PerfilController.cs | 4 ++++ app/Repositorios/PerfilRepositorio.cs | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index cbc44e5..998f399 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -85,6 +85,10 @@ public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perf [HttpDelete("{id}")] public async Task ExcluirPerfil(Guid id) { + // !TODO Quando um perfil com usuários atribuídos for excluído, + // deve-se migrar todos os usuários do perfil excluído para + // o perfil básico antes de efetuar a exclusão do perfil. US02! + authService.Require(Usuario, Permissao.PerfilRemover); try{ diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index ec2cabe..c0cabae 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -38,7 +38,11 @@ public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permis } public void RemovePerfil(Perfil perfil) - { + { + // TODO!! Quando um perfil com usuários atribuídos for excluído, + // deve-se migrar todos os usuários do perfil excluído para + // o perfil básico antes de efetuar a exclusão do perfil. US02! + dbContext.Perfis.Remove(perfil); } From cfbc78f1680d261ca1c0adbf81314a1aa6b746fb Mon Sep 17 00:00:00 2001 From: bottinolucas Date: Wed, 25 Oct 2023 19:36:44 -0300 Subject: [PATCH 111/137] feat: aplicando filtros por UF --- app/Program.cs | 2 +- app/Repositorios/UsuarioRepositorio.cs | 12 ++++++++++-- app/appsettings.Development.json | 2 +- docker-compose.yml | 8 +++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/Program.cs b/app/Program.cs index 793c30c..3c869a2 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -34,7 +34,7 @@ builder.Services.AddConfigRepositorios(); -builder.Services.AddCors(options => +builder.Services.AddCors(options => { options.AddPolicy("AllowAllOrigins", builder => diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 8d2a2f0..c7c0b9e 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -119,14 +119,22 @@ public async Task> ObterUsuariosAsync(PesquisaUsuarioFilt var total = await dbContext.Usuario.CountAsync(); var query = dbContext.Usuario.AsQueryable(); - if (filtro.Nome != null) { - query = dbContext.Usuario.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); + if (filtro.Nome != null) + { + query = query + .Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); + } + + if (filtro.UfLotacao != null) + { + query = query.Where(u => u.UfLotacao.Equals(filtro.UfLotacao)); } 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/appsettings.Development.json b/app/appsettings.Development.json index 1d4cacf..6133f64 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "PostgreSql": "Host=localhost;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" + "PostgreSql": "Host=dnit-usuario-db;Port=5432;Database=usuarioservice;Username=postgres;Password=1234" }, "Logging": { "LogLevel": { diff --git a/docker-compose.yml b/docker-compose.yml index 2457540..c21be93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,18 @@ version: "3.0" services: usuario-service: + depends_on: + - dnit-usuario-db build: context: . ports: - "7083:7083" container_name: usuario-service volumes: + - ./api:/app/api - ./app:/app/app - - ./dominio:/app/dominio - - ./repositorio:/app/repositorio - - ./service:/app/service - ./test:/app/test + - ./auth:/app/auth env_file: - .env @@ -20,6 +21,7 @@ services: image: postgres:15.4 restart: always environment: + POSTGRES_DB: usuarioservice POSTGRES_PASSWORD: 1234 ports: - "5432:5432" From e54b3c415644df8e767cb8f248eb373b9134c6b1 Mon Sep 17 00:00:00 2001 From: Wagner Martins <47457792+wagnermc506@users.noreply.github.com> Date: Wed, 25 Oct 2023 21:10:23 -0300 Subject: [PATCH 112/137] Delete .github/workflows/deploy.yml --- .github/workflows/deploy.yml | 55 ------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 0fa1701..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,55 +0,0 @@ -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 From 17955bb97ec6e24f23ef7018352835e08883bd5b Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Thu, 26 Oct 2023 01:36:49 -0300 Subject: [PATCH 113/137] =?UTF-8?q?chore:=20adiciona=20UsuarioModeloNovo?= =?UTF-8?q?=20tempor=C3=A1rio=20para=20desbloquear=20implementa=C3=A7?= =?UTF-8?q?=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/UsuarioModel.cs | 14 +++++- app/Controllers/UsuarioController.cs | 14 +++--- app/Repositorios/UsuarioRepositorio.cs | 9 ++-- app/Services/Interfaces/IUsuarioService.cs | 4 +- app/Services/Mapper.cs | 3 ++ app/Services/UsuarioService.cs | 5 +- test/Stub/AppDbContextExtensions.cs | 10 ++-- test/UsuarioControllerTest.cs | 55 +++++++++++++--------- 8 files changed, 66 insertions(+), 48 deletions(-) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index 9c83b2c..d6a5121 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -1,4 +1,6 @@ -namespace api.Usuarios +using api.Perfis; + +namespace api.Usuarios { public class UsuarioModel { @@ -8,4 +10,14 @@ public class UsuarioModel public string Nome { get; set; } public string Cnpj { get; set; } } + + public class UsuarioModelNovo + { + 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; } + } } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 98182c3..bed1873 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -6,7 +6,6 @@ using app.Services; using api; using System.Data.Common; -using app.Entidades; namespace app.Controllers { @@ -26,13 +25,6 @@ AuthService authService this.authService = authService; } - [HttpGet("listar")] - public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) - { - var usuarios = await usuarioService.ObterUsuariosAsync(filtro); - return usuarios; - } - [HttpPost("login")] public async Task Logar([FromBody] UsuarioDTO usuarioDTO) { @@ -132,5 +124,11 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha return NotFound(); } } + + [HttpGet()] + public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) + { + return await usuarioService.ObterUsuariosAsync(filtro); + } } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 0ce40b0..b1808c4 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -126,16 +126,15 @@ public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { - var total = await dbContext.Usuario.CountAsync(); var query = dbContext.Usuario.AsQueryable(); - if (filtro.Nome != null) { + if (filtro.Nome != null) query = query.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); - } - if (filtro.PerfilId != null) + if (filtro.PerfilId != null) query = query.Where(u => u.PerfilId == filtro.PerfilId); - + + var total = await query.CountAsync(); var items = await query .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) .Take(filtro.ItemsPorPagina) diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index c6600f0..f02ca17 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -15,9 +15,7 @@ public interface IUsuarioService void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); Task> ListarPermissoesAsync(int userId); - - // ou usuário DTO? - Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); + Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 63bbd20..d3da297 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -51,6 +51,9 @@ public AutoMapperConfig() .ForMember(p => p.Usuarios, opt => opt.Ignore()) .ForMember(p => p.Tipo, opt => opt.Ignore()) .ForMember(p => p.PermissoesSessao, opt => opt.Ignore()); + + CreateMap() + .ForMember(u => u.Cnpj, opt => opt.Ignore()); CreateMap() .ForMember(model => model.Permissoes, opt => opt.MapFrom diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index eb4cafe..b0242de 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -200,10 +200,11 @@ public async Task> ListarPermissoesAsync(int userId) return usuario!.Perfil?.Permissoes?.ToList() ?? new(); } - public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { var usuarios = await usuarioRepositorio.ObterUsuariosAsync(filtro); - return usuarios; + var modelos = mapper.Map>(usuarios.Items); + return new ListaPaginada(modelos, filtro.Pagina, filtro.ItemsPorPagina, usuarios.Total); } } } diff --git a/test/Stub/AppDbContextExtensions.cs b/test/Stub/AppDbContextExtensions.cs index 2e69a42..58ac1dc 100644 --- a/test/Stub/AppDbContextExtensions.cs +++ b/test/Stub/AppDbContextExtensions.cs @@ -1,9 +1,7 @@ -using app.Entidades; -using System; +using api; +using app.Entidades; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace test.Stub { @@ -24,7 +22,7 @@ public static List PopulaUsuarios(this AppDbContext context, i Email = usuarioDto.Email, Nome = usuarioDto.Nome, Senha = usuarioDto.SenhaHash, - UfLotacao = api.UF.DF, + UfLotacao = UF.DF, }; usuarioDto.Id = usuario.Id; @@ -40,7 +38,7 @@ public static List PopulaUsuarios(this AppDbContext context, i new PerfilPermissao { Id = Guid.NewGuid(), - Permissao = api.Permissao.EscolaCadastrar, + Permissao = Permissao.EscolaCadastrar, } } }; diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 0f6455a..0ed5948 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -34,29 +34,6 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } - [Fact] - public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() - { - var filtro = new PesquisaUsuarioFiltro - { - ItemsPorPagina = 10, - }; - var usuarios = await controller.ListarAsync(filtro); - Assert.Equal(5, usuarios.Total); - } - - [Fact] - public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUf() - { - var filtro = new PesquisaUsuarioFiltro - { - ItemsPorPagina = 100, - UfLotacao = UF.DF, - }; - - var usuarios = await controller.ListarAsync(filtro); - } - public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioDTO usuario) { var resultado = await controller.Logar(usuario); @@ -314,5 +291,37 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() dbContext.RemoveRange(dbContext.Usuario); dbContext.RemoveRange(dbContext.Empresa); } + + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() + { + var filtro = new PesquisaUsuarioFiltro + { + ItemsPorPagina = 10, + }; + var usuarios = await controller.ListarAsync(filtro); + Assert.Equal(5, usuarios.Total); + } + + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada() + { + var filtro = new PesquisaUsuarioFiltro + { + ItemsPorPagina = 100, + 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 usuarios = await controller.ListarAsync(filtro); + // Assert.Equal(3, usuarios.Items.Count); + } } } From 43a16afde61adbd3beac80c12c5c53d4c8212b24 Mon Sep 17 00:00:00 2001 From: bottinolucas Date: Thu, 26 Oct 2023 21:13:23 -0300 Subject: [PATCH 114/137] test: testa filtro para UF --- api/Usuarios/UsuarioModel.cs | 1 + app/Repositorios/UsuarioRepositorio.cs | 3 +++ test/UsuarioControllerTest.cs | 30 +++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index d6a5121..c36e478 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -19,5 +19,6 @@ public class UsuarioModelNovo public string Cnpj { get; set; } public Guid? PerfilId { get; set; } public PerfilModel? Perfil { get; set; } + public UF UfLotacao { get; set; } } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index b22a452..ea3b6fe 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -134,6 +134,9 @@ public async Task> ObterUsuariosAsync(PesquisaUsuarioFilt if (filtro.PerfilId != null) query = query.Where(u => u.PerfilId == filtro.PerfilId); + if (filtro.UfLotacao != null) + query = query.Where(u => u.UfLotacao == filtro.UfLotacao); + var total = await query.CountAsync(); var items = await query .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 0ed5948..6b6e4a2 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -306,7 +306,7 @@ public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() [Fact] public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada() - { + { var filtro = new PesquisaUsuarioFiltro { ItemsPorPagina = 100, @@ -320,8 +320,32 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada u[4].UfLotacao = UF.AM; dbContext.SaveChanges(); - var usuarios = await controller.ListarAsync(filtro); - // Assert.Equal(3, usuarios.Items.Count); + 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_QuandoFiltradoPorPerfilId_RetornarUsuariosDePerfilId() + // { + // var filtro = new PesquisaUsuarioFiltro + // { + // ItemsPorPagina = 100, + // PerfilId = Guid.Supervisor, + // }; + // 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(3, lista.Items.Count); + // } } } From 93c7cdf5c021924756be271a3ed8ae2f7879fa16 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Thu, 26 Oct 2023 21:26:55 -0300 Subject: [PATCH 115/137] chore: adiciona ideias pra testes --- app/Controllers/UsuarioController.cs | 7 +++++ test/UsuarioControllerTest.cs | 43 +++++++++++++++++++++------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index bed1873..fe62993 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -128,7 +128,14 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha [HttpGet()] public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) { + // authService.Require(Usuario, Permissao.UsuarioVisualizar); return await usuarioService.ObterUsuariosAsync(filtro); } + + // [HttpPut("{id}")] + // public async Task EditarPerfilUsuario(string usuarioId, [FromBody] string novoPerfilId) { + // authService.Require(Usuario, Permissao.PerfilEditar); + // await Task.Run(() => {}); + // } } } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 0ed5948..bf64f48 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -17,6 +17,7 @@ using api.Usuarios; using api.Senhas; using api; +using Microsoft.EntityFrameworkCore; namespace test { @@ -284,15 +285,6 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() Assert.IsType(resultado); } - public new void Dispose() - { - dbContext.RemoveRange(dbContext.PerfilPermissoes); - dbContext.RemoveRange(dbContext.Perfis); - dbContext.RemoveRange(dbContext.Usuario); - dbContext.RemoveRange(dbContext.Empresa); - } - - [Fact] public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() { @@ -321,7 +313,38 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada dbContext.SaveChanges(); var usuarios = await controller.ListarAsync(filtro); - // Assert.Equal(3, usuarios.Items.Count); + } + + // [Fact] + // public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() + // { + // // logarUsuarioSemPermissao() perfil basico ou customizável? + // var usuarioId = ""; + // var novoPerfilId = ""; + // var ex = await Assert.ThrowsAsync(async () + // => await controller.EditarPerfilUsuario(usuarioId, novoPerfilId)); + + // Assert.Equal("Sem permissão", ex.Message); + // } + + // [Fact] + // public async Task EditarPerfilUsuario_QuandoTemPermissao_NovoPerfilEhAtribuidoAoUsuario() + // { + // // logarUsuarioComPermissao() perfil de adm + // var usuarioId = ""; + // var novoPerfilId = ""; + // await controller.EditarPerfilUsuario(usuarioId, novoPerfilId); + + // var usuarioEditado = dbContext.Usuario.Find(usuarioId)!; + // Assert.Equal(novoPerfilId, usuarioEditado.PerfilId.ToString()); + // } + + public new void Dispose() + { + dbContext.RemoveRange(dbContext.PerfilPermissoes); + dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Empresa); } } } From 3e80853944c774b87f3fbc9af4d90c7090987ea8 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Thu, 26 Oct 2023 22:09:55 -0300 Subject: [PATCH 116/137] test: adiciona setup de teste --- .gitignore | 3 ++ app/Controllers/UsuarioController.cs | 12 +++-- test/UsuarioControllerTest.cs | 80 +++++++++++++++------------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index d11c8d5..0ea32d3 100644 --- a/.gitignore +++ b/.gitignore @@ -367,4 +367,7 @@ FodyWeavers.xsd .idea/ +cov.sh +coveragereport/ + report/ diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index fe62993..2e8386b 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -132,10 +132,12 @@ public async Task> ListarAsync([FromQuery] Pesqu return await usuarioService.ObterUsuariosAsync(filtro); } - // [HttpPut("{id}")] - // public async Task EditarPerfilUsuario(string usuarioId, [FromBody] string novoPerfilId) { - // authService.Require(Usuario, Permissao.PerfilEditar); - // await Task.Run(() => {}); - // } + [HttpPatch("{id}/perfil")] + // PATCH /api/usuario/{id}/perfil + public async Task EditarPerfilUsuario([FromRoute] string id, [FromBody] string novoPerfilId) { + + // authService.Require(Usuario, Permissao.PerfilEditar); + // await Task.Run(() => {}); + } } } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 73f54fa..ba261e1 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -298,10 +298,9 @@ public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() [Fact] public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada() - { + { var filtro = new PesquisaUsuarioFiltro { - ItemsPorPagina = 100, UfLotacao = UF.DF, }; var u = dbContext.Usuario.ToList(); @@ -312,25 +311,53 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada u[4].UfLotacao = UF.AM; dbContext.SaveChanges(); - var usuarios = await controller.ListarAsync(filtro); 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(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] + [Fact] + public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComPerfilDado() + { + 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_QuandoTemPermissao_DeveAlterarOPerfilDoUsuario() + { + // logarUsuarioComPermissao() perfil basico ou customizável? + var novoPerfilId = Guid.NewGuid(); + var usuario = dbContext.Usuario.First(); + + await controller.EditarPerfilUsuario(usuario.Id.ToString(), novoPerfilId.ToString()); + + var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; + Assert.Equal(novoPerfilId, usuarioEditado.PerfilId); + } + // public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() - // { - // // logarUsuarioSemPermissao() perfil basico ou customizável? - // var usuarioId = ""; - // var novoPerfilId = ""; - // var ex = await Assert.ThrowsAsync(async () - // => await controller.EditarPerfilUsuario(usuarioId, novoPerfilId)); - - // Assert.Equal("Sem permissão", ex.Message); - // } + // var usuarioId = ""; + // var novoPerfilId = ""; + // var ex = await Assert.ThrowsAsync(async () + // => await controller.EditarPerfilUsuario(usuarioId, novoPerfilId)); + + // Assert.Equal("Sem permissão", ex.Message); + // [Fact] // public async Task EditarPerfilUsuario_QuandoTemPermissao_NovoPerfilEhAtribuidoAoUsuario() @@ -345,27 +372,6 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada // } - // [Fact] - // public async void ObterUsuariosAsync_QuandoFiltradoPorPerfilId_RetornarUsuariosDePerfilId() - // { - // var filtro = new PesquisaUsuarioFiltro - // { - // ItemsPorPagina = 100, - // PerfilId = Guid.Supervisor, - // }; - // 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(3, lista.Items.Count); - // } - public new void Dispose() { dbContext.RemoveRange(dbContext.PerfilPermissoes); From 1c1140a3d941e792e4275b78c4b8986e0bec762a Mon Sep 17 00:00:00 2001 From: bottinolucas Date: Fri, 27 Oct 2023 00:56:53 -0300 Subject: [PATCH 117/137] feat: edicao de perfil de usuario e testes de perfil de usuario --- .gitignore | 3 ++- app/Controllers/UsuarioController.cs | 5 ++-- app/Services/Interfaces/IUsuarioService.cs | 1 + app/Services/UsuarioService.cs | 18 +++++++++++--- test/UsuarioControllerTest.cs | 28 ++++++++++++++++++---- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 0ea32d3..5aaf4df 100644 --- a/.gitignore +++ b/.gitignore @@ -357,7 +357,7 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ - + # Ionide (cross platform F# VS Code tools) working folder .ionide/ @@ -371,3 +371,4 @@ cov.sh coveragereport/ report/ +.vscode/ \ No newline at end of file diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 2e8386b..9ccd816 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -134,8 +134,9 @@ public async Task> ListarAsync([FromQuery] Pesqu [HttpPatch("{id}/perfil")] // PATCH /api/usuario/{id}/perfil - public async Task EditarPerfilUsuario([FromRoute] string id, [FromBody] string novoPerfilId) { - + public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] string novoPerfilId) { + + await usuarioService.EditarUsuarioPerfil(id, novoPerfilId); // authService.Require(Usuario, Permissao.PerfilEditar); // await Task.Run(() => {}); } diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index f02ca17..4821c52 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -17,5 +17,6 @@ public interface IUsuarioService Task> ListarPermissoesAsync(int userId); // ou usuário DTO? Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); + Task EditarUsuarioPerfil(int usuarioId ,string novoPerfilId); } } diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index b0242de..78c99f9 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -25,9 +25,9 @@ public class UsuarioService : IUsuarioService public UsuarioService ( - IUsuarioRepositorio usuarioRepositorio, - IMapper mapper, - IEmailService emailService, + IUsuarioRepositorio usuarioRepositorio, + IMapper mapper, + IEmailService emailService, IOptions senhaConfig, AppDbContext dbContext, AuthService autenticacaoService, @@ -206,5 +206,17 @@ public async Task> ObterUsuariosAsync(PesquisaUs var modelos = mapper.Map>(usuarios.Items); return new ListaPaginada(modelos, filtro.Pagina, filtro.ItemsPorPagina, usuarios.Total); } + + public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) //Implementar método para conseguir editar o PerfilId do usuário + { + var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId); + if (usuario == null) + throw new Exception("O usuário não foi encontrado."); + + // if() + throw new Exception("O Perfil não foi encontrado."); + usuario.PerfilId = Guid.Parse(novoPerfilId); + dbContext.SaveChanges(); + } } } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index ba261e1..6f4ad60 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -18,6 +18,7 @@ using api.Senhas; using api; using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; namespace test { @@ -251,7 +252,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsy usuarioServiceMock.Verify(service => service.RecuperarSenha(usuarioDTO), Times.Once); Assert.IsType(resultado); } - + [Fact] public async void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() { @@ -341,15 +342,34 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComP public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarOPerfilDoUsuario() { // logarUsuarioComPermissao() perfil basico ou customizável? - var novoPerfilId = Guid.NewGuid(); - var usuario = dbContext.Usuario.First(); + var novoPerfilId = Guid.NewGuid(); //String para definir o novo perfil do usuário + var usuario = dbContext.Usuario.First(); //Banco de dados - Pega o primeiro usuario - await controller.EditarPerfilUsuario(usuario.Id.ToString(), novoPerfilId.ToString()); + await controller.EditarPerfilUsuario(usuario.Id, novoPerfilId.ToString()); //Quando chamar a controller, queremos alterar no banco de dados o perfil do usuário var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; + Assert.Equal(novoPerfilId, usuarioEditado.PerfilId); } + [Fact] + public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrado() + { + var excecao = await Assert.ThrowsAsync(async() => await controller.EditarPerfilUsuario(-1, Guid.NewGuid().ToString())); + + Assert.Equal("O usuário não foi encontrado.", excecao.Message); + // Assert.Equal(novoPerfilId, usuarioEditado.PerfilId); + } + + [Fact] + public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEncontrado() + { + var usuario = dbContext.Usuario.First(); + //Guid.NewGuid().ToString() é passado devido à conversão do Guid + var excecao = await Assert.ThrowsAsync(async() => await controller.EditarPerfilUsuario(usuario.Id, Guid.NewGuid().ToString())); + Assert.Equal("O Perfil não foi encontrado.", excecao.Message); + } + // public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() // var usuarioId = ""; // var novoPerfilId = ""; From f728dd254591324b4250c93a82ad6f2b863e24c7 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Fri, 27 Oct 2023 05:50:49 -0300 Subject: [PATCH 118/137] =?UTF-8?q?feat:=20adiciona=20verifica=C3=A7=C3=A3?= =?UTF-8?q?o=20de=20permissao=20em=20editar=20perfil=20de=20usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 12 ++++ app/Controllers/UsuarioController.cs | 8 +-- app/DI/HandleExceptionFilter.cs | 57 +++++++++++++++++ app/DI/ServicesConfig.cs | 2 + app/Repositorios/UsuarioRepositorio.cs | 4 +- app/Services/ApiException.cs | 79 ++++++++++++++++++++++++ app/Services/UsuarioService.cs | 15 +++-- app/app.csproj | 1 + test/Fixtures/AuthTest.cs | 23 ++++++- test/UsuarioControllerTest.cs | 84 +++++++++++++------------- test/UsuarioServiceTest.cs | 23 ++++--- 11 files changed, 241 insertions(+), 67 deletions(-) create mode 100644 app/DI/HandleExceptionFilter.cs create mode 100644 app/Services/ApiException.cs diff --git a/api/Enums.cs b/api/Enums.cs index 8ec4135..87621c7 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -103,6 +103,18 @@ public enum Permissao SinistroCadastrar = 7000, } + 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("Permissao não encontrada")] + PermissaoNaoEncontrada, + } + + [JsonConverter(typeof(JsonStringEnumConverter))] public enum TipoPerfil { diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 9ccd816..2961ebc 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -133,12 +133,10 @@ public async Task> ListarAsync([FromQuery] Pesqu } [HttpPatch("{id}/perfil")] - // PATCH /api/usuario/{id}/perfil - public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] string novoPerfilId) { - + public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] string novoPerfilId) + { + authService.Require(Usuario, Permissao.PerfilEditar); await usuarioService.EditarUsuarioPerfil(id, novoPerfilId); - // authService.Require(Usuario, Permissao.PerfilEditar); - // await Task.Run(() => {}); } } } 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/ServicesConfig.cs b/app/DI/ServicesConfig.cs index af8fda0..41d3736 100644 --- a/app/DI/ServicesConfig.cs +++ b/app/DI/ServicesConfig.cs @@ -20,6 +20,8 @@ public static void AddConfigServices(this IServiceCollection services, IConfigur services.AddScoped(); services.AddScoped(); + services.AddControllers(o => o.Filters.Add(typeof(HandleExceptionFilter))); + services.AddAuth(configuration); } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index ea3b6fe..d7f7fb7 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -126,7 +126,9 @@ public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { - var query = dbContext.Usuario.AsQueryable(); + var query = dbContext.Usuario + .Include(u => u.Perfil) + .AsQueryable(); if (filtro.Nome != null) query = query.Where(u => u.Nome.ToLower().Contains(filtro.Nome.ToLower())); 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/UsuarioService.cs b/app/Services/UsuarioService.cs index 78c99f9..cae2b5e 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -16,6 +16,7 @@ public class UsuarioService : IUsuarioService { private readonly IUsuarioRepositorio usuarioRepositorio; + private readonly IPerfilRepositorio perfilRepositorio; private readonly IMapper mapper; private readonly IEmailService emailService; private readonly SenhaConfig senhaConfig; @@ -26,6 +27,7 @@ public class UsuarioService : IUsuarioService public UsuarioService ( IUsuarioRepositorio usuarioRepositorio, + IPerfilRepositorio perfilRepositorio, IMapper mapper, IEmailService emailService, IOptions senhaConfig, @@ -34,6 +36,7 @@ public UsuarioService IOptions authConfig ) { + this.perfilRepositorio = perfilRepositorio; this.usuarioRepositorio = usuarioRepositorio; this.mapper = mapper; this.emailService = emailService; @@ -209,13 +212,13 @@ public async Task> ObterUsuariosAsync(PesquisaUs public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) //Implementar método para conseguir editar o PerfilId do usuário { - var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId); - if (usuario == null) - throw new Exception("O usuário não foi encontrado."); + var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId) + ?? throw new ApiException(ErrorCodes.UsuarioNaoEncontrado); + + var permissao = await perfilRepositorio.ObterPerfilPorIdAsync(Guid.Parse(novoPerfilId)) + ?? throw new ApiException(ErrorCodes.PermissaoNaoEncontrada); - // if() - throw new Exception("O Perfil não foi encontrado."); - usuario.PerfilId = Guid.Parse(novoPerfilId); + usuario.PerfilId = permissao.Id; dbContext.SaveChanges(); } } diff --git a/app/app.csproj b/app/app.csproj index cc70e18..166e957 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -23,6 +23,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/test/Fixtures/AuthTest.cs b/test/Fixtures/AuthTest.cs index c6ee854..eed796c 100644 --- a/test/Fixtures/AuthTest.cs +++ b/test/Fixtures/AuthTest.cs @@ -1,10 +1,14 @@ 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; @@ -22,7 +26,8 @@ public AuthTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOut public (string Token, ClaimsPrincipal Usuario) AutenticarUsuario(AppController controller, AuthUserModel? usuario = null, List? permissoes = null) { - if (usuario == null) { + if (usuario == null) + { usuario = new AuthUserModel { Id = 1, @@ -34,7 +39,7 @@ public AuthTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOut { usuario.Permissions = permissoes; } - + var (token, _) = authService.GenerateToken(usuario); var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); @@ -43,5 +48,19 @@ public AuthTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOut controller.AppUsuario = Usuario; return (token, Usuario); } + + // public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioController controller, UsuarioDTO usuario) + // { + // var resultado = await controller.Logar(usuario); + + // Assert.IsType(resultado); + + // var login = (resultado as OkObjectResult)!.Value as LoginModel; + // var token = login!.Token.Split(" ")[1]; + + // var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); + // controller.AppUsuario = new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); + // return (token, login.TokenAtualizacao); + // } } } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 6f4ad60..aa2710f 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading.Tasks; -using Xunit.Microsoft.DependencyInjection.Abstracts; using Xunit.Abstractions; using System.Linq; using System.IdentityModel.Tokens.Jwt; @@ -17,12 +16,11 @@ using api.Usuarios; using api.Senhas; using api; -using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; +using app.Services; namespace test { - public class UsuarioControllerTest : TestBed, IDisposable + public class UsuarioControllerTest : AuthTest, IDisposable { const int CREATED = 201; const int INTERNAL_SERVER_ERROR = 500; @@ -36,7 +34,7 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } - public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioDTO usuario) + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTO usuario) { var resultado = await controller.Logar(usuario); @@ -72,7 +70,7 @@ public async Task ListarPermissoes_QuandoTiverLogado_DeveRetornarPermissoes() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - await AutenticarUsuario(usuario); + await AutenticarUsuarioLocal(usuario); var permissoes = await controller.ListarPermissoes(); Assert.NotEmpty(permissoes); } @@ -82,7 +80,7 @@ public async Task AtualizarToken_QuandoTiverValido_DeveRetornarNovoToken() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var login = await AutenticarUsuario(usuario); + var login = await AutenticarUsuarioLocal(usuario); var novoLogin = await controller.AtualizarToken(new AtualizarTokenDto { Token = login.Token, @@ -99,7 +97,7 @@ public async Task AtualizarToken_QuandoTiverInvalido_DeveRetornarNovoToken() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var login = await AutenticarUsuario(usuario); + var login = await AutenticarUsuarioLocal(usuario); var atualizarTokenDto = new AtualizarTokenDto { Token = login.Token, @@ -252,7 +250,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsy usuarioServiceMock.Verify(service => service.RecuperarSenha(usuarioDTO), Times.Once); Assert.IsType(resultado); } - + [Fact] public async void RedefinirSenha_QuandoRedefinicaoForConcluida_DeveRetornarOk() { @@ -339,58 +337,58 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComP } [Fact] - public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarOPerfilDoUsuario() + public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() { - // logarUsuarioComPermissao() perfil basico ou customizável? - var novoPerfilId = Guid.NewGuid(); //String para definir o novo perfil do usuário - var usuario = dbContext.Usuario.First(); //Banco de dados - Pega o primeiro usuario - - await controller.EditarPerfilUsuario(usuario.Id, novoPerfilId.ToString()); //Quando chamar a controller, queremos alterar no banco de dados o perfil do usuário + AutenticarUsuario(controller, permissoes: new()); + var ex = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(1, "id")); - var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; - - Assert.Equal(novoPerfilId, usuarioEditado.PerfilId); + Assert.Contains("não tem a permissão: ", ex.Message); } [Fact] public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrado() - { - var excecao = await Assert.ThrowsAsync(async() => await controller.EditarPerfilUsuario(-1, Guid.NewGuid().ToString())); + { + AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + var ex = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(-1, Guid.NewGuid().ToString())); - Assert.Equal("O usuário não foi encontrado.", excecao.Message); - // Assert.Equal(novoPerfilId, usuarioEditado.PerfilId); + Assert.Equal(ErrorCodes.UsuarioNaoEncontrado, ex.Error.Code); } [Fact] public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEncontrado() { - var usuario = dbContext.Usuario.First(); - //Guid.NewGuid().ToString() é passado devido à conversão do Guid - var excecao = await Assert.ThrowsAsync(async() => await controller.EditarPerfilUsuario(usuario.Id, Guid.NewGuid().ToString())); - Assert.Equal("O Perfil não foi encontrado.", excecao.Message); - } + AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + var usuarioId = dbContext.Usuario.First().Id; - // public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() - // var usuarioId = ""; - // var novoPerfilId = ""; - // var ex = await Assert.ThrowsAsync(async () - // => await controller.EditarPerfilUsuario(usuarioId, novoPerfilId)); + var excecao = await Assert.ThrowsAsync(async () + => await controller.EditarPerfilUsuario(usuarioId, Guid.NewGuid().ToString())); - // Assert.Equal("Sem permissão", ex.Message); + Assert.Equal(ErrorCodes.PermissaoNaoEncontrada, excecao.Error.Code); + } + [Fact] + public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarPerfilDoUsuario() + { + AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + 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(); - // [Fact] - // public async Task EditarPerfilUsuario_QuandoTemPermissao_NovoPerfilEhAtribuidoAoUsuario() - // { - // // logarUsuarioComPermissao() perfil de adm - // var usuarioId = ""; - // var novoPerfilId = ""; - // await controller.EditarPerfilUsuario(usuarioId, novoPerfilId); + await controller.EditarPerfilUsuario(usuario.Id, novoPerfilParaUsuario.Id.ToString()); - // var usuarioEditado = dbContext.Usuario.Find(usuarioId)!; - // Assert.Equal(novoPerfilId, usuarioEditado.PerfilId.ToString()); - // } + var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; + Assert.Equal(novoPerfilParaUsuario.Id, usuarioEditado.PerfilId); + } public new void Dispose() { diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 27ea1e0..0f81166 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -21,14 +21,14 @@ namespace test { public class UsuarioServiceTest : TestBed, IDisposable { - - AppDbContext dbContext; - Mock mapper; - Mock usuarioRepositorio; - Mock emailService; - AuthService authService; - Mock> authConfig; - IUsuarioService usuarioServiceMock; + 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) { @@ -36,13 +36,16 @@ public UsuarioServiceTest(ITestOutputHelper testOutputHelper, Base fixture) : ba 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, mapper.Object, emailService.Object, Options.Create(senhaConfig), dbContext, authService, authConfig.Object); + usuarioServiceMock = new UsuarioService( + usuarioRepositorio.Object, perfilRepositorio.Object, + mapper.Object, emailService.Object, + Options.Create(senhaConfig), dbContext, authService, authConfig.Object); } [Fact] From 246efe18494394b2e6942054d7f12f5c1283c4bd Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Fri, 27 Oct 2023 06:04:47 -0300 Subject: [PATCH 119/137] =?UTF-8?q?feat:=20adiciona=20verifica=C3=A7=C3=A3?= =?UTF-8?q?o=20de=20permiss=C3=A3o=20para=20visualizar=20usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 12 ++++++++++-- app/Controllers/UsuarioController.cs | 2 +- test/UsuarioControllerTest.cs | 14 +++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 87621c7..583c477 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -79,7 +79,7 @@ public enum Permissao //EmpresaEditar = 2001, //[Description("Remover Empresa")] //EmpresaRemover = 2002, - + [Description("Cadastrar Perfil de Usuário")] PerfilCadastrar = 3000, [Description("Editar Perfil de Usuário")] @@ -88,7 +88,7 @@ public enum Permissao PerfilRemover = 3002, [Description("Visualizar perfis")] PerfilVisualizar = 3003, - + [Description("Calcular UPS de sinistros")] UpsCalcularSinistro = 5000, [Description("Calcular UPS de escolas")] @@ -101,6 +101,14 @@ public enum Permissao [Description("Cadastrar sinistro")] SinistroCadastrar = 7000, + + UsuarioCadastrar = 8000, + [Description("Editar Usuário")] + UsuarioEditar = 8001, + [Description("Remover Usuário")] + UsuarioRemover = 8002, + [Description("Visualizar Usuário")] + UsuarioVisualizar = 8003, } public enum ErrorCodes diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 2961ebc..49cf7a5 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -128,7 +128,7 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha [HttpGet()] public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) { - // authService.Require(Usuario, Permissao.UsuarioVisualizar); + authService.Require(Usuario, Permissao.UsuarioVisualizar); return await usuarioService.ObterUsuariosAsync(filtro); } diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index aa2710f..c8fa324 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -284,9 +284,19 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() Assert.IsType(resultado); } + [Fact] + public async void ObterUsuariosAsync_QuandoNãoTemPermissaoVisualizar_RetornaErroDePermissao() + { + 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, @@ -298,6 +308,7 @@ public async void ObterUsuariosAsync_QuandoFiltroVazio_RetornaTodosUsuarios() [Fact] public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada() { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); var filtro = new PesquisaUsuarioFiltro { UfLotacao = UF.DF, @@ -321,6 +332,7 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada [Fact] public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComPerfilDado() { + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioVisualizar }); var filtro = new PesquisaUsuarioFiltro { PerfilId = Guid.NewGuid(), @@ -343,7 +355,7 @@ public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() var ex = await Assert.ThrowsAsync(async () => await controller.EditarPerfilUsuario(1, "id")); - Assert.Contains("não tem a permissão: ", ex.Message); + Assert.Contains("não tem a permissão: Editar Perfil", ex.Message); } [Fact] From a0318ec8a236f767d737a72e32260fbef10f03c3 Mon Sep 17 00:00:00 2001 From: bottinolucas Date: Fri, 27 Oct 2023 17:11:37 -0300 Subject: [PATCH 120/137] feat: adicao do municipio e filtragem --- api/Usuarios/UsuarioModel.cs | 1 + app/Entidades/Usuario.cs | 1 + app/Repositorios/UsuarioRepositorio.cs | 3 +++ app/Services/UsuarioService.cs | 7 +++++++ 4 files changed, 12 insertions(+) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index c36e478..91c7479 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -20,5 +20,6 @@ public class UsuarioModelNovo public Guid? PerfilId { get; set; } public PerfilModel? Perfil { get; set; } public UF UfLotacao { get; set; } + public Guid? Municipio { get; set; } } } diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 5fbb022..a1ac098 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -31,5 +31,6 @@ public class Usuario public string? TokenAtualizacao { get; set; } public DateTime? TokenAtualizacaoExpiracao { get; set; } + public Guid? Municipio { get; set; } } } \ No newline at end of file diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index d7f7fb7..f0b7572 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -139,6 +139,9 @@ public async Task> ObterUsuariosAsync(PesquisaUsuarioFilt if (filtro.UfLotacao != null) query = query.Where(u => u.UfLotacao == filtro.UfLotacao); + //if (filtro.Municipio != null) + // query = query.Where(u => usuario.Municipio == filtro.Municipio); talvez surja um problema em relação a conversão do tipo Guid para uma String + var total = await query.CountAsync(); var items = await query .Skip(filtro.ItemsPorPagina * (filtro.Pagina - 1)) diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index cae2b5e..0c5e16b 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -221,5 +221,12 @@ public async Task> ObterUsuariosAsync(PesquisaUs usuario.PerfilId = permissao.Id; dbContext.SaveChanges(); } + + /*public async Task CadastrarMunicipioAsync(string nomeMunicipio) + + + + */ + } } From fd492580ec5704e8fd86a58226c76460310689aa Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 02:07:17 -0300 Subject: [PATCH 121/137] =?UTF-8?q?fix:=20editar=20perfil=20de=20usu=C3=A1?= =?UTF-8?q?rio=20recebe=20json=20com=20id=20do=20novo=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/EditarPerfilUsuarioDTO.cs | 8 ++++++++ app/Controllers/UsuarioController.cs | 4 ++-- app/Entidades/Usuario.cs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 api/Usuarios/EditarPerfilUsuarioDTO.cs 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/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 49cf7a5..3c79729 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -133,10 +133,10 @@ public async Task> ListarAsync([FromQuery] Pesqu } [HttpPatch("{id}/perfil")] - public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] string novoPerfilId) + public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] EditarPerfilUsuarioDTO dto) { authService.Require(Usuario, Permissao.PerfilEditar); - await usuarioService.EditarUsuarioPerfil(id, novoPerfilId); + await usuarioService.EditarUsuarioPerfil(id, dto.NovoPerfilId); } } } diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index a1ac098..855e801 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -31,6 +31,6 @@ public class Usuario public string? TokenAtualizacao { get; set; } public DateTime? TokenAtualizacaoExpiracao { get; set; } - public Guid? Municipio { get; set; } + // public Guid? Municipio { get; set; } } } \ No newline at end of file From 3944fce79de8f3e8a18980056ecda32f373a153b Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 03:34:11 -0300 Subject: [PATCH 122/137] =?UTF-8?q?fix:=20controladora=20aceita=20DTO=20pa?= =?UTF-8?q?ra=20requisi=C3=A7=C3=A3o=20de=20edi=C3=A7=C3=A3o=20de=20perfil?= =?UTF-8?q?=20de=20usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/UsuarioModel.cs | 1 - app/Entidades/Usuario.cs | 1 - test/UsuarioControllerTest.cs | 15 +++++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index 91c7479..c36e478 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -20,6 +20,5 @@ public class UsuarioModelNovo public Guid? PerfilId { get; set; } public PerfilModel? Perfil { get; set; } public UF UfLotacao { get; set; } - public Guid? Municipio { get; set; } } } diff --git a/app/Entidades/Usuario.cs b/app/Entidades/Usuario.cs index 855e801..5fbb022 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -31,6 +31,5 @@ public class Usuario public string? TokenAtualizacao { get; set; } public DateTime? TokenAtualizacaoExpiracao { get; set; } - // public Guid? Municipio { get; set; } } } \ No newline at end of file diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index c8fa324..3ba51df 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -287,7 +287,7 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() [Fact] public async void ObterUsuariosAsync_QuandoNãoTemPermissaoVisualizar_RetornaErroDePermissao() { - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => await controller.ListarAsync(new PesquisaUsuarioFiltro())); Assert.Contains("não tem a permissão: Visualizar Usuário", ex.Message); @@ -352,8 +352,9 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorPerfil_RetornaUsuariosComP 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, "id")); + => await controller.EditarPerfilUsuario(1, dto)); Assert.Contains("não tem a permissão: Editar Perfil", ex.Message); } @@ -362,8 +363,9 @@ public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrado() { AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = "id" }; var ex = await Assert.ThrowsAsync(async () - => await controller.EditarPerfilUsuario(-1, Guid.NewGuid().ToString())); + => await controller.EditarPerfilUsuario(-1, dto)); Assert.Equal(ErrorCodes.UsuarioNaoEncontrado, ex.Error.Code); } @@ -373,9 +375,9 @@ public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEnco { AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); var usuarioId = dbContext.Usuario.First().Id; - + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = Guid.NewGuid().ToString() }; var excecao = await Assert.ThrowsAsync(async () - => await controller.EditarPerfilUsuario(usuarioId, Guid.NewGuid().ToString())); + => await controller.EditarPerfilUsuario(usuarioId, dto)); Assert.Equal(ErrorCodes.PermissaoNaoEncontrada, excecao.Error.Code); } @@ -394,8 +396,9 @@ public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarPerfilDoUsua dbContext.Perfis.Add(novoPerfilParaUsuario); dbContext.SaveChanges(); var usuario = dbContext.Usuario.First(); + var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = novoPerfilParaUsuario.Id.ToString() }; - await controller.EditarPerfilUsuario(usuario.Id, novoPerfilParaUsuario.Id.ToString()); + await controller.EditarPerfilUsuario(usuario.Id, dto); var usuarioEditado = dbContext.Usuario.Find(usuario.Id)!; From e7c0cdc4d427ef9820fb410af820be879de19129 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 12:50:48 -0300 Subject: [PATCH 123/137] =?UTF-8?q?refactor:=20inicia=20troca=20de=20usu?= =?UTF-8?q?=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/UsuarioDTO.cs | 7 +++++ api/Usuarios/UsuarioDnit.cs | 5 ++++ api/Usuarios/UsuarioTerceiro.cs | 5 ++++ app/Controllers/UsuarioController.cs | 12 ++++---- .../Interfaces/IUsuarioRepositorio.cs | 4 +-- app/Repositorios/UsuarioRepositorio.cs | 4 +-- app/Services/Interfaces/IUsuarioService.cs | 10 +++---- app/Services/Mapper.cs | 23 +++++++++------ app/Services/UsuarioService.cs | 23 +++++---------- test/Stub/UsuarioStub.cs | 21 ++++++-------- test/UsuarioControllerTest.cs | 10 +++---- test/UsuarioRepositorioTest.cs | 29 +++++++++---------- test/UsuarioServiceTest.cs | 28 +++++++++--------- 13 files changed, 94 insertions(+), 87 deletions(-) diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs index a622b46..45c6c45 100644 --- a/api/Usuarios/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -8,4 +8,11 @@ public class UsuarioDTO public UF UfLotacao { get; set; } public string? CNPJ { get; set; } } + + public class UsuarioDTONovo + { + public string Email { get; set; } + public string Senha { get; set; } + public string Nome { get; set; } + } } diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs index 3ce3909..ebbb9a9 100644 --- a/api/Usuarios/UsuarioDnit.cs +++ b/api/Usuarios/UsuarioDnit.cs @@ -4,4 +4,9 @@ public class UsuarioDnit : UsuarioModel { public UF UfLotacao { get; set; } } + + public class UsuarioDnitNovo : UsuarioDTONovo + { + public UF UfLotacao { get; set; } + } } \ No newline at end of file diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs index 1ac8d6c..660b702 100644 --- a/api/Usuarios/UsuarioTerceiro.cs +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -4,4 +4,9 @@ public class UsuarioTerceiro : UsuarioModel { public string CNPJ { get; set; } } + + public class UsuarioTerceiroNovo : UsuarioDTONovo + { + public string CNPJ { get; set; } + } } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 3c79729..915e178 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -26,7 +26,7 @@ AuthService authService } [HttpPost("login")] - public async Task Logar([FromBody] UsuarioDTO usuarioDTO) + public async Task Logar([FromBody] UsuarioDTONovo usuarioDTO) { try { @@ -59,7 +59,7 @@ public async Task AtualizarToken([FromBody] AtualizarTokenDto atuali [HttpPost("cadastrarUsuarioDnit")] - public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) + public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTONovo usuarioDTO) { try { @@ -71,14 +71,14 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua { return Conflict("Usuário já cadastrado."); } - catch (Exception) + catch (Exception ex) { return StatusCode(500, "Houve um erro interno no servidor."); } } [HttpPost("cadastrarUsuarioTerceiro")] - public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) + public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTONovo usuarioDTO) { try { @@ -97,7 +97,7 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) } [HttpPut("recuperarSenha")] - public async Task RecuperarSenhaAsync([FromBody] UsuarioDTO usuarioDto) + public async Task RecuperarSenhaAsync([FromBody] UsuarioDTONovo usuarioDto) { try { @@ -125,6 +125,7 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha } } + [Authorize] [HttpGet()] public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) { @@ -132,6 +133,7 @@ public async Task> ListarAsync([FromQuery] Pesqu return await usuarioService.ObterUsuariosAsync(filtro); } + [Authorize] [HttpPatch("{id}/perfil")] public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] EditarPerfilUsuarioDTO dto) { diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 029f8cc..af9cd17 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -13,7 +13,7 @@ public interface IUsuarioRepositorio void InserirDadosRecuperacao(string uuid, int idUsuario); string? ObterEmailRedefinicaoSenha(string uuid); void RemoverUuidRedefinicaoSenha(string uuid); - Task CadastrarUsuarioDnit(UsuarioDnit usuario); - Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); + Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario); + Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro); } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index f0b7572..1ae0184 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -45,7 +45,7 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) return await query.FirstOrDefaultAsync(); } - public async Task CadastrarUsuarioDnit(UsuarioDnit usuario) + public async Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario) { var novoUsuario = new Usuario @@ -100,7 +100,7 @@ public void InserirDadosRecuperacao(string uuid, int idUsuario) dbContext.RedefinicaoSenha.Add(newRs); } - public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) + public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro) { var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index 4821c52..d24564a 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -1,21 +1,19 @@ using api.Usuarios; using api.Senhas; using api; -using app.Entidades; namespace app.Services.Interfaces { public interface IUsuarioService { Task AutenticarUsuarioAsync(string email, string senha); - bool ValidaLogin(UsuarioDTO usuarioDTO); + bool ValidaLogin(UsuarioDTONovo usuarioDTO); Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); - Task RecuperarSenha(UsuarioDTO usuarioDto); - Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO); - void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO); + Task RecuperarSenha(UsuarioDTONovo usuarioDto); + Task CadastrarUsuarioDnit(UsuarioDTONovo usuarioDTO); + void CadastrarUsuarioTerceiro(UsuarioDTONovo usuarioDTO); Task AtualizarTokenAsync(AtualizarTokenDto atualizarTokenDto); Task> ListarPermissoesAsync(int userId); - // ou usuário DTO? Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); Task EditarUsuarioPerfil(int usuarioId ,string novoPerfilId); } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index d3da297..506b032 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -31,19 +31,27 @@ public AutoMapperConfig() CreateMap() .ForMember(dto => dto.Cnpj, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); + CreateMap() + .ForMember(u => u.Cnpj, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(u => u.CNPJ, opt => opt.Ignore()); + + CreateMap() + .ForMember(u => u.UfLotacao, opt => opt.Ignore()); + + CreateMap() + .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); + 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() - .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); - CreateMap(); - CreateMap() - .ForMember(usuarioTerceiro => usuarioTerceiro.Id, opt => opt.Ignore()); - CreateMap() .ForMember(p => p.Id, opt => opt.Ignore()) .ForMember(p => p.Permissoes, opt => opt.Ignore()) @@ -52,9 +60,6 @@ public AutoMapperConfig() .ForMember(p => p.Tipo, opt => opt.Ignore()) .ForMember(p => p.PermissoesSessao, opt => opt.Ignore()); - CreateMap() - .ForMember(u => u.Cnpj, opt => opt.Ignore()); - CreateMap() .ForMember(model => model.Permissoes, opt => opt.MapFrom ( diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 0c5e16b..5fa6318 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -46,9 +46,9 @@ IOptions authConfig this.authConfig = authConfig.Value; } - public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) + public async Task CadastrarUsuarioDnit(UsuarioDTONovo usuarioDTO) { - var usuario = mapper.Map(usuarioDTO); + var usuario = mapper.Map(usuarioDTO); usuario.Senha = EncriptarSenha(usuario.Senha); @@ -64,12 +64,10 @@ private string EncriptarSenha(string senha) return BCryptNet.HashPassword(senha, salt); } - public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) + public void CadastrarUsuarioTerceiro(UsuarioDTONovo usuarioDTO) { - var usuario = mapper.Map(usuarioDTO); - + var usuario = mapper.Map(usuarioDTO); usuario.Senha = EncriptarSenha(usuario.Senha); - usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); } @@ -93,7 +91,7 @@ public async Task AutenticarUsuarioAsync(string email, string senha) return usuario; } - public bool ValidaLogin(UsuarioDTO usuarioDTO) + public bool ValidaLogin(UsuarioDTONovo usuarioDTO) { Usuario? usuarioBanco = Obter(usuarioDTO.Email); @@ -124,9 +122,9 @@ public async Task TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) await dbContext.SaveChangesAsync(); } - public async Task RecuperarSenha(UsuarioDTO usuarioDTO) + public async Task RecuperarSenha(UsuarioDTONovo usuarioDTO) { - var usuarioEntrada = mapper.Map(usuarioDTO); + var usuarioEntrada = mapper.Map(usuarioDTO); Usuario usuarioBanco = Obter(usuarioEntrada.Email); @@ -221,12 +219,5 @@ public async Task> ObterUsuariosAsync(PesquisaUs usuario.PerfilId = permissao.Id; dbContext.SaveChanges(); } - - /*public async Task CadastrarMunicipioAsync(string nomeMunicipio) - - - - */ - } } diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 48dad95..1475c13 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -6,7 +6,7 @@ namespace test.Stub { - public class TesteUsuarioStub : UsuarioDTO + public class TesteUsuarioStub : UsuarioDTONovo { public int Id { get; set; } public string SenhaHash { get; set; } @@ -21,38 +21,35 @@ public static IEnumerable Listar() yield return new TesteUsuarioStub() { Nome = "teste " + Random.Shared.Next().ToString(), - CNPJ = string.Join("", Enumerable.Range(0, 11).Select(_ => Random.Shared.Next() % 10)), Email = $"teste{Random.Shared.Next()}@email.com", Senha = $"teste_senha_{Random.Shared.Next()}", }; } } - public UsuarioDTO RetornarUsuarioDnitDTO() + public UsuarioDTONovo RetornarUsuarioDnitDTO() { - return new UsuarioDTO + return new UsuarioDTONovo { Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UfLotacao = UF.DF }; } - public UsuarioDTO RetornarUsuarioTerceiroDTO() + public UsuarioDTONovo RetornarUsuarioTerceiroDTO() { - return new UsuarioDTO + return new UsuarioDTONovo { Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - CNPJ = "12345678901234" }; } - public UsuarioDnit RetornarUsuarioDnit() + public UsuarioDnitNovo RetornarUsuarioDnit() { - return new UsuarioDnit + return new UsuarioDnitNovo { Email = "usuarioteste@gmail.com", Senha = "senha1234", @@ -83,9 +80,9 @@ public UsuarioDTO RetornarUsuarioSenhaErrada() }; } - public UsuarioTerceiro RetornarUsuarioTerceiro() + public UsuarioTerceiroNovo RetornarUsuarioTerceiro() { - return new UsuarioTerceiro + return new UsuarioTerceiroNovo { Email = "usuarioteste@gmail.com", Senha = "senha1234", diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 3ba51df..85a65b6 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -34,7 +34,7 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } - public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTO usuario) + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTONovo usuario) { var resultado = await controller.Logar(usuario); @@ -150,7 +150,7 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConfli Mock usuarioServiceMock = new(); var excecao = new Npgsql.PostgresException("", "", "", "23505"); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -187,7 +187,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioJaExistir_DeveRetornarConflict Mock usuarioServiceMock = new(); var excecao = new Npgsql.PostgresException("", "", "", "23505"); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -206,7 +206,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern Mock usuarioServiceMock = new(); var excecao = new Exception(""); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -241,7 +241,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsy var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.RecuperarSenha(It.IsAny())).Throws(new KeyNotFoundException()); + usuarioServiceMock.Setup(service => service.RecuperarSenha(It.IsAny())).Throws(new KeyNotFoundException()); var controller = new UsuarioController(usuarioServiceMock.Object, null); diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 584d67a..9533f9b 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -14,15 +14,13 @@ namespace test { public class UsuarioRepositorioTest : TestBed, IDisposable { - IUsuarioRepositorio repositorio; - AppDbContext dbContext; - IMapper mapper; + readonly IUsuarioRepositorio repositorio; + readonly AppDbContext dbContext; public UsuarioRepositorioTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOutputHelper, fixture) { dbContext = fixture.GetService(testOutputHelper)!; repositorio = fixture.GetService(testOutputHelper)!; - mapper = fixture.GetService(testOutputHelper)!; } [Fact] @@ -31,7 +29,7 @@ public async Task ObterUsuario_QuandoEmailForPassado_DeveRetornarUsuarioCorrespo 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"); @@ -47,16 +45,15 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioForPassado_DeveCadastrarUsua var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); await dbContext.SaveChangesAsync(); - var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault(); - var usuarioObtidoDTO = mapper.Map(usuarioObtido); + var usuarioObtido = dbContext.Usuario.Where(u => u.Email == usuarioDNIT.Email).FirstOrDefault()!; - Assert.Equal(usuarioDNIT.Email, usuarioObtidoDTO.Email); - Assert.Equal(usuarioDNIT.Senha, usuarioObtidoDTO.Senha); - Assert.Equal(usuarioDNIT.Nome, usuarioObtidoDTO.Nome); - Assert.Equal(usuarioDNIT.UfLotacao, usuarioObtidoDTO.UfLotacao); + Assert.Equal(usuarioDNIT.Email, usuarioObtido.Email); + Assert.Equal(usuarioDNIT.Senha, usuarioObtido.Senha); + Assert.Equal(usuarioDNIT.Nome, usuarioObtido.Nome); + Assert.Equal(usuarioDNIT.UfLotacao, usuarioObtido.UfLotacao); } [Fact] @@ -65,7 +62,7 @@ public async Task TrocarSenha_QuandoNovaSenhaForPassada_DeveAtualizarSenhaDoUsua var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); await dbContext.SaveChangesAsync(); string novaSenha = "NovaSenha"; @@ -86,7 +83,7 @@ public async Task ObterEmailRedefinicaoSenha_QuandoUuidForPassado_DeveRetornarEm var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); @@ -107,7 +104,7 @@ public async Task RemoverUuidRedefinicaoSenha_QuandoUuidForPassado_DeveRemoverUu var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); var redefinicaoSenha = redefinicaoSenhaStub.ObterRedefinicaoSenha(); - repositorio.CadastrarUsuarioDnit(usuarioDNIT); + await repositorio.CadastrarUsuarioDnit(usuarioDNIT); await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioDNIT.Email); @@ -139,7 +136,7 @@ public async Task CadastrarUsuarioTerceiro_QuandoUsuarioForPassado_DeveCadastrar dbContext.Empresa.Add(empresa); await dbContext.SaveChangesAsync(); - repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); + await repositorio.CadastrarUsuarioTerceiro(usuarioTerceiro); await dbContext.SaveChangesAsync(); var usuarioObtido = repositorio.ObterUsuario(usuarioTerceiro.Email)!; diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 0f81166..9ccd2cc 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -55,11 +55,11 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrar var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); string senhaAntesDaEncriptografia = usuarioDNIT.Senha; - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); + usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); Assert.NotEqual(senhaAntesDaEncriptografia, usuarioDNIT.Senha); } @@ -72,11 +72,11 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr var senhaAntesDaEncriptografia = usuarioTerceiro.Senha; - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); - usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); + usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); Assert.NotEqual(senhaAntesDaEncriptografia, usuarioTerceiro.Senha); } @@ -87,8 +87,8 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_De var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); var cadastrarUsuario = async () => await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); @@ -101,8 +101,8 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroJaExistenteForPassado_ var usuarioStub = new UsuarioStub(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); var cadastrarUsuario = () => usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); @@ -151,7 +151,7 @@ public void ValidaLogin_QuandoUsuarioInexistenteForPassado_NaoDeveRealizarLogin( } [Fact] - public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSenha() + public async void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSenha() { var usuarioStub = new UsuarioStub(); var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); @@ -159,12 +159,12 @@ public void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperacaoDeSe var usuarioRetorno = usuarioStub.RetornarUsuarioDnitBanco(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + 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(usuarioRetorno); - usuarioServiceMock.RecuperarSenha(usuarioDnitDTO); + await usuarioServiceMock.RecuperarSenha(usuarioDnitDTO); emailService.Verify(x => x.EnviarEmail(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -176,7 +176,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + 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); @@ -194,7 +194,7 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() 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(usuarioDNIT); mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); @@ -217,7 +217,7 @@ public async Task TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() 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(usuarioDNIT); mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); From 36147ecec974c76b6d27c6618ebf7d3f57ec49df Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 13:24:54 -0300 Subject: [PATCH 124/137] =?UTF-8?q?refactor:=20remove=20todas=20as=20refer?= =?UTF-8?q?=C3=AAncias=20=C3=A0s=20classes=20antigas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/UsuarioDTO.cs | 9 ---- api/Usuarios/UsuarioDnit.cs | 5 --- api/Usuarios/UsuarioModel.cs | 9 ---- api/Usuarios/UsuarioTerceiro.cs | 5 --- app/Controllers/UsuarioController.cs | 3 +- .../Interfaces/IUsuarioRepositorio.cs | 2 +- app/Repositorios/UsuarioRepositorio.cs | 41 +++++++++---------- app/Services/Mapper.cs | 23 ----------- test/Stub/UsuarioStub.cs | 11 ----- test/UsuarioRepositorioTest.cs | 5 +-- 10 files changed, 23 insertions(+), 90 deletions(-) diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs index 45c6c45..a5396c4 100644 --- a/api/Usuarios/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -1,14 +1,5 @@ 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; } - public string? CNPJ { get; set; } - } - public class UsuarioDTONovo { public string Email { get; set; } diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs index ebbb9a9..e9b03a9 100644 --- a/api/Usuarios/UsuarioDnit.cs +++ b/api/Usuarios/UsuarioDnit.cs @@ -1,10 +1,5 @@ namespace api.Usuarios { - public class UsuarioDnit : UsuarioModel - { - public UF UfLotacao { get; set; } - } - public class UsuarioDnitNovo : UsuarioDTONovo { public UF UfLotacao { get; set; } diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index c36e478..9870721 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -2,15 +2,6 @@ namespace api.Usuarios { - public class UsuarioModel - { - public int Id { get; set; } - public string Email { get; set; } - public string Senha { get; set; } - public string Nome { get; set; } - public string Cnpj { get; set; } - } - public class UsuarioModelNovo { public int Id { get; set; } diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs index 660b702..2224f16 100644 --- a/api/Usuarios/UsuarioTerceiro.cs +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -1,10 +1,5 @@ namespace api.Usuarios { - public class UsuarioTerceiro : UsuarioModel - { - public string CNPJ { get; set; } - } - public class UsuarioTerceiroNovo : UsuarioDTONovo { public string CNPJ { get; set; } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 915e178..323c71e 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -64,14 +64,13 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTONovo try { await usuarioService.CadastrarUsuarioDnit(usuarioDTO); - return StatusCode(201, new NoContentResult()); } catch (DbException) { return Conflict("Usuário já cadastrado."); } - catch (Exception ex) + catch (Exception) { return StatusCode(500, "Houve um erro interno no servidor."); } diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index af9cd17..1b46e99 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -9,7 +9,7 @@ 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); + UsuarioModelNovo? TrocarSenha(string senha, string email); void InserirDadosRecuperacao(string uuid, int idUsuario); string? ObterEmailRedefinicaoSenha(string uuid); void RemoverUuidRedefinicaoSenha(string uuid); diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 1ae0184..539f549 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -3,7 +3,6 @@ using app.Entidades; using api.Usuarios; -using api; using app.Repositorios.Interfaces; using api; @@ -60,7 +59,25 @@ public async Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario) dbContext.Add(novoUsuario); } - public UsuarioModel? TrocarSenha(string email, string senha) + public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo 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 UsuarioModelNovo? TrocarSenha(string email, string senha) { var usuario = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); @@ -69,7 +86,7 @@ public async Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario) usuario.Senha = senha; } - return mapper.Map(usuario); + return mapper.Map(usuario); } public string? ObterEmailRedefinicaoSenha(string uuid) @@ -100,24 +117,6 @@ public void InserirDadosRecuperacao(string uuid, int idUsuario) dbContext.RedefinicaoSenha.Add(newRs); } - public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro) - { - var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); - - List empresas = new List { empresa }; - - var novoUsuarioTerceiro = new Usuario - { - Nome = usuarioTerceiro.Nome, - Email = usuarioTerceiro.Email, - Senha = usuarioTerceiro.Senha, - Empresas = empresas, - Perfil = await RecuperaPerfilBasicoAsync() - }; - - dbContext.Usuario.Add(novoUsuarioTerceiro); - } - private async Task RecuperaPerfilBasicoAsync() { return await dbContext.Perfis.Where(p => p.Tipo == TipoPerfil.Basico) diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 506b032..3129cf5 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -13,38 +13,15 @@ public class AutoMapperConfig : Profile { public AutoMapperConfig() { - CreateMap() - .ForMember(dto => dto.CNPJ, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); - - CreateMap() - .ForMember(u => u.RedefinicaoSenha, opt => opt.Ignore()) - .ForMember(u => u.Empresas, opt => opt.Ignore()) - .ForMember(u => u.Perfil, opt => opt.Ignore()) - .ForMember(u => u.PerfilId, opt => opt.Ignore()) - .ForMember(u => u.TokenAtualizacao, opt => opt.Ignore()) - .ForMember(u => u.TokenAtualizacaoExpiracao, opt => opt.Ignore()); - - CreateMap() - .ForMember(dto => dto.CNPJ, opt => opt.Ignore()) - .ForMember(dto => dto.UfLotacao, opt => opt.Ignore()); - - CreateMap() - .ForMember(dto => dto.Cnpj, opt => opt.MapFrom(u => u.Empresas.FirstOrDefault().Cnpj)); - CreateMap() .ForMember(u => u.Cnpj, opt => opt.Ignore()); - CreateMap(); - CreateMap() .ForMember(u => u.CNPJ, opt => opt.Ignore()); CreateMap() .ForMember(u => u.UfLotacao, opt => opt.Ignore()); - CreateMap() - .ForMember(usuarioDnit => usuarioDnit.Id, opt => opt.Ignore()); - CreateMap() .ForMember(model => model.Id, opt => opt.MapFrom(uf => (int)uf)) .ForMember(model => model.Sigla, opt => opt.MapFrom(uf => uf.ToString())) diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 1475c13..87b65de 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -69,17 +69,6 @@ public Usuario RetornarUsuarioDnitBanco() }; } - public UsuarioDTO RetornarUsuarioSenhaErrada() - { - return new UsuarioDTO - { - Email = "usuarioteste@gmail.com", - Senha = "senha1234", - Nome = "Usuario Dnit", - UfLotacao = UF.DF - }; - } - public UsuarioTerceiroNovo RetornarUsuarioTerceiro() { return new UsuarioTerceiroNovo diff --git a/test/UsuarioRepositorioTest.cs b/test/UsuarioRepositorioTest.cs index 9533f9b..a9c6c5b 100644 --- a/test/UsuarioRepositorioTest.cs +++ b/test/UsuarioRepositorioTest.cs @@ -1,13 +1,10 @@ -using app.Repositorios; -using app.Repositorios.Interfaces; -using api.Usuarios; +using app.Repositorios.Interfaces; using test.Stub; using test.Fixtures; using app.Entidades; using Xunit.Abstractions; using Xunit.Microsoft.DependencyInjection.Abstracts; using System.Linq; -using AutoMapper; using System.Threading.Tasks; namespace test From b6cace40d3eaa47a324b4e690e62cb526c0c6e58 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 13:27:11 -0300 Subject: [PATCH 125/137] =?UTF-8?q?refactor:=20remove=20'novo'=20das=20cla?= =?UTF-8?q?sses=20finalizando=20a=20substitui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Usuarios/UsuarioDTO.cs | 2 +- api/Usuarios/UsuarioDnit.cs | 2 +- api/Usuarios/UsuarioModel.cs | 2 +- api/Usuarios/UsuarioTerceiro.cs | 2 +- app/Controllers/UsuarioController.cs | 10 ++++---- .../Interfaces/IUsuarioRepositorio.cs | 6 ++--- app/Repositorios/UsuarioRepositorio.cs | 8 +++---- app/Services/Interfaces/IUsuarioService.cs | 10 ++++---- app/Services/Mapper.cs | 6 ++--- app/Services/UsuarioService.cs | 20 ++++++++-------- test/Stub/UsuarioStub.cs | 18 +++++++------- test/UsuarioControllerTest.cs | 10 ++++---- test/UsuarioServiceTest.cs | 24 +++++++++---------- 13 files changed, 60 insertions(+), 60 deletions(-) diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs index a5396c4..8f44733 100644 --- a/api/Usuarios/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -1,6 +1,6 @@ namespace api.Usuarios { - public class UsuarioDTONovo + public class UsuarioDTO { public string Email { get; set; } public string Senha { get; set; } diff --git a/api/Usuarios/UsuarioDnit.cs b/api/Usuarios/UsuarioDnit.cs index e9b03a9..cf6fb0d 100644 --- a/api/Usuarios/UsuarioDnit.cs +++ b/api/Usuarios/UsuarioDnit.cs @@ -1,6 +1,6 @@ namespace api.Usuarios { - public class UsuarioDnitNovo : UsuarioDTONovo + public class UsuarioDnit : UsuarioDTO { public UF UfLotacao { get; set; } } diff --git a/api/Usuarios/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index 9870721..f6ae6a7 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -2,7 +2,7 @@ namespace api.Usuarios { - public class UsuarioModelNovo + public class UsuarioModel { public int Id { get; set; } public string Email { get; set; } diff --git a/api/Usuarios/UsuarioTerceiro.cs b/api/Usuarios/UsuarioTerceiro.cs index 2224f16..aa56571 100644 --- a/api/Usuarios/UsuarioTerceiro.cs +++ b/api/Usuarios/UsuarioTerceiro.cs @@ -1,6 +1,6 @@ namespace api.Usuarios { - public class UsuarioTerceiroNovo : UsuarioDTONovo + public class UsuarioTerceiro : UsuarioDTO { public string CNPJ { get; set; } } diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 323c71e..3860c51 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -26,7 +26,7 @@ AuthService authService } [HttpPost("login")] - public async Task Logar([FromBody] UsuarioDTONovo usuarioDTO) + public async Task Logar([FromBody] UsuarioDTO usuarioDTO) { try { @@ -59,7 +59,7 @@ public async Task AtualizarToken([FromBody] AtualizarTokenDto atuali [HttpPost("cadastrarUsuarioDnit")] - public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTONovo usuarioDTO) + public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usuarioDTO) { try { @@ -77,7 +77,7 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTONovo } [HttpPost("cadastrarUsuarioTerceiro")] - public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTONovo usuarioDTO) + public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTO usuarioDTO) { try { @@ -96,7 +96,7 @@ public IActionResult CadastrarUsuarioTerceiro([FromBody] UsuarioDTONovo usuarioD } [HttpPut("recuperarSenha")] - public async Task RecuperarSenhaAsync([FromBody] UsuarioDTONovo usuarioDto) + public async Task RecuperarSenhaAsync([FromBody] UsuarioDTO usuarioDto) { try { @@ -126,7 +126,7 @@ public async Task RedefinirSenhaAsync([FromBody] RedefinicaoSenha [Authorize] [HttpGet()] - public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) + public async Task> ListarAsync([FromQuery] PesquisaUsuarioFiltro filtro) { authService.Require(Usuario, Permissao.UsuarioVisualizar); return await usuarioService.ObterUsuariosAsync(filtro); diff --git a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs index 1b46e99..029f8cc 100644 --- a/app/Repositorios/Interfaces/IUsuarioRepositorio.cs +++ b/app/Repositorios/Interfaces/IUsuarioRepositorio.cs @@ -9,11 +9,11 @@ public interface IUsuarioRepositorio Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); Usuario? ObterUsuario(string email); Task ObterUsuarioAsync(int? id = null, string? email = null, bool includePerfil = false); - UsuarioModelNovo? TrocarSenha(string senha, string email); + UsuarioModel? TrocarSenha(string senha, string email); void InserirDadosRecuperacao(string uuid, int idUsuario); string? ObterEmailRedefinicaoSenha(string uuid); void RemoverUuidRedefinicaoSenha(string uuid); - Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario); - Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro); + Task CadastrarUsuarioDnit(UsuarioDnit usuario); + Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro); } } diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 539f549..89f2750 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -44,7 +44,7 @@ public UsuarioRepositorio(AppDbContext dbContext, IMapper mapper) return await query.FirstOrDefaultAsync(); } - public async Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario) + public async Task CadastrarUsuarioDnit(UsuarioDnit usuario) { var novoUsuario = new Usuario @@ -59,7 +59,7 @@ public async Task CadastrarUsuarioDnit(UsuarioDnitNovo usuario) dbContext.Add(novoUsuario); } - public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro) + public async Task CadastrarUsuarioTerceiro(UsuarioTerceiro usuarioTerceiro) { var empresa = dbContext.Empresa.Where(e => e.Cnpj == usuarioTerceiro.CNPJ).FirstOrDefault(); @@ -77,7 +77,7 @@ public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro) dbContext.Usuario.Add(novoUsuarioTerceiro); } - public UsuarioModelNovo? TrocarSenha(string email, string senha) + public UsuarioModel? TrocarSenha(string email, string senha) { var usuario = dbContext.Usuario.Where(u => u.Email == email).FirstOrDefault(); @@ -86,7 +86,7 @@ public async Task CadastrarUsuarioTerceiro(UsuarioTerceiroNovo usuarioTerceiro) usuario.Senha = senha; } - return mapper.Map(usuario); + return mapper.Map(usuario); } public string? ObterEmailRedefinicaoSenha(string uuid) diff --git a/app/Services/Interfaces/IUsuarioService.cs b/app/Services/Interfaces/IUsuarioService.cs index d24564a..6d9a843 100644 --- a/app/Services/Interfaces/IUsuarioService.cs +++ b/app/Services/Interfaces/IUsuarioService.cs @@ -7,14 +7,14 @@ namespace app.Services.Interfaces public interface IUsuarioService { Task AutenticarUsuarioAsync(string email, string senha); - bool ValidaLogin(UsuarioDTONovo usuarioDTO); + bool ValidaLogin(UsuarioDTO usuarioDTO); Task TrocaSenha(RedefinicaoSenhaDTO redefinirSenhaDto); - Task RecuperarSenha(UsuarioDTONovo usuarioDto); - Task CadastrarUsuarioDnit(UsuarioDTONovo usuarioDTO); - void CadastrarUsuarioTerceiro(UsuarioDTONovo usuarioDTO); + 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> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro); Task EditarUsuarioPerfil(int usuarioId ,string novoPerfilId); } } diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 3129cf5..1b9fdbe 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -13,13 +13,13 @@ public class AutoMapperConfig : Profile { public AutoMapperConfig() { - CreateMap() + CreateMap() .ForMember(u => u.Cnpj, opt => opt.Ignore()); - CreateMap() + CreateMap() .ForMember(u => u.CNPJ, opt => opt.Ignore()); - CreateMap() + CreateMap() .ForMember(u => u.UfLotacao, opt => opt.Ignore()); CreateMap() diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 5fa6318..817d1d3 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -46,9 +46,9 @@ IOptions authConfig this.authConfig = authConfig.Value; } - public async Task CadastrarUsuarioDnit(UsuarioDTONovo usuarioDTO) + public async Task CadastrarUsuarioDnit(UsuarioDTO usuarioDTO) { - var usuario = mapper.Map(usuarioDTO); + var usuario = mapper.Map(usuarioDTO); usuario.Senha = EncriptarSenha(usuario.Senha); @@ -64,9 +64,9 @@ private string EncriptarSenha(string senha) return BCryptNet.HashPassword(senha, salt); } - public void CadastrarUsuarioTerceiro(UsuarioDTONovo usuarioDTO) + public void CadastrarUsuarioTerceiro(UsuarioDTO usuarioDTO) { - var usuario = mapper.Map(usuarioDTO); + var usuario = mapper.Map(usuarioDTO); usuario.Senha = EncriptarSenha(usuario.Senha); usuarioRepositorio.CadastrarUsuarioTerceiro(usuario); } @@ -91,7 +91,7 @@ public async Task AutenticarUsuarioAsync(string email, string senha) return usuario; } - public bool ValidaLogin(UsuarioDTONovo usuarioDTO) + public bool ValidaLogin(UsuarioDTO usuarioDTO) { Usuario? usuarioBanco = Obter(usuarioDTO.Email); @@ -122,9 +122,9 @@ public async Task TrocaSenha(RedefinicaoSenhaDTO redefinicaoSenhaDTO) await dbContext.SaveChangesAsync(); } - public async Task RecuperarSenha(UsuarioDTONovo usuarioDTO) + public async Task RecuperarSenha(UsuarioDTO usuarioDTO) { - var usuarioEntrada = mapper.Map(usuarioDTO); + var usuarioEntrada = mapper.Map(usuarioDTO); Usuario usuarioBanco = Obter(usuarioEntrada.Email); @@ -201,11 +201,11 @@ public async Task> ListarPermissoesAsync(int userId) return usuario!.Perfil?.Permissoes?.ToList() ?? new(); } - public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) + 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); + var modelos = mapper.Map>(usuarios.Items); + return new ListaPaginada(modelos, filtro.Pagina, filtro.ItemsPorPagina, usuarios.Total); } public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) //Implementar método para conseguir editar o PerfilId do usuário diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index 87b65de..d1394d7 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -6,7 +6,7 @@ namespace test.Stub { - public class TesteUsuarioStub : UsuarioDTONovo + public class TesteUsuarioStub : UsuarioDTO { public int Id { get; set; } public string SenhaHash { get; set; } @@ -27,9 +27,9 @@ public static IEnumerable Listar() } } - public UsuarioDTONovo RetornarUsuarioDnitDTO() + public UsuarioDTO RetornarUsuarioDnitDTO() { - return new UsuarioDTONovo + return new UsuarioDTO { Email = "usuarioteste@gmail.com", Senha = "senha1234", @@ -37,9 +37,9 @@ public UsuarioDTONovo RetornarUsuarioDnitDTO() }; } - public UsuarioDTONovo RetornarUsuarioTerceiroDTO() + public UsuarioDTO RetornarUsuarioTerceiroDTO() { - return new UsuarioDTONovo + return new UsuarioDTO { Email = "usuarioteste@gmail.com", Senha = "senha1234", @@ -47,9 +47,9 @@ public UsuarioDTONovo RetornarUsuarioTerceiroDTO() }; } - public UsuarioDnitNovo RetornarUsuarioDnit() + public UsuarioDnit RetornarUsuarioDnit() { - return new UsuarioDnitNovo + return new UsuarioDnit { Email = "usuarioteste@gmail.com", Senha = "senha1234", @@ -69,9 +69,9 @@ public Usuario RetornarUsuarioDnitBanco() }; } - public UsuarioTerceiroNovo RetornarUsuarioTerceiro() + public UsuarioTerceiro RetornarUsuarioTerceiro() { - return new UsuarioTerceiroNovo + return new UsuarioTerceiro { Email = "usuarioteste@gmail.com", Senha = "senha1234", diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 85a65b6..3ba51df 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -34,7 +34,7 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } - public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTONovo usuario) + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTO usuario) { var resultado = await controller.Logar(usuario); @@ -150,7 +150,7 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioJaExistir_DeveRetornarConfli Mock usuarioServiceMock = new(); var excecao = new Npgsql.PostgresException("", "", "", "23505"); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioDnit(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -187,7 +187,7 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioJaExistir_DeveRetornarConflict Mock usuarioServiceMock = new(); var excecao = new Npgsql.PostgresException("", "", "", "23505"); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -206,7 +206,7 @@ public void CadastrarUsuarioTerceiro_QuandoCadastroFalhar_DeveRetornarErroIntern Mock usuarioServiceMock = new(); var excecao = new Exception(""); - usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); + usuarioServiceMock.Setup(service => service.CadastrarUsuarioTerceiro(It.IsAny())).Throws(excecao); var controller = new UsuarioController(usuarioServiceMock.Object, null); @@ -241,7 +241,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFoundAsy var usuarioDTO = usuarioStub.RetornarUsuarioDnitDTO(); Mock usuarioServiceMock = new(); - usuarioServiceMock.Setup(service => service.RecuperarSenha(It.IsAny())).Throws(new KeyNotFoundException()); + usuarioServiceMock.Setup(service => service.RecuperarSenha(It.IsAny())).Throws(new KeyNotFoundException()); var controller = new UsuarioController(usuarioServiceMock.Object, null); diff --git a/test/UsuarioServiceTest.cs b/test/UsuarioServiceTest.cs index 9ccd2cc..1eb5099 100644 --- a/test/UsuarioServiceTest.cs +++ b/test/UsuarioServiceTest.cs @@ -55,11 +55,11 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitForPassado_DeveCadastrar var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); string senhaAntesDaEncriptografia = usuarioDNIT.Senha; - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); - usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); + usuarioRepositorio.Verify(x => x.CadastrarUsuarioDnit(It.IsAny()), Times.Once); Assert.NotEqual(senhaAntesDaEncriptografia, usuarioDNIT.Senha); } @@ -72,11 +72,11 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroForPassado_DeveCadastr var senhaAntesDaEncriptografia = usuarioTerceiro.Senha; - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); - usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); + usuarioRepositorio.Verify(x => x.CadastrarUsuarioTerceiro(It.IsAny()), Times.Once); Assert.NotEqual(senhaAntesDaEncriptografia, usuarioTerceiro.Senha); } @@ -87,8 +87,8 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioDnitJaExistenteForPassado_De var usuarioStub = new UsuarioStub(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioDnit(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); var cadastrarUsuario = async () => await usuarioServiceMock.CadastrarUsuarioDnit(usuarioStub.RetornarUsuarioDnitDTO()); @@ -101,8 +101,8 @@ public void CadastrarUsuarioTerceiro_QuandoUsuarioTerceiroJaExistenteForPassado_ var usuarioStub = new UsuarioStub(); var usuarioTerceiro = usuarioStub.RetornarUsuarioTerceiro(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); - usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); + mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioTerceiro); + usuarioRepositorio.Setup(x => x.CadastrarUsuarioTerceiro(It.IsAny())).Throws(new InvalidOperationException("Email já cadastrado.")); var cadastrarUsuario = () => usuarioServiceMock.CadastrarUsuarioTerceiro(usuarioStub.RetornarUsuarioTerceiroDTO()); @@ -159,7 +159,7 @@ public async void RecuperarSenha_QuandoUsuarioExistir_DeveEnviarEmailDeRecuperac var usuarioRetorno = usuarioStub.RetornarUsuarioDnitBanco(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + 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(usuarioRetorno); @@ -176,7 +176,7 @@ public async Task RecuperarSenha_QuandoUsuarioNaoExistir_DeveLancarException() var usuarioDnitDTO = usuarioStub.RetornarUsuarioDnitDTO(); var usuarioDNIT = usuarioStub.RetornarUsuarioDnit(); - mapper.Setup(x => x.Map(It.IsAny())).Returns(usuarioDNIT); + 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); @@ -194,7 +194,7 @@ public void TrocaSenha_QuandoUuidForValido_DeveTrocarSenha() 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(usuarioDNIT); mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); @@ -217,7 +217,7 @@ public async Task TrocaSenha_QuandoUuidNaoForValido_DeveLancarException() 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(usuarioDNIT); mapper.Setup(x => x.Map(It.IsAny())).Returns(redefinicaoSenha); usuarioRepositorio.Setup(x => x.InserirDadosRecuperacao(It.IsAny(), It.IsAny())); From d46cea80ef3436d59a8e04002a65e5c595921c5e Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 14:19:56 -0300 Subject: [PATCH 126/137] =?UTF-8?q?feat:=20adiciona=20filtro=20por=20munic?= =?UTF-8?q?=C3=ADpio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Municipios/MunicipioModel.cs | 8 + api/Usuarios/PesquisaUsuarioFiltro.cs | 1 + api/Usuarios/UsuarioModel.cs | 4 +- app/Entidades/AppDbContext.cs | 55 +- app/Entidades/Municipio.cs | 15 + app/Entidades/Usuario.cs | 6 +- ...cipioERelacionamentoComUsuario.Designer.cs | 267 + ...cionaMunicipioERelacionamentoComUsuario.cs | 66 + app/Migrations/AppDbContextModelSnapshot.cs | 32 + app/Migrations/Data/municipios.csv | 5570 +++++++++++++++++ app/Program.cs | 1 + app/Repositorios/UsuarioRepositorio.cs | 5 +- app/Services/Mapper.cs | 3 + app/Services/UsuarioService.cs | 6 +- test/UsuarioControllerTest.cs | 25 + 15 files changed, 6052 insertions(+), 12 deletions(-) create mode 100644 api/Municipios/MunicipioModel.cs create mode 100644 app/Entidades/Municipio.cs create mode 100644 app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.Designer.cs create mode 100644 app/Migrations/20231028163722_AdicionaMunicipioERelacionamentoComUsuario.cs create mode 100644 app/Migrations/Data/municipios.csv 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/Usuarios/PesquisaUsuarioFiltro.cs b/api/Usuarios/PesquisaUsuarioFiltro.cs index 5bd870d..2bbdc3d 100644 --- a/api/Usuarios/PesquisaUsuarioFiltro.cs +++ b/api/Usuarios/PesquisaUsuarioFiltro.cs @@ -7,5 +7,6 @@ public class PesquisaUsuarioFiltro 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/UsuarioModel.cs b/api/Usuarios/UsuarioModel.cs index f6ae6a7..f209450 100644 --- a/api/Usuarios/UsuarioModel.cs +++ b/api/Usuarios/UsuarioModel.cs @@ -1,4 +1,5 @@ -using api.Perfis; +using api.Municipios; +using api.Perfis; namespace api.Usuarios { @@ -11,5 +12,6 @@ public class UsuarioModel public Guid? PerfilId { get; set; } public PerfilModel? Perfil { get; set; } public UF UfLotacao { get; set; } + public MunicipioModel? Municipio { get; set; } } } diff --git a/app/Entidades/AppDbContext.cs b/app/Entidades/AppDbContext.cs index acfc392..22cce9d 100644 --- a/app/Entidades/AppDbContext.cs +++ b/app/Entidades/AppDbContext.cs @@ -1,10 +1,12 @@ +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; } @@ -12,7 +14,7 @@ public class AppDbContext : DbContext public DbSet Perfis { get; set; } public DbSet PerfilPermissoes { get; set; } - public AppDbContext (DbContextOptions options) : base (options) + public AppDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -25,7 +27,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasIndex(u => u.Email) .IsUnique(); - + modelBuilder.Entity() .Property(r => r.Id).ValueGeneratedOnAdd(); @@ -41,7 +43,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasMany(e => e.Usuarios) .WithMany(u => u.Empresas) - .UsingEntity(em => + .UsingEntity(em => { em.Property("UsuariosId").HasColumnName("IdUsuario"); em.Property("EmpresasCnpj").HasColumnName("CnpjEmpresa"); @@ -62,5 +64,50 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .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/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/Usuario.cs b/app/Entidades/Usuario.cs index 5fbb022..d5081b5 100644 --- a/app/Entidades/Usuario.cs +++ b/app/Entidades/Usuario.cs @@ -18,12 +18,12 @@ public class Usuario [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; } @@ -31,5 +31,7 @@ public class Usuario 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/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 index 6181e9a..2ee0404 100644 --- a/app/Migrations/AppDbContextModelSnapshot.cs +++ b/app/Migrations/AppDbContextModelSnapshot.cs @@ -59,6 +59,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -136,6 +157,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(50) .HasColumnType("character varying(50)"); + b.Property("MunicipioId") + .HasColumnType("integer"); + b.Property("Nome") .IsRequired() .HasMaxLength(150) @@ -163,6 +187,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Email") .IsUnique(); + b.HasIndex("MunicipioId"); + b.HasIndex("PerfilId"); b.ToTable("Usuario"); @@ -207,11 +233,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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"); }); 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 3c869a2..aa83f2d 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -68,6 +68,7 @@ .GetRequiredService(); dbContext.Database.Migrate(); + dbContext.Popula(); } app.Run(); diff --git a/app/Repositorios/UsuarioRepositorio.cs b/app/Repositorios/UsuarioRepositorio.cs index 89f2750..4d1f7e7 100644 --- a/app/Repositorios/UsuarioRepositorio.cs +++ b/app/Repositorios/UsuarioRepositorio.cs @@ -126,6 +126,7 @@ public void InserirDadosRecuperacao(string uuid, int idUsuario) public async Task> ObterUsuariosAsync(PesquisaUsuarioFiltro filtro) { var query = dbContext.Usuario + .Include(u => u.Municipio) .Include(u => u.Perfil) .AsQueryable(); @@ -138,8 +139,8 @@ public async Task> ObterUsuariosAsync(PesquisaUsuarioFilt if (filtro.UfLotacao != null) query = query.Where(u => u.UfLotacao == filtro.UfLotacao); - //if (filtro.Municipio != null) - // query = query.Where(u => usuario.Municipio == filtro.Municipio); talvez surja um problema em relação a conversão do tipo Guid para uma String + if (filtro.MunicipioId != null) + query = query.Where(u => u.MunicipioId == filtro.MunicipioId); var total = await query.CountAsync(); var items = await query diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 1b9fdbe..27e210c 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -6,6 +6,7 @@ using EnumsNET; using api.Perfis; using api.Permissoes; +using api.Municipios; namespace app.Services.Mapper { @@ -13,6 +14,8 @@ public class AutoMapperConfig : Profile { public AutoMapperConfig() { + CreateMap(); + CreateMap() .ForMember(u => u.Cnpj, opt => opt.Ignore()); diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 817d1d3..5c1808f 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -208,12 +208,12 @@ public async Task> ObterUsuariosAsync(PesquisaUsuari return new ListaPaginada(modelos, filtro.Pagina, filtro.ItemsPorPagina, usuarios.Total); } - public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) //Implementar método para conseguir editar o PerfilId do usuário + public async Task EditarUsuarioPerfil(int usuarioId, string novoPerfilId) { - var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId) + var usuario = await usuarioRepositorio.ObterUsuarioAsync(usuarioId) ?? throw new ApiException(ErrorCodes.UsuarioNaoEncontrado); - var permissao = await perfilRepositorio.ObterPerfilPorIdAsync(Guid.Parse(novoPerfilId)) + var permissao = await perfilRepositorio.ObterPerfilPorIdAsync(Guid.Parse(novoPerfilId)) ?? throw new ApiException(ErrorCodes.PermissaoNaoEncontrada); usuario.PerfilId = permissao.Id; diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 3ba51df..c4fa9d3 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -329,6 +329,31 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorUf_RetornaUsuariosDaUfDada 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_QuandoFiltradoPorPerfil_RetornaUsuariosComPerfilDado() { From 657a778b0b5869c8a338419025af6878d6a53019 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sat, 28 Oct 2023 16:26:33 -0300 Subject: [PATCH 127/137] =?UTF-8?q?refactor:=20muda=20nome=20da=20permiss?= =?UTF-8?q?=C3=A3o=20para=20UsuarioPerfilEditar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 2 ++ app/Controllers/UsuarioController.cs | 2 +- app/appsettings.Development.json | 2 +- test/UsuarioControllerTest.cs | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 583c477..ac7c29d 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -109,6 +109,8 @@ public enum Permissao UsuarioRemover = 8002, [Description("Visualizar Usuário")] UsuarioVisualizar = 8003, + [Description("Editar Perfil Usuário")] + UsuarioPerfilEditar = 8004, } public enum ErrorCodes diff --git a/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index 3860c51..c2c4bd6 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -136,7 +136,7 @@ public async Task> ListarAsync([FromQuery] PesquisaU [HttpPatch("{id}/perfil")] public async Task EditarPerfilUsuario([FromRoute] int id, [FromBody] EditarPerfilUsuarioDTO dto) { - authService.Require(Usuario, Permissao.PerfilEditar); + authService.Require(Usuario, Permissao.UsuarioPerfilEditar); await usuarioService.EditarUsuarioPerfil(id, dto.NovoPerfilId); } } diff --git a/app/appsettings.Development.json b/app/appsettings.Development.json index f181ade..4edc16d 100644 --- a/app/appsettings.Development.json +++ b/app/appsettings.Development.json @@ -13,7 +13,7 @@ "RedefinirSenhaUrl": "http://localhost:3000/redefinirSenha" }, "Auth": { - "Enabled": false, + "Enabled": true, "Key": "chave secreta chave secreta chave secreta chave secreta chave secreta chave secreta", "Issuer": "https://localhost:7083/", "Audience": "https://localhost:7083/", diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index c4fa9d3..6006ad9 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -387,7 +387,7 @@ public async Task EditarPerfilUsuario_QuandoNaoTemPermissao_ErroDePermissao() [Fact] public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrado() { - AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioPerfilEditar }); var dto = new EditarPerfilUsuarioDTO { NovoPerfilId = "id" }; var ex = await Assert.ThrowsAsync(async () => await controller.EditarPerfilUsuario(-1, dto)); @@ -398,7 +398,7 @@ public async Task EditarPerfilUsuario_QuandoUsuarioNaoExiste_RetornaNaoEncontrad [Fact] public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEncontrado() { - AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + 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 () @@ -410,7 +410,7 @@ public async Task EditarPerfilUsuario_QuandoPerfilNaoExiste_RetornaPerfilNaoEnco [Fact] public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarPerfilDoUsuario() { - AutenticarUsuario(controller, permissoes: new() { Permissao.PerfilEditar }); + AutenticarUsuario(controller, permissoes: new() { Permissao.UsuarioPerfilEditar }); var novoPerfilParaUsuario = new Perfil { Id = Guid.NewGuid(), From e6c6712fc95dff31047795a73840414ce19908fc Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 02:13:47 -0300 Subject: [PATCH 128/137] =?UTF-8?q?refactor:=20move=20testes=20de=20editar?= =?UTF-8?q?=20perfil=20usu=C3=A1rio=20para=20nova=20classe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/EditarUsuarioPerfilTest.cs | 189 ++++++++++++++++++++++++++++++++ test/UsuarioControllerTest.cs | 167 +--------------------------- 2 files changed, 195 insertions(+), 161 deletions(-) create mode 100644 test/EditarUsuarioPerfilTest.cs diff --git a/test/EditarUsuarioPerfilTest.cs b/test/EditarUsuarioPerfilTest.cs new file mode 100644 index 0000000..0bc8f19 --- /dev/null +++ b/test/EditarUsuarioPerfilTest.cs @@ -0,0 +1,189 @@ +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_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); + } + } +} diff --git a/test/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 6006ad9..2acc36c 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -15,12 +15,11 @@ using app.Controllers; using api.Usuarios; using api.Senhas; -using api; -using app.Services; +using Xunit.Microsoft.DependencyInjection.Abstracts; namespace test { - public class UsuarioControllerTest : AuthTest, IDisposable + public class UsuarioControllerTest : TestBed, IDisposable { const int CREATED = 201; const int INTERNAL_SERVER_ERROR = 500; @@ -34,7 +33,7 @@ public UsuarioControllerTest(ITestOutputHelper testOutputHelper, Base fixture) : dbContext.PopulaUsuarios(5); } - public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuarioLocal(UsuarioDTO usuario) + public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioDTO usuario) { var resultado = await controller.Logar(usuario); @@ -70,7 +69,7 @@ public async Task ListarPermissoes_QuandoTiverLogado_DeveRetornarPermissoes() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - await AutenticarUsuarioLocal(usuario); + await AutenticarUsuario(usuario); var permissoes = await controller.ListarPermissoes(); Assert.NotEmpty(permissoes); } @@ -80,7 +79,7 @@ public async Task AtualizarToken_QuandoTiverValido_DeveRetornarNovoToken() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var login = await AutenticarUsuarioLocal(usuario); + var login = await AutenticarUsuario(usuario); var novoLogin = await controller.AtualizarToken(new AtualizarTokenDto { Token = login.Token, @@ -97,7 +96,7 @@ public async Task AtualizarToken_QuandoTiverInvalido_DeveRetornarNovoToken() { var usuario = dbContext.PopulaUsuarios(1, includePerfil: true).First(); - var login = await AutenticarUsuarioLocal(usuario); + var login = await AutenticarUsuario(usuario); var atualizarTokenDto = new AtualizarTokenDto { Token = login.Token, @@ -283,159 +282,5 @@ public async void RedefinirSenha_QuandoUsuarioNaoExistir_DeveRetornarNotFound() usuarioServiceMock.Verify(service => service.TrocaSenha(redefinicaoSenhaDTO), Times.Once); Assert.IsType(resultado); } - - [Fact] - public async void ObterUsuariosAsync_QuandoNãoTemPermissaoVisualizar_RetornaErroDePermissao() - { - 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_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.Empresa); - } } } From 4c79381f02918b13eb23af3efa81bf2e06183c02 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 03:38:55 -0300 Subject: [PATCH 129/137] =?UTF-8?q?fix:=20n=C3=A3o=20aceita=20cria=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20usu=C3=A1rio=20DNIT=20com=20UF=20inv=C3=A1lido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 2 ++ api/Usuarios/UsuarioDTO.cs | 1 + app/Controllers/UsuarioController.cs | 4 ++++ app/Services/Mapper.cs | 3 +-- app/Services/UsuarioService.cs | 3 +++ test/Stub/UsuarioStub.cs | 23 ++++++++++++++++++----- test/UsuarioControllerTest.cs | 18 ++++++++++++++++++ 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index ac7c29d..8ccd793 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -120,6 +120,8 @@ public enum ErrorCodes NaoPermitido, [Description("Usuário não encontrado")] UsuarioNaoEncontrado, + [Description("Código UF inválido")] + CodigoUfInvalido, [Description("Permissao não encontrada")] PermissaoNaoEncontrada, } diff --git a/api/Usuarios/UsuarioDTO.cs b/api/Usuarios/UsuarioDTO.cs index 8f44733..f0ca2a2 100644 --- a/api/Usuarios/UsuarioDTO.cs +++ b/api/Usuarios/UsuarioDTO.cs @@ -5,5 +5,6 @@ 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/app/Controllers/UsuarioController.cs b/app/Controllers/UsuarioController.cs index c2c4bd6..e9593b0 100644 --- a/app/Controllers/UsuarioController.cs +++ b/app/Controllers/UsuarioController.cs @@ -70,6 +70,10 @@ public async Task CadastrarUsuarioDnit([FromBody] UsuarioDTO usua { return Conflict("Usuário já cadastrado."); } + catch (ApiException ex) + { + return StatusCode(400, ex.Message); + } catch (Exception) { return StatusCode(500, "Houve um erro interno no servidor."); diff --git a/app/Services/Mapper.cs b/app/Services/Mapper.cs index 27e210c..fade123 100644 --- a/app/Services/Mapper.cs +++ b/app/Services/Mapper.cs @@ -22,8 +22,7 @@ public AutoMapperConfig() CreateMap() .ForMember(u => u.CNPJ, opt => opt.Ignore()); - CreateMap() - .ForMember(u => u.UfLotacao, opt => opt.Ignore()); + CreateMap(); CreateMap() .ForMember(model => model.Id, opt => opt.MapFrom(uf => (int)uf)) diff --git a/app/Services/UsuarioService.cs b/app/Services/UsuarioService.cs index 5c1808f..ce07ed2 100644 --- a/app/Services/UsuarioService.cs +++ b/app/Services/UsuarioService.cs @@ -48,6 +48,9 @@ IOptions authConfig 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); diff --git a/test/Stub/UsuarioStub.cs b/test/Stub/UsuarioStub.cs index d1394d7..fff76c2 100644 --- a/test/Stub/UsuarioStub.cs +++ b/test/Stub/UsuarioStub.cs @@ -14,6 +14,13 @@ public class TesteUsuarioStub : UsuarioDTO 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) @@ -23,6 +30,7 @@ public static IEnumerable Listar() Nome = "teste " + Random.Shared.Next().ToString(), Email = $"teste{Random.Shared.Next()}@email.com", Senha = $"teste_senha_{Random.Shared.Next()}", + UfLotacao = UfAleatoria(), }; } } @@ -34,6 +42,7 @@ public UsuarioDTO RetornarUsuarioDnitDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", + UfLotacao = UfAleatoria(), }; } @@ -44,6 +53,7 @@ public UsuarioDTO RetornarUsuarioTerceiroDTO() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", + UfLotacao = UfAleatoria() }; } @@ -54,7 +64,7 @@ public UsuarioDnit RetornarUsuarioDnit() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - UfLotacao = UF.DF + UfLotacao = UfAleatoria() }; } @@ -65,7 +75,7 @@ public Usuario RetornarUsuarioDnitBanco() Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", Nome = "Usuario Dnit", - UfLotacao = UF.DF + UfLotacao = UfAleatoria() }; } @@ -76,7 +86,8 @@ public UsuarioTerceiro RetornarUsuarioTerceiro() Email = "usuarioteste@gmail.com", Senha = "senha1234", Nome = "Usuario Dnit", - CNPJ = "12345678901234" + CNPJ = "12345678901234", + UfLotacao = UfAleatoria() }; } @@ -86,7 +97,8 @@ public Usuario RetornarUsuarioValidoLogin() { Email = "usuarioteste@gmail.com", Senha = "$2a$11$p0Q3r8Q7pBBcfoW.EIdvvuosHDfgr6TBBOxQvpnG18fLLlHjC/J6O", - Nome = "Usuario Dnit" + Nome = "Usuario Dnit", + UfLotacao = UfAleatoria() }; } @@ -96,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/UsuarioControllerTest.cs b/test/UsuarioControllerTest.cs index 2acc36c..a63eac6 100644 --- a/test/UsuarioControllerTest.cs +++ b/test/UsuarioControllerTest.cs @@ -22,6 +22,7 @@ namespace test 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; @@ -138,6 +139,23 @@ public async Task CadastrarUsuarioDnit_QuandoUsuarioForCadastrado_DeveRetornarCr var objeto = Assert.IsType(resultado); Assert.Equal(CREATED, objeto.StatusCode); + + 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 = await controller.CadastrarUsuarioDnit(usuarioDTO); + + var objeto = Assert.IsType(resultado); + Assert.Equal(BAD_REQUEST, objeto.StatusCode); + Assert.Equal("Código UF inválido", objeto!.Value); } [Fact] From d1f0c5aa3aeb0a64c6a8e017578468ac504e6345 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 16:08:00 -0300 Subject: [PATCH 130/137] =?UTF-8?q?fix:=20usu=C3=A1rios=20com=20perfil=20e?= =?UTF-8?q?xclu=C3=ADdo=20s=C3=A3o=20definidos=20com=20perfil=20b=C3=A1sic?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/PerfilService.cs | 38 ++++++++++++++++--------- test/EditarUsuarioPerfilTest.cs | 1 + test/PerfilServiceTest.cs | 49 +++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index b047878..e3c6b8a 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -1,5 +1,4 @@ using api; -using api.Perfis; using app.Entidades; using app.Repositorios.Interfaces; using app.Services.Interfaces; @@ -10,6 +9,7 @@ namespace app.Services public class PerfilService : IPerfilService { private readonly IPerfilRepositorio perfilRepositorio; + private readonly IUsuarioRepositorio usuarioRepositorio; private readonly AppDbContext dbContext; private readonly IMapper mapper; @@ -24,7 +24,7 @@ public Perfil CriarPerfil(Perfil perfil, List permissoes) { var novoPerfil = perfilRepositorio.RegistraPerfil(perfil); - foreach(var permissao in permissoes) + foreach (var permissao in permissoes) { perfilRepositorio.AdicionaPermissaoAoPerfil(novoPerfil.Id, permissao); } @@ -35,8 +35,8 @@ public Perfil CriarPerfil(Perfil perfil, List permissoes) } public async Task EditarPerfil(Perfil perfil, List permissoes) - { - var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? + { + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfil.Id) ?? throw new KeyNotFoundException("Perfil não encontrado"); perfilDb.Nome = perfil.Nome; @@ -50,36 +50,48 @@ public async Task EditarPerfil(Perfil perfil, List permissoes perfilDb.PerfilPermissoes!.Remove(permissao); } - foreach(var permissao in permissoesNovas) + foreach (var permissao in permissoesNovas) { perfilRepositorio.AdicionaPermissaoAoPerfil(perfil.Id, permissao); } await dbContext.SaveChangesAsync(); - return perfilDb; + return perfilDb; } public async Task ExcluirPerfil(Guid id) - { - var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(id) ?? + { + var perfilParaExcluir = await perfilRepositorio.ObterPerfilPorIdAsync(id) ?? throw new KeyNotFoundException("Perfil não encontrado"); - if (perfilDb.Tipo == TipoPerfil.Basico || perfilDb.Tipo == TipoPerfil.Administrador) + if (perfilParaExcluir.Tipo == TipoPerfil.Basico || perfilParaExcluir.Tipo == TipoPerfil.Administrador) { throw new InvalidOperationException("Esse Perfil não pode ser excluído."); } - foreach(var perfilPermissao in perfilDb.PerfilPermissoes!) + foreach (var perfilPermissao in perfilParaExcluir.PerfilPermissoes!) { perfilRepositorio.RemovePermissaoDoPerfil(perfilPermissao); } - perfilRepositorio.RemovePerfil(perfilDb); + DefinirPerfilBasicoParaUsuariosComPerfilParaExcluir(perfilParaExcluir.Id); + perfilRepositorio.RemovePerfil(perfilParaExcluir); dbContext.SaveChanges(); } + 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 async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) { var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize, nome); @@ -95,8 +107,8 @@ public async Task> ListarPerfisAsync(int pageIndex, int pageSize, s public async Task ObterPorIdAsync(Guid id) { var perfil = await perfilRepositorio.ObterPerfilPorIdAsync(id); - - if(perfil != null && perfil.Tipo == TipoPerfil.Administrador) + + if (perfil != null && perfil.Tipo == TipoPerfil.Administrador) { PreencherPermissoesAdministrador(perfil); } diff --git a/test/EditarUsuarioPerfilTest.cs b/test/EditarUsuarioPerfilTest.cs index 0bc8f19..88d5df2 100644 --- a/test/EditarUsuarioPerfilTest.cs +++ b/test/EditarUsuarioPerfilTest.cs @@ -184,6 +184,7 @@ public async Task EditarPerfilUsuario_QuandoTemPermissao_DeveAlterarPerfilDoUsua dbContext.RemoveRange(dbContext.PerfilPermissoes); dbContext.RemoveRange(dbContext.Perfis); dbContext.RemoveRange(dbContext.Usuario); + dbContext.RemoveRange(dbContext.Municipio); } } } diff --git a/test/PerfilServiceTest.cs b/test/PerfilServiceTest.cs index a99ecf4..9894e99 100644 --- a/test/PerfilServiceTest.cs +++ b/test/PerfilServiceTest.cs @@ -45,7 +45,7 @@ public async Task CriarPerfil_QuandoPerfilPassado_DeveRetornarPerfilRegistrado() Assert.Equal(1, perfilDb.Permissoes.Count()); } - [Fact] + [Fact] public async Task EditarPerfil_QuandoNomeJaCadastrado_DeveRetornarPerfilAtualizado() { var perfilDTO = PerfilStub.RetornaPerfilDTO(); @@ -59,7 +59,7 @@ public async Task EditarPerfil_QuandoNomeJaCadastrado_DeveRetornarPerfilAtualiza perfilEdicao.Id = perfilRetorno.Id; var perfilRetornoEditado = await perfilService.EditarPerfil(perfilEdicao, perfilDTOEdicao.Permissoes); - + var perfilDb = await perfilRepositorio.ObterPerfilPorIdAsync(perfilRetorno.Id); Assert.NotNull(perfilRetorno); @@ -68,7 +68,7 @@ public async Task EditarPerfil_QuandoNomeJaCadastrado_DeveRetornarPerfilAtualiza Assert.Equal(perfilRetorno.Id, perfilDb.Id); Assert.Equal(perfilRetornoEditado, perfilDb); Assert.NotEqual(perfilDb.Nome, perfilDTO.Nome); - Assert.NotEqual(perfilDb.Permissoes.Count(), perfilDTO.Permissoes.Count()); + Assert.NotEqual(perfilDb.Permissoes.Count(), perfilDTO.Permissoes.Count()); } [Fact] @@ -77,7 +77,7 @@ public async Task EditarPerfil_QuandoPerfilNaoExiste_DeveLancarKeyNotFoundExcept var perfilDTO = PerfilStub.RetornaPerfilDTO(); var perfil = mapper.Map(perfilDTO); - await Assert.ThrowsAsync( async () => await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes)); + await Assert.ThrowsAsync(async () => await perfilService.EditarPerfil(perfil, perfilDTO.Permissoes)); } [Fact] @@ -95,10 +95,46 @@ public async Task ExcluirPerfil_QuandoPerfilExiste_DeveExcluirPerfilDoDB() 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())); + await Assert.ThrowsAsync(async () => await perfilService.ExcluirPerfil(Guid.NewGuid())); } [Fact] @@ -116,7 +152,7 @@ public async Task ListarPerfis_QuandoExistir_DeveRetornarListaDePerfis() lista.ForEach(p => perfilService.CriarPerfil(p, p.Permissoes.ToList())); - var listaRetorno = await perfilService.ListarPerfisAsync(1,5); + var listaRetorno = await perfilService.ListarPerfisAsync(1, 5); Assert.Equal(5, listaRetorno.Count()); } @@ -125,6 +161,7 @@ public void Dispose() { dbContext.RemoveRange(dbContext.PerfilPermissoes); dbContext.RemoveRange(dbContext.Perfis); + dbContext.RemoveRange(dbContext.Usuario); dbContext.SaveChanges(); } } From ea106df8de70892c4d1b45b7964ff5d93d233641 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 16:33:54 -0300 Subject: [PATCH 131/137] =?UTF-8?q?refactor:=20move=20l=C3=B3gica=20de=20a?= =?UTF-8?q?tribui=C3=A7=C3=A3o=20de=20perfil=20b=C3=A1sico=20para=20reposi?= =?UTF-8?q?t=C3=B3rio=20de=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/PerfilController.cs | 4 ---- app/Repositorios/PerfilRepositorio.cs | 22 +++++++++++++++------- app/Services/PerfilService.cs | 13 ------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/Controllers/PerfilController.cs b/app/Controllers/PerfilController.cs index 998f399..cbc44e5 100644 --- a/app/Controllers/PerfilController.cs +++ b/app/Controllers/PerfilController.cs @@ -85,10 +85,6 @@ public async Task EditarPerfil(Guid id, [FromBody] PerfilDTO perf [HttpDelete("{id}")] public async Task ExcluirPerfil(Guid id) { - // !TODO Quando um perfil com usuários atribuídos for excluído, - // deve-se migrar todos os usuários do perfil excluído para - // o perfil básico antes de efetuar a exclusão do perfil. US02! - authService.Require(Usuario, Permissao.PerfilRemover); try{ diff --git a/app/Repositorios/PerfilRepositorio.cs b/app/Repositorios/PerfilRepositorio.cs index c0cabae..60f79df 100644 --- a/app/Repositorios/PerfilRepositorio.cs +++ b/app/Repositorios/PerfilRepositorio.cs @@ -19,19 +19,19 @@ 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 + var novoPerfilPermissao = new PerfilPermissao { Id = Guid.NewGuid(), PerfilId = perfilId, Permissao = permissao }; - + dbContext.Add(novoPerfilPermissao); return novoPerfilPermissao; @@ -39,13 +39,21 @@ public PerfilPermissao AdicionaPermissaoAoPerfil(Guid perfilId, Permissao permis public void RemovePerfil(Perfil perfil) { - // TODO!! Quando um perfil com usuários atribuídos for excluído, - // deve-se migrar todos os usuários do perfil excluído para - // o perfil básico antes de efetuar a exclusão do perfil. US02! - + 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); diff --git a/app/Services/PerfilService.cs b/app/Services/PerfilService.cs index e3c6b8a..4d056fe 100644 --- a/app/Services/PerfilService.cs +++ b/app/Services/PerfilService.cs @@ -75,23 +75,10 @@ public async Task ExcluirPerfil(Guid id) perfilRepositorio.RemovePermissaoDoPerfil(perfilPermissao); } - DefinirPerfilBasicoParaUsuariosComPerfilParaExcluir(perfilParaExcluir.Id); - perfilRepositorio.RemovePerfil(perfilParaExcluir); dbContext.SaveChanges(); } - 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 async Task> ListarPerfisAsync(int pageIndex, int pageSize, string? nome = null) { var perfis = await perfilRepositorio.ListarPerfisAsync(pageIndex, pageSize, nome); From 569d5e3357a2c04ff956ad1f33ec46ddc4a68944 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 17:15:23 -0300 Subject: [PATCH 132/137] =?UTF-8?q?test:=20testa=20filtro=20por=20nome=20p?= =?UTF-8?q?ara=20lista=20de=20usu=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/EditarUsuarioPerfilTest.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/EditarUsuarioPerfilTest.cs b/test/EditarUsuarioPerfilTest.cs index 88d5df2..cceda41 100644 --- a/test/EditarUsuarioPerfilTest.cs +++ b/test/EditarUsuarioPerfilTest.cs @@ -103,6 +103,29 @@ public async void ObterUsuariosAsync_QuandoFiltradoPorMunicipio_RetornaUsuariosD 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() { From 2a0da8b2e2cfbbd9b9c8077037b9f6e89b2c197d Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 17:28:29 -0300 Subject: [PATCH 133/137] =?UTF-8?q?refactor:=20remove=20permiss=C3=B5es=20?= =?UTF-8?q?n=C3=A3o=20utilizadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 28 ---------------------------- app/app.csproj | 2 +- test/AuthTest.cs | 5 ----- test/Fixtures/AuthTest.cs | 14 -------------- 4 files changed, 1 insertion(+), 48 deletions(-) diff --git a/api/Enums.cs b/api/Enums.cs index 8ccd793..8bd43fe 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -66,19 +66,6 @@ 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, @@ -89,24 +76,9 @@ public enum Permissao [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, - - UsuarioCadastrar = 8000, - [Description("Editar Usuário")] - UsuarioEditar = 8001, - [Description("Remover Usuário")] - UsuarioRemover = 8002, [Description("Visualizar Usuário")] UsuarioVisualizar = 8003, [Description("Editar Perfil Usuário")] diff --git a/app/app.csproj b/app/app.csproj index 166e957..22c40e6 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -38,7 +38,7 @@ - Migrations\**, DI\**, Program.cs + Migrations\**, DI\**, Program.cs, Entidades/AppDbContext.cs diff --git a/test/AuthTest.cs b/test/AuthTest.cs index b2471b7..89cc3d0 100644 --- a/test/AuthTest.cs +++ b/test/AuthTest.cs @@ -2,15 +2,10 @@ using app.Services; using auth; using Microsoft.Extensions.Options; -using Moq; -using System.Buffers.Text; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; -using test.Fixtures; -using Xunit.Abstractions; -using Xunit.Microsoft.DependencyInjection.Abstracts; namespace test { diff --git a/test/Fixtures/AuthTest.cs b/test/Fixtures/AuthTest.cs index eed796c..f117090 100644 --- a/test/Fixtures/AuthTest.cs +++ b/test/Fixtures/AuthTest.cs @@ -48,19 +48,5 @@ public AuthTest(ITestOutputHelper testOutputHelper, Base fixture) : base(testOut controller.AppUsuario = Usuario; return (token, Usuario); } - - // public async Task<(string Token, string TokenAtualizacao)> AutenticarUsuario(UsuarioController controller, UsuarioDTO usuario) - // { - // var resultado = await controller.Logar(usuario); - - // Assert.IsType(resultado); - - // var login = (resultado as OkObjectResult)!.Value as LoginModel; - // var token = login!.Token.Split(" ")[1]; - - // var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); - // controller.AppUsuario = new ClaimsPrincipal(new ClaimsIdentity(jwt.Claims)); - // return (token, login.TokenAtualizacao); - // } } } From 9fa3452137bf63ddc2019d2a5a0d96d947d8a78c Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Sun, 29 Oct 2023 19:15:46 -0300 Subject: [PATCH 134/137] =?UTF-8?q?fix:=20devolve=20permiss=C3=B5es=20excl?= =?UTF-8?q?u=C3=ADdas=20e=20apaga=20permiss=C3=B5es=20de=20usu=C3=A1rio=20?= =?UTF-8?q?n=C3=A3o=20usadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/Enums.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/api/Enums.cs b/api/Enums.cs index 8bd43fe..9eb2084 100644 --- a/api/Enums.cs +++ b/api/Enums.cs @@ -66,6 +66,19 @@ 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, @@ -76,9 +89,19 @@ public enum Permissao [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")] From 93383a2337a418ef837a8bb1cf785108c18968f7 Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:29:59 -0300 Subject: [PATCH 135/137] fix: generaliza servidor smtp --- app/Services/EmailService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Services/EmailService.cs b/app/Services/EmailService.cs index 793888c..ffcbcec 100644 --- a/app/Services/EmailService.cs +++ b/app/Services/EmailService.cs @@ -19,7 +19,8 @@ public void EnviarEmail(string emailDestinatario, string assunto, string corpo) 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), From 73f5a575d063d7664a769c5b34c2a38c0d0138ed Mon Sep 17 00:00:00 2001 From: Thiago Paiva <54081877+thiagohdaqw@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:33:22 -0300 Subject: [PATCH 136/137] fix: inclui dados do municipio --- app/app.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/app.csproj b/app/app.csproj index 22c40e6..349bb09 100644 --- a/app/app.csproj +++ b/app/app.csproj @@ -30,6 +30,10 @@ + + + + From 1c9edc971251f2ae443644f9e98b8f1f24b70541 Mon Sep 17 00:00:00 2001 From: Yudi Yamane Date: Fri, 17 Nov 2023 22:40:20 -0300 Subject: [PATCH 137/137] chore: inicia network DNIT --- docker-compose.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index d7d9db7..1713202 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ version: "3.0" + services: usuario-service: depends_on: @@ -17,6 +18,8 @@ services: - MODE=container env_file: - .env + networks: + - dnit-network dnit-usuario-db: container_name: dnit-usuario-db @@ -29,6 +32,8 @@ services: - "5432:5432" volumes: - pg-data-volume:/var/lib/postgresql/data + networks: + - dnit-network pgadmin: container_name: dnit-pg-admin @@ -40,7 +45,14 @@ services: 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