diff --git a/.github/workflows/azure-functions-app-dotnet.yml b/.github/workflows/azure-functions-app-dotnet.yml new file mode 100644 index 00000000..051ad179 --- /dev/null +++ b/.github/workflows/azure-functions-app-dotnet.yml @@ -0,0 +1,55 @@ +name: Deploy WebFunctions to Azure + +on: + push: + branches: ["main"] + +env: + AZURE_FUNCTIONAPP_NAME: "GPIC-Stg-WebFunctions" # set this to your function app name on Azure + AZURE_FUNCTIONAPP_PACKAGE_PATH: "src" # set this to the path to your function app project, defaults to the repository root + DOTNET_VERSION: "7.0" # set this to the dotnet version to use (e.g. '2.1.x', '3.1.x', '5.0.x') + +jobs: + build-and-deploy: + runs-on: ubuntu-latest # For Linux, use ubuntu-latest + environment: dev + steps: + - name: "Checkout GitHub Action" + uses: actions/checkout@v3 + + - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: "Resolve Project Dependencies Using Dotnet" + shell: bash # For Linux, use bash + run: | + pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' + dotnet build --configuration Release --output ./output + popd + + - name: Create env + run: | + echo "AZURE_BLOB_STORAGE_CONNECTION_STRING=${{ secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING }} + AZURE_BLOB_STORAGE_CONTAINER_NAME=${{ secrets.AZURE_BLOB_STORAGE_CONTAINER_NAME }} + POSTGRES_CONNECTION_STRING=${{ secrets.POSTGRES_CONNECTION_STRING }} + FRONTEND_URL=${{ secrets.FRONTEND_URL }} + ALLOW_ORIGINS=${{ secrets.ALLOW_ORIGINS }} + JWT_AUDIENCE=${{ secrets.JWT_AUDIENCE }} + JWT_EXPIRE_IN=${{ secrets.JWT_EXPIRE_IN }} + JWT_ISSUER=${{ secrets.JWT_ISSUER }} + JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} + SEQ_API_KEY=${{ secrets.SEQ_API_KEY }} + SEQ_URL=${{ secrets.SEQ_URL }} + SMTP_EMAIL_PASSWORD=${{ secrets.SMTP_EMAIL_PASSWORD }} + SMTP_EMAIL_USERNAME=${{ secrets.SMTP_EMAIL_USERNAME }} + EXECUTE_MIGRATION=${{ secrets.EXECUTE_MIGRATION }}" > src/Infrastructure/WebAPI/.env + + - name: "Run Azure Functions Action" + uses: Azure/functions-action@v1 + id: fa + with: + app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} + package: "${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output" + publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} # Remove publish-profile to use Azure RBAC diff --git a/.github/workflows/azure-webapps-dotnet-core.yml b/.github/workflows/azure-webapps-dotnet-core.yml index 08b8208a..286b9e85 100644 --- a/.github/workflows/azure-webapps-dotnet-core.yml +++ b/.github/workflows/azure-webapps-dotnet-core.yml @@ -1,13 +1,18 @@ -name: Build and deploy ASP.Net Core app to an Azure Web App +name: Deploy WebAPI to Azure env: - AZURE_WEBAPP_NAME: GPIC-Staging-WebAPI # set this to the name of your Azure Web App - AZURE_WEBAPP_PACKAGE_PATH: 'src' # set this to the path to your web app project, defaults to the repository root - DOTNET_VERSION: '7.0' # set this to the .NET Core version to use + AZURE_WEBAPP_NAME: ${{ secrets.AZURE_WEBAPP_NAME }} + AZURE_WEBAPP_PACKAGE_PATH: "." + DOTNET_VERSION: "7.0" + PROJECT_PATH: "src/Infrastructure/WebAPI/WebAPI.csproj" + WEBAPI_PATH: "src/Infrastructure/WebAPI/" + UNIT_TESTS_PROJ: "src/Domain.Tests/Domain.Tests.csproj" + INTEGRATION_TESTS_PROJ: "src/Application.Tests/Application.Tests.csproj" + ASPNETCORE_ENVIRONMENT: ${{ secrets.ASPNETCORE_ENVIRONMENT }} on: push: - branches: [ "main" ] + branches: ["staging"] workflow_dispatch: permissions: @@ -16,6 +21,7 @@ permissions: jobs: build: runs-on: ubuntu-latest + environment: STAGING steps: - uses: actions/checkout@v3 @@ -33,11 +39,34 @@ jobs: restore-keys: | ${{ runner.os }}-nuget- + - name: Create env + run: | + echo "AZURE_BLOB_STORAGE_CONNECTION_STRING=${{ secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING }} + AZURE_BLOB_STORAGE_CONTAINER_NAME=${{ secrets.AZURE_BLOB_STORAGE_CONTAINER_NAME }} + POSTGRES_CONNECTION_STRING=${{ secrets.POSTGRES_CONNECTION_STRING }} + FRONTEND_URL=${{ secrets.FRONTEND_URL }} + ALLOW_ORIGINS=${{ secrets.ALLOW_ORIGINS }} + JWT_AUDIENCE=${{ secrets.JWT_AUDIENCE }} + JWT_EXPIRE_IN=${{ secrets.JWT_EXPIRE_IN }} + JWT_ISSUER=${{ secrets.JWT_ISSUER }} + JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} + SEQ_API_KEY=${{ secrets.SEQ_API_KEY }} + SEQ_URL=${{ secrets.SEQ_URL }} + SMTP_EMAIL_PASSWORD=${{ secrets.SMTP_EMAIL_PASSWORD }} + SMTP_EMAIL_USERNAME=${{ secrets.SMTP_EMAIL_USERNAME }} + EXECUTE_MIGRATION=${{ secrets.EXECUTE_MIGRATION }}" > src/Infrastructure/WebAPI/.env + + - name: Set environment variable + run: echo "ASPNETCORE_ENVIRONMENT=${{ env.ASPNETCORE_ENVIRONMENT }}" >> $GITHUB_ENV + - name: Build with dotnet - run: dotnet build --configuration Release + run: dotnet build ${{env.PROJECT_PATH}} --configuration Release -o ${{env.DOTNET_ROOT}}/dist - - name: dotnet publish - run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp + - name: Dotnet publish + run: dotnet publish ${{env.PROJECT_PATH}} -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Copy XML documentation file + run: cp ${{env.DOTNET_ROOT}}/dist/*.xml ${{env.DOTNET_ROOT}}/myapp/ - name: Upload artifact for deployment job uses: actions/upload-artifact@v3 @@ -45,13 +74,33 @@ jobs: name: .net-app path: ${{env.DOTNET_ROOT}}/myapp + test: + needs: build + runs-on: ubuntu-latest + environment: STAGING + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v2 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Run Unit Tests + run: dotnet test ${{ env.UNIT_TESTS_PROJ }} --configuration Release + + - name: Run Integration Tests + run: dotnet test ${{ env.INTEGRATION_TESTS_PROJ }} --configuration Release + deploy: permissions: contents: none runs-on: ubuntu-latest - needs: build + needs: test environment: - name: 'Development' + name: STAGING url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} steps: diff --git a/.gitignore b/.gitignore index b19556e6..28cdfd4f 100644 --- a/.gitignore +++ b/.gitignore @@ -398,7 +398,6 @@ FodyWeavers.xsd *.sln.iml volumes -Migrations node_modules .DS_Store @@ -406,6 +405,14 @@ node_modules certificate certificate/* -RemoteFiles -RemoteFiles/* -src/Infrastructure/WebAPI/.env +storage +storage/* + +temp +temp/* + +files +files/* + +**/.env +thunder-tests diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 45c511c0..bb763007 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,6 @@ { - "recommendations": [] + "recommendations": [ + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp" + ] } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 635e62d1..1b23f5d7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,6 +28,12 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach" + }, + { + "name": "Attach to .NET Functions", + "type": "coreclr", + "request": "attach", + "processId": "${command:azureFunctions.pickProcess}" } ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1501347a..7181dee0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,11 @@ { - "dotnet-test-explorer.testProjectPath": "src/Domain.Tests/Domain.Tests.csproj" -} + // "dotnet-test-explorer.testProjectPath": "src/Domain.Tests/Domain.Tests.csproj", + "dotnet-test-explorer.testProjectPath": "src/Application.Tests/Application.Tests.csproj", + "dotnet.defaultSolution": "src/GPIC.BackEnd.sln", + "azureFunctions.deploySubpath": "src/Infrastructure/WebFunctions/bin/Release/net7.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.projectSubpath": "src/Infrastructure/WebFunctions", + "azureFunctions.preDeployTask": "publish (functions)" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c39e443c..3b7307cf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -36,6 +36,82 @@ "${workspaceFolder}/src/Infrastructure/WebAPI/WebAPI.csproj" ], "problemMatcher": "$msCompile" + }, + { + "label": "clean (functions)", + "command": "dotnet", + "args": [ + "clean", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/Infrastructure/WebFunctions" + } + }, + { + "label": "build (functions)", + "command": "dotnet", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean (functions)", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/Infrastructure/WebFunctions" + } + }, + { + "label": "clean release (functions)", + "command": "dotnet", + "args": [ + "clean", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/Infrastructure/WebFunctions" + } + }, + { + "label": "publish (functions)", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean release (functions)", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/Infrastructure/WebFunctions" + } + }, + { + "type": "func", + "dependsOn": "build (functions)", + "options": { + "cwd": "${workspaceFolder}/src/Infrastructure/WebFunctions/bin/Debug/net7.0" + }, + "command": "host start", + "isBackground": true, + "problemMatcher": "$func-dotnet-watch" } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index c7de7194..0627cd68 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,3 @@ -# Infrastructure.WebAPI +# GPIC WebAPI / WebFunctions -Restful API created in .NET 7.0.0 to support SPA. - -## DataBase - PostgreSQL - -Para levantar o banco de dados é necessário executar o comando abaixo na pasta raiz: - -```bash -cd docker -docker compose up -d -``` - -Em seguida, é preciso acessar o pgAdmin através da rota abaixo: - -- [PGAdmin](http://localhost:16543/browser) - -E criar um servidor utilizando as informações de _host_, _username_, _password_ e _database_ que estão informadas no arquivo docker-compose.yaml utilizado. -Exemplo: - -- **host**: copet-system-db -- **username**: copet-admin -- **password**: Copet@123 -- **database**: COPET_DB -- **port**: 5432 - -## Migrations - -Criando as Migrations iniciais para criação das tabelas do banco de dados: - -```bash -cd src/Infrastructure/WebAPI -dotnet ef migrations add Initialize --project ../Persistence/Persistence.csproj -``` - -Executando as Migrations: - -```bash -dotnet ef database update -``` - -Removendo as Migrations: - -```bash -cd src/Infrastructure/WebAPI -dotnet ef migrations remove --project ../Persistence/Persistence.csproj -``` - -## Execução com Dotnet - -Comandos para execução do projeto .NET utilizando CLI: - -```bash -cd Infrastructure.WebAPI -dotnet build -dotnet run -``` - -## Execucação com Docker - -Comandos para execução do projeto .NET utilizando Docker: - -```bash -docker build . -t copet-system-api:dev -docker run --name copet-system-api -p 8080:80 -d copet-system-api:dev -``` +Restful API created in .NET 7 to centralize the business rules of the GPIC system (Gerenciador de Projetos de Iniciação Científica). diff --git a/docs/forms/entrega-docs.md b/docs/forms/entrega-docs.md index 06920ae4..4251b236 100644 --- a/docs/forms/entrega-docs.md +++ b/docs/forms/entrega-docs.md @@ -29,7 +29,7 @@ Essa seção deverá ser preenchida apenas pelos alunos bolsistas. Caso o aluno - Número da Conta Corrente - Comprovante de Abertura de Conta -## DOCUMENTOS DO ALUNO A SEREM ENTREGUES (ANEXADOS) +## Documentos do Aluno a serem Entregues (Anexados) Nos próximos itens anexe os documentos solicitados @@ -43,7 +43,7 @@ Nos próximos itens anexe os documentos solicitados - Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET -> File) - Aceite (Boolean) - ```text - DECLARO PARA TODOS OS FINS PERANTE A COORDENADORIA DE PESQUISA E ESTUDOS TECNOLÓGICOS – COPET, QUE AS INFORMAÇÕES PRESTADAS NESTE FORMULÁRIO E OS DOCUMENTOS ANEXADOS SÃO VERDADEIROS. - DECLARO AINDA TER PLENA CIÊNCIA DOS DIREITOS, COMPROMISSOS E OBRIGAÇÕES ASSUMIDOS, DE ACORDO COM OS TERMOS DO EDITAL PIBIC 2020 E ESPECIALMENTE DO TERMO DE ACEITE. - ``` +```text +DECLARO PARA TODOS OS FINS PERANTE A COORDENADORIA DE PESQUISA E ESTUDOS TECNOLÓGICOS – COPET, QUE AS INFORMAÇÕES PRESTADAS NESTE FORMULÁRIO E OS DOCUMENTOS ANEXADOS SÃO VERDADEIROS. +DECLARO AINDA TER PLENA CIÊNCIA DOS DIREITOS, COMPROMISSOS E OBRIGAÇÕES ASSUMIDOS, DE ACORDO COM OS TERMOS DO EDITAL PIBIC 2020 E ESPECIALMENTE DO TERMO DE ACEITE. +``` diff --git a/docs/forms/inscricao-edital.md b/docs/forms/inscricao-edital.md index b24b6e88..0172e68e 100644 --- a/docs/forms/inscricao-edital.md +++ b/docs/forms/inscricao-edital.md @@ -22,7 +22,7 @@ _Campos que comporão as entidades relacionadas com Projeto._ - Título do Projeto de Iniciação Científica - Grande Área (areas.txt -> \*.00.00.00) - Área (areas.txt -> \*.00.00) -- Sub-área (areas.txt -> others) +- Sub-área (areas.txt -> \*.00) - É candidato a Bolsa? (Boolean) - Palavras Chave (3x - String) @@ -85,7 +85,7 @@ _Campos que comporão as entidades relacionadas com Projeto._ ### PontuacaoAtividades (Índice AP - Edital PIBIC) (OK) -- Pegar do arquivo Excel <-- +- _(Planilha de avaliação do projeto de IC)_ ### CompromissoOrientador diff --git a/docs/forms/relatorio-parcial.md b/docs/forms/relatorio-parcial.md new file mode 100644 index 00000000..886108b7 --- /dev/null +++ b/docs/forms/relatorio-parcial.md @@ -0,0 +1,16 @@ +# Refatório Parcial (Edital PIBIC/PIBIC-EM) + +## Relatorio + +- Nome Completo do Orientador +- E-mail institucional do orientador +- Nome completo do bolsista +- E-mail institucional do bolsista +- Programa do Bolsista +- Título do Projeto de IC +- Estágio atual de desenvolvimento do Projeto de IC: + - Percentual atual de desenvolvimento do projeto (aproximadamente de acordo com o cronograma) + - Combobox de 10% à 100%, de 10 em 10. +- Avaliação de Desempenho do Bolsista Segundo o Orientador: + - Ruim, Regular, Bom, Muito Bom, Excelente. +- Informações Adicionais (caso julgue necessário) diff --git a/docs/images/apim_gpic.png b/docs/images/apim_gpic.png new file mode 100644 index 00000000..d9e53da8 Binary files /dev/null and b/docs/images/apim_gpic.png differ diff --git a/docs/images/app_architecture.png b/docs/images/app_architecture.png new file mode 100644 index 00000000..7a3bc8bc Binary files /dev/null and b/docs/images/app_architecture.png differ diff --git a/docs/images/app_tests.png b/docs/images/app_tests.png new file mode 100644 index 00000000..7f240003 Binary files /dev/null and b/docs/images/app_tests.png differ diff --git a/docs/images/ci-cd.png b/docs/images/ci-cd.png new file mode 100644 index 00000000..bd895a0b Binary files /dev/null and b/docs/images/ci-cd.png differ diff --git a/docs/images/demo_rate_limit.png b/docs/images/demo_rate_limit.png new file mode 100644 index 00000000..712a4ea9 Binary files /dev/null and b/docs/images/demo_rate_limit.png differ diff --git a/docs/images/der_simple.png b/docs/images/der_simple.png new file mode 100644 index 00000000..26d938df Binary files /dev/null and b/docs/images/der_simple.png differ diff --git a/docs/images/diag_application.png b/docs/images/diag_application.png new file mode 100644 index 00000000..4910bce7 Binary files /dev/null and b/docs/images/diag_application.png differ diff --git a/docs/images/diag_domain.png b/docs/images/diag_domain.png new file mode 100644 index 00000000..ff7de028 Binary files /dev/null and b/docs/images/diag_domain.png differ diff --git a/docs/images/diag_ioc.png b/docs/images/diag_ioc.png new file mode 100644 index 00000000..9f38cc8b Binary files /dev/null and b/docs/images/diag_ioc.png differ diff --git a/docs/images/diag_persistence.png b/docs/images/diag_persistence.png new file mode 100644 index 00000000..367f3ee9 Binary files /dev/null and b/docs/images/diag_persistence.png differ diff --git a/docs/images/diag_services.png b/docs/images/diag_services.png new file mode 100644 index 00000000..79f4c7dc Binary files /dev/null and b/docs/images/diag_services.png differ diff --git a/docs/images/diag_web_api_functions.png b/docs/images/diag_web_api_functions.png new file mode 100644 index 00000000..df7b65d7 Binary files /dev/null and b/docs/images/diag_web_api_functions.png differ diff --git a/docs/images/domain_tests.png b/docs/images/domain_tests.png new file mode 100644 index 00000000..065fa21f Binary files /dev/null and b/docs/images/domain_tests.png differ diff --git a/docs/images/notice_periods.png b/docs/images/notice_periods.png new file mode 100644 index 00000000..f56f7d2e Binary files /dev/null and b/docs/images/notice_periods.png differ diff --git a/docs/images/project_status.png b/docs/images/project_status.png new file mode 100644 index 00000000..2ba400f1 Binary files /dev/null and b/docs/images/project_status.png differ diff --git a/docs/images/seq_sample.png b/docs/images/seq_sample.png new file mode 100644 index 00000000..4ab90664 Binary files /dev/null and b/docs/images/seq_sample.png differ diff --git a/docs/images/swagger.png b/docs/images/swagger.png new file mode 100644 index 00000000..46fab243 Binary files /dev/null and b/docs/images/swagger.png differ diff --git a/docs/images/system_design.png b/docs/images/system_design.png new file mode 100644 index 00000000..003a8b64 Binary files /dev/null and b/docs/images/system_design.png differ diff --git a/docs/images/webfunctions_cli.png b/docs/images/webfunctions_cli.png new file mode 100644 index 00000000..8624666e Binary files /dev/null and b/docs/images/webfunctions_cli.png differ diff --git a/docs/readme.md b/docs/readme.md index 4ef3585a..9694a792 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,12 +1,9 @@ # Copet System ## COPET - COORDENADORIA DE PESQUISA E ESTUDOS TECNOLÓGICOS + - Sistema de controle de projetos de iniciação científica ## Exemplo de Certificado -- Pasta certificate -## Execução do Docker Compose -``` -docker-compose up -d -``` \ No newline at end of file +- Pasta certificate diff --git a/docs/usecases/analyst-usecases.feature b/docs/usecases/analyst-usecases.feature deleted file mode 100644 index db314af7..00000000 --- a/docs/usecases/analyst-usecases.feature +++ /dev/null @@ -1,4 +0,0 @@ -# Casos de uso do avaliador - -# TODO: - # - ... \ No newline at end of file diff --git a/src/Adapters/Adapters.csproj b/src/Adapters/Adapters.csproj deleted file mode 100644 index 3f4de6a2..00000000 --- a/src/Adapters/Adapters.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net7.0 - enable - enable - 0.0.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Adapters/Gateways/Area/CreateAreaRequest.cs b/src/Adapters/Gateways/Area/CreateAreaRequest.cs deleted file mode 100644 index c860057f..00000000 --- a/src/Adapters/Gateways/Area/CreateAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Area; - -namespace Adapters.Gateways.Area; -public class CreateAreaRequest : CreateAreaInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Area/DetailedReadAreaResponse.cs b/src/Adapters/Gateways/Area/DetailedReadAreaResponse.cs deleted file mode 100644 index f35b5ff5..00000000 --- a/src/Adapters/Gateways/Area/DetailedReadAreaResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Adapters.Gateways.MainArea; -using Adapters.Gateways.Base; - -namespace Adapters.Gateways.Area -{ - public class DetailedReadAreaResponse : IResponse - { - public Guid? Id { get; set; } - public string? Name { get; set; } - public string? Code { get; set; } - public DateTime? DeletedAt { get; set; } - public virtual DetailedReadMainAreaResponse? MainArea { get; set; } - } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/Area/ResumedReadAreaResponse.cs b/src/Adapters/Gateways/Area/ResumedReadAreaResponse.cs deleted file mode 100644 index 7327fec1..00000000 --- a/src/Adapters/Gateways/Area/ResumedReadAreaResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Area; - -namespace Adapters.Gateways.Area; -public class ResumedReadAreaResponse : ResumedReadAreaOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Area/UpdateAreaRequest.cs b/src/Adapters/Gateways/Area/UpdateAreaRequest.cs deleted file mode 100644 index e1ecb8ed..00000000 --- a/src/Adapters/Gateways/Area/UpdateAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Area; - -namespace Adapters.Gateways.Area; -public class UpdateAreaRequest : UpdateAreaInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Auth/UserLoginRequest.cs b/src/Adapters/Gateways/Auth/UserLoginRequest.cs deleted file mode 100644 index 2925b4ef..00000000 --- a/src/Adapters/Gateways/Auth/UserLoginRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Auth; - -namespace Adapters.Gateways.Auth; -public class UserLoginRequest : UserLoginInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Auth/UserLoginResponse.cs b/src/Adapters/Gateways/Auth/UserLoginResponse.cs deleted file mode 100644 index 2d1c8e22..00000000 --- a/src/Adapters/Gateways/Auth/UserLoginResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Auth; - -namespace Adapters.Gateways.Auth; -public class UserLoginResponse : UserLoginOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Auth/UserResetPasswordRequest.cs b/src/Adapters/Gateways/Auth/UserResetPasswordRequest.cs deleted file mode 100644 index 56353c5f..00000000 --- a/src/Adapters/Gateways/Auth/UserResetPasswordRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Auth; - -namespace Adapters.Gateways.Auth; -public class UserResetPasswordRequest : UserResetPasswordInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Base/IRequest.cs b/src/Adapters/Gateways/Base/IRequest.cs deleted file mode 100644 index ad64201d..00000000 --- a/src/Adapters/Gateways/Base/IRequest.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Adapters.Gateways.Base -{ - public interface IRequest { } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/Base/IResponse.cs b/src/Adapters/Gateways/Base/IResponse.cs deleted file mode 100644 index efce3e0f..00000000 --- a/src/Adapters/Gateways/Base/IResponse.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Adapters.Gateways.Base -{ - public interface IResponse { } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/Campus/CreateCampusRequest.cs b/src/Adapters/Gateways/Campus/CreateCampusRequest.cs deleted file mode 100644 index 9c3474eb..00000000 --- a/src/Adapters/Gateways/Campus/CreateCampusRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Campus; - -namespace Adapters.Gateways.Campus; -public class CreateCampusRequest : CreateCampusInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Campus/DetailedReadCampusResponse.cs b/src/Adapters/Gateways/Campus/DetailedReadCampusResponse.cs deleted file mode 100644 index 177a0c80..00000000 --- a/src/Adapters/Gateways/Campus/DetailedReadCampusResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Campus; - -namespace Adapters.Gateways.Campus; -public class DetailedReadCampusResponse : DetailedReadCampusOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Campus/ResumedReadCampusResponse.cs b/src/Adapters/Gateways/Campus/ResumedReadCampusResponse.cs deleted file mode 100644 index b2f85732..00000000 --- a/src/Adapters/Gateways/Campus/ResumedReadCampusResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Campus; - -namespace Adapters.Gateways.Campus; -public class ResumedReadCampusResponse : ResumedReadCampusOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Campus/UpdateCampusRequest.cs b/src/Adapters/Gateways/Campus/UpdateCampusRequest.cs deleted file mode 100644 index 788cff37..00000000 --- a/src/Adapters/Gateways/Campus/UpdateCampusRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Campus; - -namespace Adapters.Gateways.Campus; -public class UpdateCampusRequest : UpdateCampusInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Course/CreateCourseRequest.cs b/src/Adapters/Gateways/Course/CreateCourseRequest.cs deleted file mode 100644 index 10aa47d6..00000000 --- a/src/Adapters/Gateways/Course/CreateCourseRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Course; - -namespace Adapters.Gateways.Course; -public class CreateCourseRequest : CreateCourseInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Course/DetailedReadCourseResponse.cs b/src/Adapters/Gateways/Course/DetailedReadCourseResponse.cs deleted file mode 100644 index 8585a523..00000000 --- a/src/Adapters/Gateways/Course/DetailedReadCourseResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Course; - -namespace Adapters.Gateways.Course; -public class DetailedReadCourseResponse : DetailedReadCourseOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Course/ResumedReadCourseResponse.cs b/src/Adapters/Gateways/Course/ResumedReadCourseResponse.cs deleted file mode 100644 index a5c0a4e9..00000000 --- a/src/Adapters/Gateways/Course/ResumedReadCourseResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Course; - -namespace Adapters.Gateways.Course; -public class ResumedReadCourseResponse : ResumedReadCourseOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Course/UpdateCourseRequest.cs b/src/Adapters/Gateways/Course/UpdateCourseRequest.cs deleted file mode 100644 index 3c9b6a6a..00000000 --- a/src/Adapters/Gateways/Course/UpdateCourseRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Course; - -namespace Adapters.Gateways.Course; -public class UpdateCourseRequest : UpdateCourseInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/MainArea/CreateMainAreaRequest.cs b/src/Adapters/Gateways/MainArea/CreateMainAreaRequest.cs deleted file mode 100644 index 841bf092..00000000 --- a/src/Adapters/Gateways/MainArea/CreateMainAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.MainArea; - -namespace Adapters.Gateways.MainArea; -public class CreateMainAreaRequest : CreateMainAreaInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/MainArea/DetailedReadMainAreaResponse.cs b/src/Adapters/Gateways/MainArea/DetailedReadMainAreaResponse.cs deleted file mode 100644 index 7868cda5..00000000 --- a/src/Adapters/Gateways/MainArea/DetailedReadMainAreaResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.MainArea; - -namespace Adapters.Gateways.MainArea; -public class DetailedReadMainAreaResponse : DetailedMainAreaOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/MainArea/ResumedReadMainAreaResponse.cs b/src/Adapters/Gateways/MainArea/ResumedReadMainAreaResponse.cs deleted file mode 100644 index 9136d943..00000000 --- a/src/Adapters/Gateways/MainArea/ResumedReadMainAreaResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.MainArea; - -namespace Adapters.Gateways.MainArea; -public class ResumedReadMainAreaResponse : ResumedReadMainAreaOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/MainArea/UpdateMainAreaRequest.cs b/src/Adapters/Gateways/MainArea/UpdateMainAreaRequest.cs deleted file mode 100644 index 7f0017b9..00000000 --- a/src/Adapters/Gateways/MainArea/UpdateMainAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.MainArea; - -namespace Adapters.Gateways.MainArea; -public class UpdateMainAreaRequest : UpdateMainAreaInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Notice/CreateNoticeRequest.cs b/src/Adapters/Gateways/Notice/CreateNoticeRequest.cs deleted file mode 100644 index 1c605b28..00000000 --- a/src/Adapters/Gateways/Notice/CreateNoticeRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Notice; - -namespace Adapters.Gateways.Notice; -public class CreateNoticeRequest : CreateNoticeInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Notice/DetailedReadNoticeResponse.cs b/src/Adapters/Gateways/Notice/DetailedReadNoticeResponse.cs deleted file mode 100644 index 029b4335..00000000 --- a/src/Adapters/Gateways/Notice/DetailedReadNoticeResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Notice; - -namespace Adapters.Gateways.Notice; -public class DetailedReadNoticeResponse : DetailedReadNoticeOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Notice/ResumedReadNoticeResponse.cs b/src/Adapters/Gateways/Notice/ResumedReadNoticeResponse.cs deleted file mode 100644 index becd366f..00000000 --- a/src/Adapters/Gateways/Notice/ResumedReadNoticeResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Notice; - -namespace Adapters.Gateways.Notice; -public class ResumedReadNoticeResponse : ResumedReadNoticeOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Notice/UpdateNoticeRequest.cs b/src/Adapters/Gateways/Notice/UpdateNoticeRequest.cs deleted file mode 100644 index af554da6..00000000 --- a/src/Adapters/Gateways/Notice/UpdateNoticeRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Notice; - -namespace Adapters.Gateways.Notice; -public class UpdateNoticeRequest : UpdateNoticeInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Professor/CreateProfessorRequest.cs b/src/Adapters/Gateways/Professor/CreateProfessorRequest.cs deleted file mode 100644 index 89c5fa4f..00000000 --- a/src/Adapters/Gateways/Professor/CreateProfessorRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Professor; - -namespace Adapters.Gateways.Professor; -public class CreateProfessorRequest : CreateProfessorInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Professor/DetailedReadProfessorResponse.cs b/src/Adapters/Gateways/Professor/DetailedReadProfessorResponse.cs deleted file mode 100644 index cd3b1ea5..00000000 --- a/src/Adapters/Gateways/Professor/DetailedReadProfessorResponse.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.User; - -namespace Adapters.Gateways.Professor -{ - public class DetailedReadProfessorResponse : IResponse - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - public UserReadResponse? User { get; set; } - - #region Required Properties - public string? SIAPEEnrollment { get; set; } - public long IdentifyLattes { get; set; } - #endregion - } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/Professor/ResumedReadProfessorResponse.cs b/src/Adapters/Gateways/Professor/ResumedReadProfessorResponse.cs deleted file mode 100644 index 922a4b54..00000000 --- a/src/Adapters/Gateways/Professor/ResumedReadProfessorResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Professor; - -namespace Adapters.Gateways.Professor; -public class ResumedReadProfessorResponse : ResumedReadProfessorOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Professor/UpdateProfessorRequest.cs b/src/Adapters/Gateways/Professor/UpdateProfessorRequest.cs deleted file mode 100644 index f301356c..00000000 --- a/src/Adapters/Gateways/Professor/UpdateProfessorRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Professor; - -namespace Adapters.Gateways.Professor; -public class UpdateProfessorRequest : UpdateProfessorInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProgramType/CreateProgramTypeRequest.cs b/src/Adapters/Gateways/ProgramType/CreateProgramTypeRequest.cs deleted file mode 100644 index 2f999f4d..00000000 --- a/src/Adapters/Gateways/ProgramType/CreateProgramTypeRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProgramType; - -namespace Adapters.Gateways.ProgramType; -public class CreateProgramTypeRequest : CreateProgramTypeInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProgramType/DetailedReadProgramTypeResponse.cs b/src/Adapters/Gateways/ProgramType/DetailedReadProgramTypeResponse.cs deleted file mode 100644 index c79c1e27..00000000 --- a/src/Adapters/Gateways/ProgramType/DetailedReadProgramTypeResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProgramType; - -namespace Adapters.Gateways.ProgramType; -public class DetailedReadProgramTypeResponse : DetailedReadProgramTypeOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProgramType/ResumedReadProgramTypeResponse.cs b/src/Adapters/Gateways/ProgramType/ResumedReadProgramTypeResponse.cs deleted file mode 100644 index ebf1a418..00000000 --- a/src/Adapters/Gateways/ProgramType/ResumedReadProgramTypeResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProgramType; - -namespace Adapters.Gateways.ProgramType; -public class ResumedReadProgramTypeResponse : ResumedReadProgramTypeOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProgramType/UpdateProgramTypeRequest.cs b/src/Adapters/Gateways/ProgramType/UpdateProgramTypeRequest.cs deleted file mode 100644 index a7d9c8c2..00000000 --- a/src/Adapters/Gateways/ProgramType/UpdateProgramTypeRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProgramType; - -namespace Adapters.Gateways.ProgramType; -public class UpdateProgramTypeRequest : UpdateProgramTypeInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Project/DetailedReadProjectResponse.cs b/src/Adapters/Gateways/Project/DetailedReadProjectResponse.cs deleted file mode 100644 index e3855e0f..00000000 --- a/src/Adapters/Gateways/Project/DetailedReadProjectResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Project; - -namespace Adapters.Gateways.Project; -public class DetailedReadProjectResponse : DetailedReadProjectOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Project/OpenProjectRequest.cs b/src/Adapters/Gateways/Project/OpenProjectRequest.cs deleted file mode 100644 index 5581eb40..00000000 --- a/src/Adapters/Gateways/Project/OpenProjectRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Project; - -namespace Adapters.Gateways.Project; -public class OpenProjectRequest : OpenProjectInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Project/ResumedReadProjectResponse.cs b/src/Adapters/Gateways/Project/ResumedReadProjectResponse.cs deleted file mode 100644 index a7e56f60..00000000 --- a/src/Adapters/Gateways/Project/ResumedReadProjectResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Project; - -namespace Adapters.Gateways.Project; -public class ResumedReadProjectResponse : ResumedReadProjectOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Project/UpdateProjectRequest.cs b/src/Adapters/Gateways/Project/UpdateProjectRequest.cs deleted file mode 100644 index 2b1adb9f..00000000 --- a/src/Adapters/Gateways/Project/UpdateProjectRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Project; - -namespace Adapters.Gateways.Project; -public class UpdateProjectRequest : UpdateProjectInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProjectEvaluation/DetailedReadProjectEvaluationResponse.cs b/src/Adapters/Gateways/ProjectEvaluation/DetailedReadProjectEvaluationResponse.cs deleted file mode 100644 index c7a6791a..00000000 --- a/src/Adapters/Gateways/ProjectEvaluation/DetailedReadProjectEvaluationResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProjectEvaluation; - -namespace Adapters.Gateways.ProjectEvaluation; -public class DetailedReadProjectEvaluationResponse : DetailedReadProjectEvaluationOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProjectEvaluation/EvaluateAppealProjectRequest.cs b/src/Adapters/Gateways/ProjectEvaluation/EvaluateAppealProjectRequest.cs deleted file mode 100644 index 8a643003..00000000 --- a/src/Adapters/Gateways/ProjectEvaluation/EvaluateAppealProjectRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProjectEvaluation; - -namespace Adapters.Gateways.ProjectEvaluation; -public class EvaluateAppealProjectRequest : EvaluateAppealProjectInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/ProjectEvaluation/EvaluateSubmissionProjectRequest.cs b/src/Adapters/Gateways/ProjectEvaluation/EvaluateSubmissionProjectRequest.cs deleted file mode 100644 index ef3118c9..00000000 --- a/src/Adapters/Gateways/ProjectEvaluation/EvaluateSubmissionProjectRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.ProjectEvaluation; - -namespace Adapters.Gateways.ProjectEvaluation; -public class EvaluateSubmissionProjectRequest : EvaluateSubmissionProjectInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Student/CreateStudentRequest.cs b/src/Adapters/Gateways/Student/CreateStudentRequest.cs deleted file mode 100644 index fd4431bd..00000000 --- a/src/Adapters/Gateways/Student/CreateStudentRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Student; - -namespace Adapters.Gateways.Student; -public class CreateStudentRequest : CreateStudentInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Student/DetailedReadStudentResponse.cs b/src/Adapters/Gateways/Student/DetailedReadStudentResponse.cs deleted file mode 100644 index e4e95e6c..00000000 --- a/src/Adapters/Gateways/Student/DetailedReadStudentResponse.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Campus; -using Adapters.Gateways.Course; -using Adapters.Gateways.User; - -namespace Adapters.Gateways.Student -{ - public class DetailedReadStudentResponse : IResponse - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - public UserReadResponse? User { get; set; } - public DetailedReadCampusResponse? Campus { get; set; } - public DetailedReadCourseResponse? Course { get; set; } - - #region Required Properties - public DateTime BirthDate { get; set; } - public long RG { get; set; } - public string? IssuingAgency { get; set; } - public DateTime DispatchDate { get; set; } - public int Gender { get; set; } - public int Race { get; set; } - public string? HomeAddress { get; set; } - public string? City { get; set; } - public string? UF { get; set; } - public long CEP { get; set; } - public Guid? CampusId { get; set; } - public Guid? CourseId { get; set; } - public string? StartYear { get; set; } - public Guid? TypeAssistanceId { get; set; } - #endregion - - #region Optional Properties - public int? PhoneDDD { get; set; } - public long? Phone { get; set; } - public int? CellPhoneDDD { get; set; } - public long? CellPhone { get; set; } - #endregion - } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/Student/ResumedReadStudentResponse.cs b/src/Adapters/Gateways/Student/ResumedReadStudentResponse.cs deleted file mode 100644 index 7d572b4a..00000000 --- a/src/Adapters/Gateways/Student/ResumedReadStudentResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Student; - -namespace Adapters.Gateways.Student; -public class ResumedReadStudentResponse : ResumedReadStudentOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/Student/UpdateStudentRequest.cs b/src/Adapters/Gateways/Student/UpdateStudentRequest.cs deleted file mode 100644 index 65123897..00000000 --- a/src/Adapters/Gateways/Student/UpdateStudentRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.Student; - -namespace Adapters.Gateways.Student; -public class UpdateStudentRequest : UpdateStudentInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/StudentDocuments/CreateStudentDocumentsRequest.cs b/src/Adapters/Gateways/StudentDocuments/CreateStudentDocumentsRequest.cs deleted file mode 100644 index 7fe19815..00000000 --- a/src/Adapters/Gateways/StudentDocuments/CreateStudentDocumentsRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.StudentDocuments; - -namespace Adapters.Gateways.StudentDocuments; -public class CreateStudentDocumentsRequest : CreateStudentDocumentsInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/StudentDocuments/DetailedReadStudentDocumentsResponse.cs b/src/Adapters/Gateways/StudentDocuments/DetailedReadStudentDocumentsResponse.cs deleted file mode 100644 index 4403e5bb..00000000 --- a/src/Adapters/Gateways/StudentDocuments/DetailedReadStudentDocumentsResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.StudentDocuments; - -namespace Adapters.Gateways.StudentDocuments; -public class DetailedReadStudentDocumentsResponse : DetailedReadStudentDocumentsOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/StudentDocuments/ResumedReadStudentDocumentsResponse.cs b/src/Adapters/Gateways/StudentDocuments/ResumedReadStudentDocumentsResponse.cs deleted file mode 100644 index cc4b5a05..00000000 --- a/src/Adapters/Gateways/StudentDocuments/ResumedReadStudentDocumentsResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.StudentDocuments; - -namespace Adapters.Gateways.StudentDocuments; -public class ResumedReadStudentDocumentsResponse : ResumedReadStudentDocumentsOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/StudentDocuments/UpdateStudentDocumentsRequest.cs b/src/Adapters/Gateways/StudentDocuments/UpdateStudentDocumentsRequest.cs deleted file mode 100644 index 069e11cf..00000000 --- a/src/Adapters/Gateways/StudentDocuments/UpdateStudentDocumentsRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.StudentDocuments; - -namespace Adapters.Gateways.StudentDocuments; -public class UpdateStudentDocumentsRequest : UpdateStudentDocumentsInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/SubArea/CreateSubAreaRequest.cs b/src/Adapters/Gateways/SubArea/CreateSubAreaRequest.cs deleted file mode 100644 index e6e0ed67..00000000 --- a/src/Adapters/Gateways/SubArea/CreateSubAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.SubArea; - -namespace Adapters.Gateways.SubArea; -public class CreateSubAreaRequest : CreateSubAreaInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/SubArea/DetailedReadSubAreaResponse.cs b/src/Adapters/Gateways/SubArea/DetailedReadSubAreaResponse.cs deleted file mode 100644 index 3bba32a0..00000000 --- a/src/Adapters/Gateways/SubArea/DetailedReadSubAreaResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Adapters.Gateways.Area; -using Adapters.Gateways.Base; - -namespace Adapters.Gateways.SubArea -{ - public class DetailedReadSubAreaResponse : IResponse - { - public Guid? Id { get; set; } - public string? Name { get; set; } - public string? Code { get; set; } - public DateTime? DeletedAt { get; set; } - public virtual DetailedReadAreaResponse? Area { get; set; } - } -} \ No newline at end of file diff --git a/src/Adapters/Gateways/SubArea/ResumedReadSubAreaResponse.cs b/src/Adapters/Gateways/SubArea/ResumedReadSubAreaResponse.cs deleted file mode 100644 index abc26575..00000000 --- a/src/Adapters/Gateways/SubArea/ResumedReadSubAreaResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.SubArea; - -namespace Adapters.Gateways.SubArea; -public class ResumedReadSubAreaResponse : ResumedReadSubAreaOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/SubArea/UpdateSubAreaRequest.cs b/src/Adapters/Gateways/SubArea/UpdateSubAreaRequest.cs deleted file mode 100644 index c73ffee1..00000000 --- a/src/Adapters/Gateways/SubArea/UpdateSubAreaRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.SubArea; - -namespace Adapters.Gateways.SubArea; -public class UpdateSubAreaRequest : UpdateSubAreaInput, IRequest { } diff --git a/src/Adapters/Gateways/TypeAssistance/CreateTypeAssistanceRequest.cs b/src/Adapters/Gateways/TypeAssistance/CreateTypeAssistanceRequest.cs deleted file mode 100644 index ccb1ad37..00000000 --- a/src/Adapters/Gateways/TypeAssistance/CreateTypeAssistanceRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.TypeAssistance; - -namespace Adapters.Gateways.TypeAssistance; -public class CreateTypeAssistanceRequest : CreateTypeAssistanceInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/TypeAssistance/DetailedReadTypeAssistanceResponse.cs b/src/Adapters/Gateways/TypeAssistance/DetailedReadTypeAssistanceResponse.cs deleted file mode 100644 index 646b09a5..00000000 --- a/src/Adapters/Gateways/TypeAssistance/DetailedReadTypeAssistanceResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.TypeAssistance; - -namespace Adapters.Gateways.TypeAssistance; -public class DetailedReadTypeAssistanceResponse : DetailedReadTypeAssistanceOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/TypeAssistance/ResumedReadTypeAssistanceResponse.cs b/src/Adapters/Gateways/TypeAssistance/ResumedReadTypeAssistanceResponse.cs deleted file mode 100644 index a5302e93..00000000 --- a/src/Adapters/Gateways/TypeAssistance/ResumedReadTypeAssistanceResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.TypeAssistance; - -namespace Adapters.Gateways.TypeAssistance; -public class ResumedReadTypeAssistanceResponse : ResumedReadTypeAssistanceOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/TypeAssistance/UpdateTypeAssistanceRequest.cs b/src/Adapters/Gateways/TypeAssistance/UpdateTypeAssistanceRequest.cs deleted file mode 100644 index 404174b8..00000000 --- a/src/Adapters/Gateways/TypeAssistance/UpdateTypeAssistanceRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.TypeAssistance; - -namespace Adapters.Gateways.TypeAssistance; -public class UpdateTypeAssistanceRequest : UpdateTypeAssistanceInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Gateways/User/UserReadResponse.cs b/src/Adapters/Gateways/User/UserReadResponse.cs deleted file mode 100644 index bbf5c23c..00000000 --- a/src/Adapters/Gateways/User/UserReadResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.User; - -namespace Adapters.Gateways.User; -public class UserReadResponse : UserReadOutput, IResponse { } \ No newline at end of file diff --git a/src/Adapters/Gateways/User/UserUpdateRequest.cs b/src/Adapters/Gateways/User/UserUpdateRequest.cs deleted file mode 100644 index d68d87b3..00000000 --- a/src/Adapters/Gateways/User/UserUpdateRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Adapters.Gateways.Base; -using Domain.Contracts.User; - -namespace Adapters.Gateways.User; -public class UserUpdateRequest : UserUpdateInput, IRequest { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/Base/IGenericCRUDPresenterController.cs b/src/Adapters/Interfaces/Base/IGenericCRUDPresenterController.cs deleted file mode 100644 index 55d12e7d..00000000 --- a/src/Adapters/Interfaces/Base/IGenericCRUDPresenterController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Adapters.Gateways.Base; - -namespace Adapters.Interfaces.Base -{ - public interface IGenericCRUDPresenterController - { - /// - /// Busca entidade pelo Id informado. - /// Lança uma exceção quando a entidade não é encontrada. - /// - /// Id da entidade. - /// Entidade encontrada. - Task GetById(Guid? id); - - /// - /// Busca todas as entidades ativas. - /// - /// Lista de entidades ativas. - Task> GetAll(int skip, int take); - - /// - /// Cria entidade conforme parâmetros fornecidos. - /// - /// Parâmetros de criação. - /// Entidade criada. - Task Create(IRequest request); - - /// - /// Remove entidade através do Id informado. - /// - /// Id da entidade a ser removida. - /// Entidade removida. - Task Delete(Guid? id); - - /// - /// Atualiza entidade conforme parâmetros fornecidos. - /// - /// Parâmetros de atualização. - /// Entidade atualizada. - Task Update(Guid? id, IRequest request); - } -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/IAreaPresenterController.cs b/src/Adapters/Interfaces/IAreaPresenterController.cs deleted file mode 100644 index 4696f8b2..00000000 --- a/src/Adapters/Interfaces/IAreaPresenterController.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface IAreaPresenterController : IGenericCRUDPresenterController -{ - Task> GetAreasByMainArea(Guid? mainAreaId, int skip, int take); -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/IAuthPresenterController.cs b/src/Adapters/Interfaces/IAuthPresenterController.cs deleted file mode 100644 index 608a8f00..00000000 --- a/src/Adapters/Interfaces/IAuthPresenterController.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Adapters.Gateways.Auth; - -namespace Adapters.Interfaces; -public interface IAuthPresenterController -{ - Task ConfirmEmail(string? email, string? token); - Task ForgotPassword(string? email); - Task Login(UserLoginRequest request); - Task ResetPassword(UserResetPasswordRequest request); -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/ICampusPresenterController.cs b/src/Adapters/Interfaces/ICampusPresenterController.cs deleted file mode 100644 index e8068a91..00000000 --- a/src/Adapters/Interfaces/ICampusPresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface ICampusPresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/ICoursePresenterController.cs b/src/Adapters/Interfaces/ICoursePresenterController.cs deleted file mode 100644 index c031fa60..00000000 --- a/src/Adapters/Interfaces/ICoursePresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface ICoursePresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/IMainAreaPresenterController.cs b/src/Adapters/Interfaces/IMainAreaPresenterController.cs deleted file mode 100644 index fd38bcc9..00000000 --- a/src/Adapters/Interfaces/IMainAreaPresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface IMainAreaPresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/INoticePresenterController.cs b/src/Adapters/Interfaces/INoticePresenterController.cs deleted file mode 100644 index e5a0346d..00000000 --- a/src/Adapters/Interfaces/INoticePresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface INoticePresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/IProfessorPresenterController.cs b/src/Adapters/Interfaces/IProfessorPresenterController.cs deleted file mode 100644 index d80895f1..00000000 --- a/src/Adapters/Interfaces/IProfessorPresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface IProfessorPresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/IProgramTypePresenterController.cs b/src/Adapters/Interfaces/IProgramTypePresenterController.cs deleted file mode 100644 index 559ac319..00000000 --- a/src/Adapters/Interfaces/IProgramTypePresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface IProgramTypePresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/IProjectEvaluationPresenterController.cs b/src/Adapters/Interfaces/IProjectEvaluationPresenterController.cs deleted file mode 100644 index 760f3838..00000000 --- a/src/Adapters/Interfaces/IProjectEvaluationPresenterController.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Adapters.Gateways.Project; -using Adapters.Gateways.ProjectEvaluation; - -namespace Adapters.Interfaces -{ - public interface IProjectEvaluationPresenterController - { - Task EvaluateAppealProject(EvaluateAppealProjectRequest request); - Task EvaluateSubmissionProject(EvaluateSubmissionProjectRequest request); - Task GetEvaluationByProjectId(Guid? projectId); - } -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/IProjectPresenterController.cs b/src/Adapters/Interfaces/IProjectPresenterController.cs deleted file mode 100644 index 63a737e8..00000000 --- a/src/Adapters/Interfaces/IProjectPresenterController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Adapters.Gateways.Project; - -namespace Adapters.Interfaces; -public interface IProjectPresenterController -{ - Task> GetClosedProjects(int skip, int take, bool onlyMyProjects = true); - Task> GetOpenProjects(int skip, int take, bool onlyMyProjects = true); - Task GetProjectById(Guid? id); - Task OpenProject(OpenProjectRequest input); - Task UpdateProject(Guid? id, UpdateProjectRequest input); - Task CancelProject(Guid? id, string? observation); - Task AppealProject(Guid? projectId, string? appealDescription); - Task SubmitProject(Guid? projectId); -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/IStudentDocumentsPresenterController.cs b/src/Adapters/Interfaces/IStudentDocumentsPresenterController.cs deleted file mode 100644 index 3b2eec0e..00000000 --- a/src/Adapters/Interfaces/IStudentDocumentsPresenterController.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Adapters.Gateways.StudentDocuments; - -namespace Adapters.Interfaces; -public interface IStudentDocumentsPresenterController -{ - Task Create(CreateStudentDocumentsRequest model); - Task Delete(Guid? id); - Task GetByProjectId(Guid? projectId); - Task GetByStudentId(Guid? studentId); - Task Update(Guid? id, UpdateStudentDocumentsRequest model); -} \ No newline at end of file diff --git a/src/Adapters/Interfaces/IStudentPresenterController.cs b/src/Adapters/Interfaces/IStudentPresenterController.cs deleted file mode 100644 index 665b441a..00000000 --- a/src/Adapters/Interfaces/IStudentPresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface IStudentPresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/ISubAreaPresenterController.cs b/src/Adapters/Interfaces/ISubAreaPresenterController.cs deleted file mode 100644 index a4379b22..00000000 --- a/src/Adapters/Interfaces/ISubAreaPresenterController.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface ISubAreaPresenterController : IGenericCRUDPresenterController -{ - Task> GetSubAreasByArea(Guid? areaId, int skip, int take); -} diff --git a/src/Adapters/Interfaces/ITypeAssistancePresenterController.cs b/src/Adapters/Interfaces/ITypeAssistancePresenterController.cs deleted file mode 100644 index 38b302a0..00000000 --- a/src/Adapters/Interfaces/ITypeAssistancePresenterController.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Adapters.Interfaces.Base; - -namespace Adapters.Interfaces; -public interface ITypeAssistancePresenterController : IGenericCRUDPresenterController { } \ No newline at end of file diff --git a/src/Adapters/Interfaces/IUserPresenterController.cs b/src/Adapters/Interfaces/IUserPresenterController.cs deleted file mode 100644 index 96cd62e1..00000000 --- a/src/Adapters/Interfaces/IUserPresenterController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Adapters.Gateways.User; - -namespace Adapters.Interfaces -{ - public interface IUserPresenterController - { - Task ActivateUser(Guid? id); - Task DeactivateUser(Guid? id); - Task> GetActiveUsers(int skip, int take); - Task> GetInactiveUsers(int skip, int take); - Task GetUserById(Guid? id); - Task UpdateUser(UserUpdateRequest request); - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/AreaMappings.cs b/src/Adapters/Mappings/AreaMappings.cs deleted file mode 100755 index bf389e13..00000000 --- a/src/Adapters/Mappings/AreaMappings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Area; -using Domain.Contracts.Area; - -namespace Adapters.Mappings -{ - public class AreaMappings : Profile - { - public AreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.MainArea, opt => opt.MapFrom(src => src.MainArea)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/AuthMappings.cs b/src/Adapters/Mappings/AuthMappings.cs deleted file mode 100755 index 1bc010e1..00000000 --- a/src/Adapters/Mappings/AuthMappings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Auth; -using Domain.Contracts.Auth; - -namespace Adapters.Mappings -{ - public class AuthMappings : Profile - { - public AuthMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/CampusMappings.cs b/src/Adapters/Mappings/CampusMappings.cs deleted file mode 100755 index f073b4e6..00000000 --- a/src/Adapters/Mappings/CampusMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Campus; -using Domain.Contracts.Campus; - -namespace Adapters.Mappings -{ - public class CampusMappings : Profile - { - public CampusMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/CourseMappings.cs b/src/Adapters/Mappings/CourseMappings.cs deleted file mode 100755 index 00ea98e5..00000000 --- a/src/Adapters/Mappings/CourseMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Course; -using Domain.Contracts.Course; - -namespace Adapters.Mappings -{ - public class CourseMappings : Profile - { - public CourseMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/MainAreaMappings.cs b/src/Adapters/Mappings/MainAreaMappings.cs deleted file mode 100755 index 7460be78..00000000 --- a/src/Adapters/Mappings/MainAreaMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.MainArea; -using Domain.Contracts.MainArea; - -namespace Adapters.Mappings -{ - public class MainAreaMappings : Profile - { - public MainAreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/NoticeMappings.cs b/src/Adapters/Mappings/NoticeMappings.cs deleted file mode 100755 index 27231971..00000000 --- a/src/Adapters/Mappings/NoticeMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Notice; -using Domain.Contracts.Notice; - -namespace Adapters.Mappings -{ - public class NoticeMappings : Profile - { - public NoticeMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/ProfessorMappings.cs b/src/Adapters/Mappings/ProfessorMappings.cs deleted file mode 100755 index 21a625b4..00000000 --- a/src/Adapters/Mappings/ProfessorMappings.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Professor; -using Domain.Contracts.Professor; - -namespace Adapters.Mappings -{ - public class ProfessorMappings : Profile - { - public ProfessorMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.User, - opt => opt.MapFrom(src => src.User)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/ProgramTypeMappings.cs b/src/Adapters/Mappings/ProgramTypeMappings.cs deleted file mode 100755 index c3712dfb..00000000 --- a/src/Adapters/Mappings/ProgramTypeMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.ProgramType; -using Domain.Contracts.ProgramType; - -namespace Adapters.Mappings -{ - public class ProgramTypeMappings : Profile - { - public ProgramTypeMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/ProjectEvaluationMapping.cs b/src/Adapters/Mappings/ProjectEvaluationMapping.cs deleted file mode 100644 index 460c9d25..00000000 --- a/src/Adapters/Mappings/ProjectEvaluationMapping.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Adapters.Gateways.ProjectEvaluation; -using AutoMapper; -using Domain.Contracts.ProjectEvaluation; - -namespace Adapters.Mappings -{ - public class ProjectEvaluationMapping : Profile - { - public ProjectEvaluationMapping() - { - CreateMap(); - CreateMap(); - CreateMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/ProjectMappings.cs b/src/Adapters/Mappings/ProjectMappings.cs deleted file mode 100755 index 0f5a5cd7..00000000 --- a/src/Adapters/Mappings/ProjectMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Project; -using Domain.Contracts.Project; - -namespace Adapters.Mappings -{ - public class ProjectMappings : Profile - { - public ProjectMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/StudentDocumentsMappings.cs b/src/Adapters/Mappings/StudentDocumentsMappings.cs deleted file mode 100755 index ac90ccd3..00000000 --- a/src/Adapters/Mappings/StudentDocumentsMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.StudentDocuments; -using Domain.Contracts.StudentDocuments; - -namespace Adapters.Mappings -{ - public class StudentDocumentsMappings : Profile - { - public StudentDocumentsMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/StudentMappings.cs b/src/Adapters/Mappings/StudentMappings.cs deleted file mode 100755 index 6ef7296b..00000000 --- a/src/Adapters/Mappings/StudentMappings.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.Student; -using Domain.Contracts.Student; - -namespace Adapters.Mappings -{ - public class StudentMappings : Profile - { - public StudentMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.User, - opt => opt.MapFrom(src => src.User)) - .ForMember(dest => dest.Campus, - opt => opt.MapFrom(src => src.Campus)) - .ForMember(dest => dest.Course, - opt => opt.MapFrom(src => src.Course)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/SubAreaMappings.cs b/src/Adapters/Mappings/SubAreaMappings.cs deleted file mode 100755 index 0abf0238..00000000 --- a/src/Adapters/Mappings/SubAreaMappings.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.SubArea; -using Domain.Contracts.SubArea; - -namespace Adapters.Mappings -{ - public class SubAreaMappings : Profile - { - public SubAreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.Area, opt => opt.MapFrom(src => src.Area)) - .ForPath(dest => dest.Area!.MainArea, opt => opt.MapFrom(src => src.Area!.MainArea)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/TypeAssistanceMappings.cs b/src/Adapters/Mappings/TypeAssistanceMappings.cs deleted file mode 100755 index 5f417cf0..00000000 --- a/src/Adapters/Mappings/TypeAssistanceMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.TypeAssistance; -using Domain.Contracts.TypeAssistance; - -namespace Adapters.Mappings -{ - public class TypeAssistanceMappings : Profile - { - public TypeAssistanceMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/Mappings/UserMappings.cs b/src/Adapters/Mappings/UserMappings.cs deleted file mode 100755 index e5e05eaf..00000000 --- a/src/Adapters/Mappings/UserMappings.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Adapters.Gateways.User; -using Domain.Contracts.User; - -namespace Adapters.Mappings -{ - public class UserMappings : Profile - { - public UserMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/AreaPresenterController.cs b/src/Adapters/PresenterController/AreaPresenterController.cs deleted file mode 100755 index 4547775c..00000000 --- a/src/Adapters/PresenterController/AreaPresenterController.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Adapters.Gateways.Area; -using Adapters.Gateways.Base; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Area; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class AreaPresenterController : IAreaPresenterController - { - #region Global Scope - private readonly ICreateArea _createArea; - private readonly IUpdateArea _updateArea; - private readonly IDeleteArea _deleteArea; - private readonly IGetAreasByMainArea _getAreasByMainArea; - private readonly IGetAreaById _getAreaById; - private readonly IMapper _mapper; - - public AreaPresenterController(ICreateArea createArea, IUpdateArea updateArea, IDeleteArea deleteArea, IGetAreasByMainArea getAreasByMainArea, IGetAreaById getAreaById, IMapper mapper) - { - _createArea = createArea; - _updateArea = updateArea; - _deleteArea = deleteArea; - _getAreasByMainArea = getAreasByMainArea; - _getAreaById = getAreaById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateAreaRequest; - var input = _mapper.Map(dto); - var result = await _createArea.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteArea.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAreasByMainArea(Guid? mainAreaId, int skip, int take) - { - var result = await _getAreasByMainArea.Execute(mainAreaId, skip, take); - return _mapper.Map>(result); - } - - public Task> GetAll(int skip, int take) - { - throw new NotImplementedException(); - } - - public async Task GetById(Guid? id) - { - var result = await _getAreaById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateAreaRequest; - var input = _mapper.Map(dto); - var result = await _updateArea.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/AuthPresenterController.cs b/src/Adapters/PresenterController/AuthPresenterController.cs deleted file mode 100755 index 95b81892..00000000 --- a/src/Adapters/PresenterController/AuthPresenterController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Adapters.Gateways.Auth; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Auth; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class AuthPresenterController : IAuthPresenterController - { - #region Global Scope - private readonly IMapper _mapper; - private readonly IConfirmEmail _confirmUserEmail; - private readonly IForgotPassword _forgotPassword; - private readonly ILogin _login; - private readonly IResetPassword _resetPassword; - - public AuthPresenterController(IMapper mapper, IConfirmEmail confirmUserEmail, IForgotPassword forgotPassword, ILogin login, IResetPassword resetPassword) - { - _mapper = mapper; - _confirmUserEmail = confirmUserEmail; - _forgotPassword = forgotPassword; - _login = login; - _resetPassword = resetPassword; - } - #endregion - - public async Task ConfirmEmail(string? email, string? token) => await _confirmUserEmail.Execute(email, token); - - public async Task ForgotPassword(string? email) => await _forgotPassword.Execute(email); - - public async Task Login(UserLoginRequest request) => _mapper - .Map(await _login - .Execute(_mapper - .Map(request))); - - public async Task ResetPassword(UserResetPasswordRequest request) => await _resetPassword.Execute(_mapper.Map(request)); - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/CampusPresenterController.cs b/src/Adapters/PresenterController/CampusPresenterController.cs deleted file mode 100755 index 89e1e210..00000000 --- a/src/Adapters/PresenterController/CampusPresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Campus; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class CampusPresenterController : ICampusPresenterController - { - #region Global Scope - private readonly ICreateCampus _createCampus; - private readonly IUpdateCampus _updateCampus; - private readonly IDeleteCampus _deleteCampus; - private readonly IGetCampuses _getCampuses; - private readonly IGetCampusById _getCampusById; - private readonly IMapper _mapper; - - public CampusPresenterController(ICreateCampus createCampus, IUpdateCampus updateCampus, IDeleteCampus deleteCampus, IGetCampuses getCampuses, IGetCampusById getCampusById, IMapper mapper) - { - _createCampus = createCampus; - _updateCampus = updateCampus; - _deleteCampus = deleteCampus; - _getCampuses = getCampuses; - _getCampusById = getCampusById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateCampusRequest; - var input = _mapper.Map(dto); - var result = await _createCampus.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteCampus.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getCampuses.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getCampusById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateCampusRequest; - var input = _mapper.Map(dto); - var result = await _updateCampus.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/CoursePresenterController.cs b/src/Adapters/PresenterController/CoursePresenterController.cs deleted file mode 100755 index 12904a90..00000000 --- a/src/Adapters/PresenterController/CoursePresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Course; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class CoursePresenterController : ICoursePresenterController - { - #region Global Scope - private readonly ICreateCourse _createCourse; - private readonly IUpdateCourse _updateCourse; - private readonly IDeleteCourse _deleteCourse; - private readonly IGetCourses _getCourses; - private readonly IGetCourseById _getCourseById; - private readonly IMapper _mapper; - - public CoursePresenterController(ICreateCourse createCourse, IUpdateCourse updateCourse, IDeleteCourse deleteCourse, IGetCourses getCourses, IGetCourseById getCourseById, IMapper mapper) - { - _createCourse = createCourse; - _updateCourse = updateCourse; - _deleteCourse = deleteCourse; - _getCourses = getCourses; - _getCourseById = getCourseById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateCourseRequest; - var input = _mapper.Map(dto); - var result = await _createCourse.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteCourse.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getCourses.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getCourseById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateCourseRequest; - var input = _mapper.Map(dto); - var result = await _updateCourse.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/MainAreaPresenterController.cs b/src/Adapters/PresenterController/MainAreaPresenterController.cs deleted file mode 100755 index 4864f15e..00000000 --- a/src/Adapters/PresenterController/MainAreaPresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.MainArea; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class MainAreaPresenterController : IMainAreaPresenterController - { - #region Global Scope - private readonly ICreateMainArea _createMainArea; - private readonly IUpdateMainArea _updateMainArea; - private readonly IDeleteMainArea _deleteMainArea; - private readonly IGetMainAreas _getMainAreas; - private readonly IGetMainAreaById _getMainAreaById; - private readonly IMapper _mapper; - - public MainAreaPresenterController(ICreateMainArea createMainArea, IUpdateMainArea updateMainArea, IDeleteMainArea deleteMainArea, IGetMainAreas getMainAreas, IGetMainAreaById getMainAreaById, IMapper mapper) - { - _createMainArea = createMainArea; - _updateMainArea = updateMainArea; - _deleteMainArea = deleteMainArea; - _getMainAreas = getMainAreas; - _getMainAreaById = getMainAreaById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateMainAreaRequest; - var input = _mapper.Map(dto); - var result = await _createMainArea.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteMainArea.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getMainAreas.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getMainAreaById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateMainAreaRequest; - var input = _mapper.Map(dto); - var result = await _updateMainArea.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/NoticePresenterController.cs b/src/Adapters/PresenterController/NoticePresenterController.cs deleted file mode 100755 index c7061713..00000000 --- a/src/Adapters/PresenterController/NoticePresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Notice; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class NoticePresenterController : INoticePresenterController - { - #region Global Scope - private readonly ICreateNotice _createNotice; - private readonly IUpdateNotice _updateNotice; - private readonly IDeleteNotice _deleteNotice; - private readonly IGetNotices _getNotices; - private readonly IGetNoticeById _getNoticeById; - private readonly IMapper _mapper; - - public NoticePresenterController(ICreateNotice createNotice, IUpdateNotice updateNotice, IDeleteNotice deleteNotice, IGetNotices getNotices, IGetNoticeById getNoticeById, IMapper mapper) - { - _createNotice = createNotice; - _updateNotice = updateNotice; - _deleteNotice = deleteNotice; - _getNotices = getNotices; - _getNoticeById = getNoticeById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateNoticeRequest; - var input = _mapper.Map(dto); - var result = await _createNotice.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteNotice.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getNotices.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getNoticeById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateNoticeRequest; - var input = _mapper.Map(dto); - var result = await _updateNotice.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/ProfessorPresenterController.cs b/src/Adapters/PresenterController/ProfessorPresenterController.cs deleted file mode 100755 index 5d614f7c..00000000 --- a/src/Adapters/PresenterController/ProfessorPresenterController.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Professor; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class ProfessorPresenterController : IProfessorPresenterController - { - #region Global Scope - private readonly ICreateProfessor _createProfessor; - private readonly IUpdateProfessor _updateProfessor; - private readonly IDeleteProfessor _deleteProfessor; - private readonly IGetProfessors _getProfessors; - private readonly IGetProfessorById _getProfessorById; - private readonly IMapper _mapper; - - public ProfessorPresenterController(ICreateProfessor createProfessor, - IUpdateProfessor updateProfessor, - IDeleteProfessor deleteProfessor, - IGetProfessors getProfessors, - IGetProfessorById getProfessorById, - IMapper mapper) - { - _createProfessor = createProfessor; - _updateProfessor = updateProfessor; - _deleteProfessor = deleteProfessor; - _getProfessors = getProfessors; - _getProfessorById = getProfessorById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateProfessorRequest; - var input = _mapper.Map(dto); - var result = await _createProfessor.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteProfessor.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getProfessors.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getProfessorById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateProfessorRequest; - var input = _mapper.Map(dto); - var result = await _updateProfessor.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/ProgramTypePresenterController.cs b/src/Adapters/PresenterController/ProgramTypePresenterController.cs deleted file mode 100755 index 7499dbf7..00000000 --- a/src/Adapters/PresenterController/ProgramTypePresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.ProgramType; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class ProgramTypePresenterController : IProgramTypePresenterController - { - #region Global Scope - private readonly ICreateProgramType _createProgramType; - private readonly IUpdateProgramType _updateProgramType; - private readonly IDeleteProgramType _deleteProgramType; - private readonly IGetProgramTypes _getProgramTypes; - private readonly IGetProgramTypeById _getProgramTypeById; - private readonly IMapper _mapper; - - public ProgramTypePresenterController(ICreateProgramType createProgramType, IUpdateProgramType updateProgramType, IDeleteProgramType deleteProgramType, IGetProgramTypes getProgramTypes, IGetProgramTypeById getProgramTypeById, IMapper mapper) - { - _createProgramType = createProgramType; - _updateProgramType = updateProgramType; - _deleteProgramType = deleteProgramType; - _getProgramTypes = getProgramTypes; - _getProgramTypeById = getProgramTypeById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateProgramTypeRequest; - var input = _mapper.Map(dto); - var result = await _createProgramType.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteProgramType.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getProgramTypes.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getProgramTypeById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateProgramTypeRequest; - var input = _mapper.Map(dto); - var result = await _updateProgramType.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/ProjectEvaluationPresenterController.cs b/src/Adapters/PresenterController/ProjectEvaluationPresenterController.cs deleted file mode 100644 index 1fabff06..00000000 --- a/src/Adapters/PresenterController/ProjectEvaluationPresenterController.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Adapters.Gateways.Project; -using Adapters.Gateways.ProjectEvaluation; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.ProjectEvaluation; -using Domain.Interfaces.UseCases.ProjectEvaluation; - -namespace Adapters.PresenterController -{ - public class ProjectEvaluationPresenterController : IProjectEvaluationPresenterController - { - #region Global Scope - private readonly IEvaluateAppealProject _evaluateAppealProject; - private readonly IEvaluateSubmissionProject _evaluateSubmissionProject; - private readonly IGetEvaluationByProjectId _getEvaluationByProjectId; - private readonly IMapper _mapper; - - public ProjectEvaluationPresenterController( - IEvaluateAppealProject evaluateAppealProject, - IEvaluateSubmissionProject evaluateSubmissionProject, - IGetEvaluationByProjectId getEvaluationByProjectId, - IMapper mapper) - { - _evaluateAppealProject = evaluateAppealProject; - _evaluateSubmissionProject = evaluateSubmissionProject; - _getEvaluationByProjectId = getEvaluationByProjectId; - _mapper = mapper; - } - #endregion - - public async Task EvaluateAppealProject(EvaluateAppealProjectRequest request) - { - var input = _mapper.Map(request); - var output = await _evaluateAppealProject.Execute(input); - return _mapper.Map(output); - } - - public async Task EvaluateSubmissionProject(EvaluateSubmissionProjectRequest request) - { - var input = _mapper.Map(request); - var output = await _evaluateSubmissionProject.Execute(input); - return _mapper.Map(output); - } - - public async Task GetEvaluationByProjectId(Guid? projectId) - { - var output = await _getEvaluationByProjectId.Execute(projectId); - return _mapper.Map(output); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/ProjectPresenterController.cs b/src/Adapters/PresenterController/ProjectPresenterController.cs deleted file mode 100644 index 4bba9aff..00000000 --- a/src/Adapters/PresenterController/ProjectPresenterController.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Adapters.Gateways.Project; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Interfaces.UseCases.Project; - -namespace Adapters.PresenterController -{ - public class ProjectPresenterController : IProjectPresenterController - { - #region Global Scope - private readonly ICancelProject _cancelProject; - private readonly IGetClosedProjects _getClosedProjects; - private readonly IGetOpenProjects _getOpenProjects; - private readonly IGetProjectById _getProjectById; - private readonly IOpenProject _openProject; - private readonly IUpdateProject _updateProject; - private readonly IAppealProject _appealProject; - private readonly ISubmitProject _submitProject; - private readonly IMapper _mapper; - - public ProjectPresenterController( - ICancelProject cancelProject, - IGetClosedProjects getClosedProjects, - IGetOpenProjects getOpenProjects, - IGetProjectById getProjectById, - IOpenProject openProject, - IUpdateProject updateProject, - IAppealProject appealProject, - ISubmitProject submitProject, - IMapper mapper) - { - _cancelProject = cancelProject; - _getClosedProjects = getClosedProjects; - _getOpenProjects = getOpenProjects; - _getProjectById = getProjectById; - _openProject = openProject; - _updateProject = updateProject; - _appealProject = appealProject; - _submitProject = submitProject; - _mapper = mapper; - } - #endregion - - public async Task AppealProject(Guid? projectId, string? appealDescription) - { - var output = await _appealProject.Execute(projectId, appealDescription); - return _mapper.Map(output); - } - - public async Task CancelProject(Guid? id, string? observation) - { - var output = await _cancelProject.Execute(id, observation); - return _mapper.Map(output); - } - - public async Task> GetClosedProjects(int skip, int take, bool onlyMyProjects = true) - { - var output = await _getClosedProjects.Execute(skip, take, onlyMyProjects); - return _mapper.Map>(output); - } - - public async Task> GetOpenProjects(int skip, int take, bool onlyMyProjects = true) - { - var output = await _getOpenProjects.Execute(skip, take, onlyMyProjects); - return _mapper.Map>(output); - } - - public async Task GetProjectById(Guid? id) - { - var output = await _getProjectById.Execute(id); - return _mapper.Map(output); - } - - public async Task OpenProject(OpenProjectRequest request) - { - var input = _mapper.Map(request); - var output = await _openProject.Execute(input); - return _mapper.Map(output); - } - - public async Task SubmitProject(Guid? projectId) - { - var output = await _submitProject.Execute(projectId); - return _mapper.Map(output); - } - - public async Task UpdateProject(Guid? id, UpdateProjectRequest request) - { - var input = _mapper.Map(request); - var output = await _updateProject.Execute(id, input); - return _mapper.Map(output); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/StudentDocumentsPresenterController.cs b/src/Adapters/PresenterController/StudentDocumentsPresenterController.cs deleted file mode 100644 index 8c2631b7..00000000 --- a/src/Adapters/PresenterController/StudentDocumentsPresenterController.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Adapters.Gateways.StudentDocuments; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class StudentDocumentsPresenterController : IStudentDocumentsPresenterController - { - #region Global Scope - private readonly ICreateStudentDocuments _createStudentDocuments; - private readonly IUpdateStudentDocuments _updateStudentDocuments; - private readonly IDeleteStudentDocuments _deleteStudentDocuments; - private readonly IGetStudentDocumentsByProjectId _getStudentDocumentsByProject; - private readonly IGetStudentDocumentsByStudentId _getStudentDocumentsByStudent; - private readonly IMapper _mapper; - public StudentDocumentsPresenterController( - ICreateStudentDocuments createStudentDocuments, - IUpdateStudentDocuments updateStudentDocuments, - IDeleteStudentDocuments deleteStudentDocuments, - IGetStudentDocumentsByProjectId getStudentDocumentsByProject, - IGetStudentDocumentsByStudentId getStudentDocumentsByStudent, - IMapper mapper) - { - _createStudentDocuments = createStudentDocuments; - _updateStudentDocuments = updateStudentDocuments; - _deleteStudentDocuments = deleteStudentDocuments; - _getStudentDocumentsByProject = getStudentDocumentsByProject; - _getStudentDocumentsByStudent = getStudentDocumentsByStudent; - _mapper = mapper; - } - #endregion - - public async Task Create(CreateStudentDocumentsRequest model) - { - var input = _mapper.Map(model); - var result = await _createStudentDocuments.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteStudentDocuments.Execute(id); - return _mapper.Map(result); - } - - public async Task GetByProjectId(Guid? projectId) - { - var result = await _getStudentDocumentsByProject.Execute(projectId); - return _mapper.Map(result); - } - - public async Task GetByStudentId(Guid? studentId) - { - var result = await _getStudentDocumentsByStudent.Execute(studentId); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, UpdateStudentDocumentsRequest model) - { - var input = _mapper.Map(model); - var result = await _updateStudentDocuments.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/StudentPresenterController.cs b/src/Adapters/PresenterController/StudentPresenterController.cs deleted file mode 100755 index e2ce0885..00000000 --- a/src/Adapters/PresenterController/StudentPresenterController.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.Student; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class StudentPresenterController : IStudentPresenterController - { - #region Global Scope - private readonly ICreateStudent _createStudent; - private readonly IUpdateStudent _updateStudent; - private readonly IDeleteStudent _deleteStudent; - private readonly IGetStudents _getStudents; - private readonly IGetStudentById _getStudentById; - private readonly IMapper _mapper; - - public StudentPresenterController(ICreateStudent createStudent, IUpdateStudent updateStudent, IDeleteStudent deleteStudent, IGetStudents getStudents, IGetStudentById getStudentById, IMapper mapper) - { - _createStudent = createStudent; - _updateStudent = updateStudent; - _deleteStudent = deleteStudent; - _getStudents = getStudents; - _getStudentById = getStudentById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateStudentRequest; - var input = _mapper.Map(dto); - var result = await _createStudent.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteStudent.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getStudents.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getStudentById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateStudentRequest; - var input = _mapper.Map(dto); - var result = await _updateStudent.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/SubAreaPresenterController.cs b/src/Adapters/PresenterController/SubAreaPresenterController.cs deleted file mode 100755 index f6d9f52f..00000000 --- a/src/Adapters/PresenterController/SubAreaPresenterController.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.SubArea; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class SubAreaPresenterController : ISubAreaPresenterController - { - #region Global Scope - private readonly ICreateSubArea _createSubArea; - private readonly IUpdateSubArea _updateSubArea; - private readonly IDeleteSubArea _deleteSubArea; - private readonly IGetSubAreasByArea _getSubAreasByArea; - private readonly IGetSubAreaById _getSubAreaById; - private readonly IMapper _mapper; - - public SubAreaPresenterController(ICreateSubArea createSubArea, IUpdateSubArea updateSubArea, IDeleteSubArea deleteSubArea, - IGetSubAreasByArea getSubAreasByArea, IGetSubAreaById getSubAreaById, IMapper mapper) - { - _createSubArea = createSubArea; - _updateSubArea = updateSubArea; - _deleteSubArea = deleteSubArea; - _getSubAreasByArea = getSubAreasByArea; - _getSubAreaById = getSubAreaById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateSubAreaRequest; - var input = _mapper.Map(dto); - var result = await _createSubArea.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteSubArea.Execute(id); - return _mapper.Map(result); - } - - public Task> GetAll(int skip, int take) - { - throw new NotImplementedException(); - } - - public async Task GetById(Guid? id) - { - var result = await _getSubAreaById.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetSubAreasByArea(Guid? areaId, int skip, int take) - { - var result = await _getSubAreasByArea.Execute(areaId, skip, take); - return _mapper.Map>(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateSubAreaRequest; - var input = _mapper.Map(dto); - var result = await _updateSubArea.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/TypeAssistancePresenterController.cs b/src/Adapters/PresenterController/TypeAssistancePresenterController.cs deleted file mode 100755 index a76f0ae9..00000000 --- a/src/Adapters/PresenterController/TypeAssistancePresenterController.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Adapters.Gateways.Base; -using Adapters.Gateways.TypeAssistance; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class TypeAssistancePresenterController : ITypeAssistancePresenterController - { - #region Global Scope - private readonly ICreateTypeAssistance _createTypeAssistance; - private readonly IUpdateTypeAssistance _updateTypeAssistance; - private readonly IDeleteTypeAssistance _deleteTypeAssistance; - private readonly IGetTypeAssistances _getTypeAssistances; - private readonly IGetTypeAssistanceById _getTypeAssistanceById; - private readonly IMapper _mapper; - - public TypeAssistancePresenterController(ICreateTypeAssistance createTypeAssistance, - IUpdateTypeAssistance updateTypeAssistance, - IDeleteTypeAssistance deleteTypeAssistance, - IGetTypeAssistances getTypeAssistances, - IGetTypeAssistanceById getTypeAssistanceById, - IMapper mapper) - { - _createTypeAssistance = createTypeAssistance; - _updateTypeAssistance = updateTypeAssistance; - _deleteTypeAssistance = deleteTypeAssistance; - _getTypeAssistances = getTypeAssistances; - _getTypeAssistanceById = getTypeAssistanceById; - _mapper = mapper; - } - #endregion - - public async Task Create(IRequest request) - { - var dto = request as CreateTypeAssistanceRequest; - var input = _mapper.Map(dto); - var result = await _createTypeAssistance.Execute(input); - return _mapper.Map(result); - } - - public async Task Delete(Guid? id) - { - var result = await _deleteTypeAssistance.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetAll(int skip, int take) - { - var result = await _getTypeAssistances.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetById(Guid? id) - { - var result = await _getTypeAssistanceById.Execute(id); - return _mapper.Map(result); - } - - public async Task Update(Guid? id, IRequest request) - { - var dto = request as UpdateTypeAssistanceRequest; - var input = _mapper.Map(dto); - var result = await _updateTypeAssistance.Execute(id, input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Adapters/PresenterController/UserPresenterController.cs b/src/Adapters/PresenterController/UserPresenterController.cs deleted file mode 100755 index 52fd0321..00000000 --- a/src/Adapters/PresenterController/UserPresenterController.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Adapters.Gateways.User; -using Adapters.Interfaces; -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.UseCases; - -namespace Adapters.PresenterController -{ - public class UserPresenterController : IUserPresenterController - { - #region Global Scope - private readonly IActivateUser _activateUser; - private readonly IDeactivateUser _deactivateUser; - private readonly IGetActiveUsers _getActiveUsers; - private readonly IGetInactiveUsers _getInactiveUsers; - private readonly IGetUserById _getUserById; - private readonly IUpdateUser _updateUser; - private readonly IMapper _mapper; - public UserPresenterController(IActivateUser activateUser, - IDeactivateUser deactivateUser, - IGetActiveUsers getActiveUsers, - IGetInactiveUsers getInactiveUsers, - IGetUserById getUserById, - IUpdateUser updateUser, - IMapper mapper) - { - _activateUser = activateUser; - _deactivateUser = deactivateUser; - _getActiveUsers = getActiveUsers; - _getInactiveUsers = getInactiveUsers; - _getUserById = getUserById; - _updateUser = updateUser; - _mapper = mapper; - } - #endregion - - public async Task ActivateUser(Guid? id) - { - var result = await _activateUser.Execute(id); - return _mapper.Map(result); - } - - public async Task DeactivateUser(Guid? id) - { - var result = await _deactivateUser.Execute(id); - return _mapper.Map(result); - } - - public async Task> GetActiveUsers(int skip, int take) - { - var result = await _getActiveUsers.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task> GetInactiveUsers(int skip, int take) - { - var result = await _getInactiveUsers.Execute(skip, take); - return _mapper.Map>(result); - } - - public async Task GetUserById(Guid? id) - { - var result = await _getUserById.Execute(id); - return _mapper.Map(result); - } - - public async Task UpdateUser(UserUpdateRequest request) - { - var input = _mapper.Map(request); - var result = await _updateUser.Execute(input); - return _mapper.Map(result); - } - } -} \ No newline at end of file diff --git a/src/Application.Tests/Application.Tests.csproj b/src/Application.Tests/Application.Tests.csproj new file mode 100644 index 00000000..27c75bb2 --- /dev/null +++ b/src/Application.Tests/Application.Tests.csproj @@ -0,0 +1,50 @@ + + + + net7.0 + enable + enable + false + true + 0.1.0 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ActivityTypeMock.cs b/src/Application.Tests/Mocks/ActivityTypeMock.cs new file mode 100644 index 00000000..f74a20ec --- /dev/null +++ b/src/Application.Tests/Mocks/ActivityTypeMock.cs @@ -0,0 +1,12 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class ActivityTypeMock + { + public static ActivityType MockValidActivityType() + { + return new ActivityType("Activity Type Name", "Activity Type Description", Guid.NewGuid()); + } + } +} diff --git a/src/Application.Tests/Mocks/CampusMock.cs b/src/Application.Tests/Mocks/CampusMock.cs new file mode 100644 index 00000000..3cb91a56 --- /dev/null +++ b/src/Application.Tests/Mocks/CampusMock.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class CampusMock + { + public static Campus MockValidCampus() => new("Campus Name"); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ClaimsMock.cs b/src/Application.Tests/Mocks/ClaimsMock.cs new file mode 100644 index 00000000..c70bc288 --- /dev/null +++ b/src/Application.Tests/Mocks/ClaimsMock.cs @@ -0,0 +1,7 @@ +namespace Application.Tests.Mocks +{ + public static class ClaimsMock + { + public static Dictionary MockValidClaims() => new() { { Guid.NewGuid(), UserMock.MockValidUser() } }; + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/CourseMock.cs b/src/Application.Tests/Mocks/CourseMock.cs new file mode 100644 index 00000000..fbd22ec9 --- /dev/null +++ b/src/Application.Tests/Mocks/CourseMock.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class CourseMock + { + public static Course MockValidCourse() => new("Course Name"); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/FileMock.cs b/src/Application.Tests/Mocks/FileMock.cs new file mode 100644 index 00000000..2b7d2145 --- /dev/null +++ b/src/Application.Tests/Mocks/FileMock.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; +using Moq; + +namespace Application.Tests.Mocks +{ + public static class FileMock + { + public static IFormFile CreateIFormFile() + { + // Create a mock IFormFile, you can adjust the implementation as needed. + var fileMock = new Mock(); + fileMock.Setup(file => file.FileName).Returns("file.txt"); + fileMock.Setup(file => file.Length).Returns(1024); + // Add other setup as needed. + return fileMock.Object; + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/NoticeMock.cs b/src/Application.Tests/Mocks/NoticeMock.cs new file mode 100644 index 00000000..40ca8f73 --- /dev/null +++ b/src/Application.Tests/Mocks/NoticeMock.cs @@ -0,0 +1,38 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class NoticeMock + { + public static Notice MockValidNotice() => new( + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); + + public static Notice MockValidNoticeWithId() => new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProgramTypeMock.cs b/src/Application.Tests/Mocks/ProgramTypeMock.cs new file mode 100644 index 00000000..f46d4159 --- /dev/null +++ b/src/Application.Tests/Mocks/ProgramTypeMock.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class ProgramTypeMock + { + public static ProgramType MockValidProgramType() => new("Program Name", "Program Description"); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProjectActivityMock.cs b/src/Application.Tests/Mocks/ProjectActivityMock.cs new file mode 100644 index 00000000..2ea34fee --- /dev/null +++ b/src/Application.Tests/Mocks/ProjectActivityMock.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class ProjectActivityMock + { + public static ProjectActivity MockValidProjectActivity() => new(Guid.NewGuid(), Guid.NewGuid(), 5, 3); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProjectEvaluationMock.cs b/src/Application.Tests/Mocks/ProjectEvaluationMock.cs new file mode 100644 index 00000000..370b4dad --- /dev/null +++ b/src/Application.Tests/Mocks/ProjectEvaluationMock.cs @@ -0,0 +1,23 @@ +using Domain.Entities; +using Domain.Entities.Enums; + +namespace Application.Tests.Mocks +{ + public static class ProjectEvaluationMock + { + public static ProjectEvaluation MockValidProjectEvaluation() => + new( + Guid.NewGuid(), + true, + Guid.NewGuid(), + EProjectStatus.Accepted, + DateTime.UtcNow, + "Valid Submission Evaluation Description", + EQualification.Doctor, + EScore.Excellent, + EScore.Excellent, + EScore.Excellent, + EScore.Excellent, + 10.0); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProjectFinalReportMock.cs b/src/Application.Tests/Mocks/ProjectFinalReportMock.cs new file mode 100644 index 00000000..fff03133 --- /dev/null +++ b/src/Application.Tests/Mocks/ProjectFinalReportMock.cs @@ -0,0 +1,25 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class ProjectFinalReportMock + { + public static ProjectFinalReport MockValidProjectFinalReport() + { + return new ProjectFinalReport(Guid.NewGuid(), Guid.NewGuid()) + { + ReportUrl = "https://example.com/report", + SendDate = DateTime.UtcNow + }; + } + + public static ProjectFinalReport MockValidProjectFinalReportWithId() + { + return new ProjectFinalReport(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()) + { + ReportUrl = "https://example.com/report", + SendDate = DateTime.UtcNow + }; + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProjectMock.cs b/src/Application.Tests/Mocks/ProjectMock.cs new file mode 100644 index 00000000..69dfdc7e --- /dev/null +++ b/src/Application.Tests/Mocks/ProjectMock.cs @@ -0,0 +1,105 @@ +using Domain.Entities; +using Domain.Entities.Enums; + +namespace Application.Tests.Mocks +{ + public static class ProjectMock + { + public static Project MockValidProject() => new( + "Project Title", + "Keyword 1", + "Keyword 2", + "Keyword 3", + true, + "Objective", + "Methodology", + "Expected Results", + "Schedule", + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + EProjectStatus.Opened, + "Status Description", + "Appeal Observation", + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + "Cancellation Reason"); + + public static Project MockValidProjectWithId() => new( + Guid.NewGuid(), + "Project Title", + "Keyword 1", + "Keyword 2", + "Keyword 3", + true, + "Objective", + "Methodology", + "Expected Results", + "Schedule", + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + EProjectStatus.Opened, + "Status Description", + "Appeal Observation", + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + "Cancellation Reason"); + + public static Project MockValidProjectProfessorAndNotice() + { + return new( + Guid.NewGuid(), + "Project Title", + "Keyword 1", + "Keyword 2", + "Keyword 3", + true, + "Objective", + "Methodology", + "Expected Results", + "Schedule", + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + EProjectStatus.Opened, + "Status Description", + "Appeal Observation", + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + "Cancellation Reason") + { + SubArea = new SubArea(Guid.NewGuid(), "SubArea Name", "SubArea Code"), + ProgramType = new ProgramType("Program Type Name", "Program Type Description"), + Professor = new Professor("1234567", 1234567) + { + User = new User("Name", "professor@email.com", "Password", "58411338029", ERole.ADMIN) + }, + Notice = new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ) + }; + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/ProjectPartialReportMock.cs b/src/Application.Tests/Mocks/ProjectPartialReportMock.cs new file mode 100644 index 00000000..8f98e8e8 --- /dev/null +++ b/src/Application.Tests/Mocks/ProjectPartialReportMock.cs @@ -0,0 +1,17 @@ +using Domain.Entities.Enums; + +namespace Application.Tests.Mocks +{ + public class ProjectPartialReportMock + { + public static Domain.Entities.ProjectPartialReport MockValidProjectPartialReport() + { + return new Domain.Entities.ProjectPartialReport( + projectId: Guid.NewGuid(), + currentDevelopmentStage: 1, + scholarPerformance: EScholarPerformance.Good, + additionalInfo: "Additional Information", + userId: Guid.NewGuid()); + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/StudentMock.cs b/src/Application.Tests/Mocks/StudentMock.cs new file mode 100644 index 00000000..e9611b86 --- /dev/null +++ b/src/Application.Tests/Mocks/StudentMock.cs @@ -0,0 +1,52 @@ +using Domain.Entities.Enums; + +namespace Application.Tests.Mocks +{ + public static class StudentMock + { + public static Domain.Entities.Student MockValidStudent() => new( + birthDate: new DateTime(2000, 1, 1), + rg: 123456789, + issuingAgency: "Agency", + dispatchDate: new DateTime(2020, 1, 1), + gender: EGender.M, + race: ERace.White, + homeAddress: "Street 1", + city: "City", + uf: "UF", + cep: 12345678, + phoneDDD: 11, + phone: 12345678, + cellPhoneDDD: 22, + cellPhone: 987654321, + campusId: Guid.NewGuid(), + courseId: Guid.NewGuid(), + startYear: "2022", + studentAssistanceProgramId: Guid.NewGuid(), + registrationCode: "GCOM1234567" + ); + + public static Domain.Entities.Student MockValidStudentWithId() => new( + id: Guid.NewGuid(), + birthDate: new DateTime(2000, 1, 1), + rg: 123456789, + issuingAgency: "Agency", + dispatchDate: new DateTime(2020, 1, 1), + gender: EGender.M, + race: ERace.White, + homeAddress: "Street 1", + city: "City", + uf: "UF", + cep: 12345678, + phoneDDD: 11, + phone: 12345678, + cellPhoneDDD: 22, + cellPhone: 987654321, + campusId: Guid.NewGuid(), + courseId: Guid.NewGuid(), + startYear: "2022", + studentAssistanceProgramId: Guid.NewGuid(), + registrationCode: "GCOM1234567" + ); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/SubAreaMock.cs b/src/Application.Tests/Mocks/SubAreaMock.cs new file mode 100644 index 00000000..9a723876 --- /dev/null +++ b/src/Application.Tests/Mocks/SubAreaMock.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Application.Tests.Mocks +{ + public static class SubAreaMock + { + public static SubArea MockValidSubArea() => new(Guid.NewGuid(), "ABC", "SubArea Name"); + } +} \ No newline at end of file diff --git a/src/Application.Tests/Mocks/UserMock.cs b/src/Application.Tests/Mocks/UserMock.cs new file mode 100644 index 00000000..069f5ec9 --- /dev/null +++ b/src/Application.Tests/Mocks/UserMock.cs @@ -0,0 +1,10 @@ +using Domain.Entities.Enums; + +namespace Application.Tests.Mocks +{ + public static class UserMock + { + public static Domain.Entities.User MockValidUser() => new(Guid.NewGuid(), "John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + + } +} \ No newline at end of file diff --git a/src/Application.Tests/Samples/sample.pdf b/src/Application.Tests/Samples/sample.pdf new file mode 100644 index 00000000..334df3a1 Binary files /dev/null and b/src/Application.Tests/Samples/sample.pdf differ diff --git a/src/Application.Tests/UseCases/ActivityType/GetActivitiesByNoticeIdTests.cs b/src/Application.Tests/UseCases/ActivityType/GetActivitiesByNoticeIdTests.cs new file mode 100644 index 00000000..bb7b3a6e --- /dev/null +++ b/src/Application.Tests/UseCases/ActivityType/GetActivitiesByNoticeIdTests.cs @@ -0,0 +1,61 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Activity; +using Application.Interfaces.UseCases.ActivityType; +using Application.UseCases.ActivityType; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ActivityType +{ + public class GetActivitiesByNoticeIdTests + { + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetActivitiesByNoticeId CreateUseCase() => + new GetActivitiesByNoticeId(_activityTypeRepositoryMock.Object, _mapperMock.Object); + + private static Domain.Entities.ActivityType MockValidActivityType() => new("Type 1", "Unity 1", Guid.NewGuid()); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsListOfActivityTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var noticeId = Guid.NewGuid(); + var activityTypes = new List + { + MockValidActivityType() + }; + + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(noticeId)).ReturnsAsync(activityTypes); + _mapperMock.Setup(mapper => mapper.Map>(activityTypes)).Returns(new List()); + + // Act + var result = await useCase.ExecuteAsync(noticeId); + + // Assert + Assert.NotNull(result); + _activityTypeRepositoryMock.Verify(repo => repo.GetByNoticeIdAsync(noticeId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(activityTypes), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidId_ReturnsEmptyList() + { + // Arrange + var useCase = CreateUseCase(); + var invalidNoticeId = Guid.Empty; + + // Act + var result = await useCase.ExecuteAsync(invalidNoticeId); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + _activityTypeRepositoryMock.Verify(repo => repo.GetByNoticeIdAsync(invalidNoticeId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ActivityType/GetLastNoticeActivitiesTests.cs b/src/Application.Tests/UseCases/ActivityType/GetLastNoticeActivitiesTests.cs new file mode 100644 index 00000000..759252c4 --- /dev/null +++ b/src/Application.Tests/UseCases/ActivityType/GetLastNoticeActivitiesTests.cs @@ -0,0 +1,62 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Activity; +using Application.Interfaces.UseCases.ActivityType; +using Application.UseCases.ActivityType; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ActivityType +{ + public class GetLastNoticeActivitiesTests + { + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetLastNoticeActivities CreateUseCase() => + new GetLastNoticeActivities(_activityTypeRepositoryMock.Object, _mapperMock.Object); + + private static Domain.Entities.ActivityType MockValidActivityType() => new("Type 1", "Unity 1", Guid.NewGuid()); + + [Fact] + public async Task ExecuteAsync_ValidData_ReturnsListOfActivityTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var activityTypes = new List + { + MockValidActivityType() + }; + + _activityTypeRepositoryMock.Setup(repo => repo.GetLastNoticeActivitiesAsync()).ReturnsAsync(activityTypes); + _mapperMock.Setup(mapper => mapper.Map>(activityTypes)).Returns(new List()); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.NotNull(result); + _activityTypeRepositoryMock.Verify(repo => repo.GetLastNoticeActivitiesAsync(), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(activityTypes), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NoData_ReturnsEmptyList() + { + // Arrange + var useCase = CreateUseCase(); + List activityTypes = null; + + _activityTypeRepositoryMock.Setup(repo => repo.GetLastNoticeActivitiesAsync()).ReturnsAsync(activityTypes); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + _activityTypeRepositoryMock.Verify(repo => repo.GetLastNoticeActivitiesAsync(), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Area/CreateAreaTests.cs b/src/Application.Tests/UseCases/Area/CreateAreaTests.cs new file mode 100644 index 00000000..acf523f2 --- /dev/null +++ b/src/Application.Tests/UseCases/Area/CreateAreaTests.cs @@ -0,0 +1,133 @@ +using AutoMapper; +using Application.Ports.Area; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Validation; +using Moq; +using NUnit.Framework; +using Application.UseCases.Area; + +namespace Application.Tests.UseCases.Area +{ + [TestFixture] + public class CreateAreaTests + { + private ICreateArea _createArea; + private Mock _areaRepositoryMock; + private Mock _mainAreaRepositoryMock; + private Mock _mapperMock; + + public static Domain.Entities.Area MockValidArea() => new(Guid.NewGuid(), "ABC", "Area Name"); + private static Domain.Entities.MainArea MockValidMainArea() => new(Guid.NewGuid(), "ABC", "Main Area Name"); + + [SetUp] + public void Setup() + { + _areaRepositoryMock = new Mock(); + _mainAreaRepositoryMock = new Mock(); + _mapperMock = new Mock(); + + _createArea = new CreateArea( + _areaRepositoryMock.Object, + _mainAreaRepositoryMock.Object, + _mapperMock.Object + ); + } + + [Test] + public async Task Execute_WithValidInput_ShouldCreateArea() + { + // Arrange + var input = new CreateAreaInput + { + Name = "Area Name", + Code = "areaCode0", + MainAreaId = Guid.NewGuid(), + }; + + var areaEntity = MockValidArea(); + var detailedOutput = new DetailedReadAreaOutput(); + + _areaRepositoryMock.Setup(r => r.GetByCodeAsync(input.Code)).ReturnsAsync((Domain.Entities.Area)null); + _mainAreaRepositoryMock.Setup(r => r.GetByIdAsync(input.MainAreaId)).ReturnsAsync(MockValidMainArea()); + _areaRepositoryMock.Setup(r => r.CreateAsync(It.IsAny())).ReturnsAsync(areaEntity); + _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); + + // Act + var result = await _createArea.ExecuteAsync(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(detailedOutput, result); + _areaRepositoryMock.Verify(r => r.GetByCodeAsync(input.Code), Times.Once); + _mainAreaRepositoryMock.Verify(r => r.GetByIdAsync(input.MainAreaId), Times.Once); + _areaRepositoryMock.Verify(r => r.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); + } + + [Test] + public void Execute_WithExistingCode_ShouldThrowException() + { + // Arrange + var input = new CreateAreaInput + { + Name = "Area Name", + Code = "existingCode2", + MainAreaId = Guid.NewGuid(), + }; + + var existingArea = MockValidArea(); + + _areaRepositoryMock.Setup(r => r.GetByCodeAsync(input.Code)).ReturnsAsync(existingArea); + + // Act & Assert + Assert.ThrowsAsync(async () => await _createArea.ExecuteAsync(input)); + _areaRepositoryMock.Verify(r => r.GetByCodeAsync(input.Code), Times.Once); + _mainAreaRepositoryMock.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); + _areaRepositoryMock.Verify(r => r.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + + [Test] + public void Execute_WithMissingMainAreaId_ShouldThrowException() + { + // Arrange + var input = new CreateAreaInput + { + Name = "Area Name", + Code = "areaCode1", + MainAreaId = null, + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await _createArea.ExecuteAsync(input)); + _areaRepositoryMock.Verify(r => r.GetByCodeAsync(It.IsAny()), Times.Once); + _mainAreaRepositoryMock.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); + _areaRepositoryMock.Verify(r => r.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + + [Test] + public void Execute_WithInactiveMainArea_ShouldThrowException() + { + // Arrange + var input = new CreateAreaInput + { + Name = "Area Name", + Code = "areaCode2", + MainAreaId = Guid.NewGuid(), + }; + + var inactiveMainArea = MockValidMainArea(); + inactiveMainArea.DeactivateEntity(); + _mainAreaRepositoryMock.Setup(r => r.GetByIdAsync(input.MainAreaId)).ReturnsAsync(inactiveMainArea); + + // Act & Assert + Assert.ThrowsAsync(async () => await _createArea.ExecuteAsync(input)); + _areaRepositoryMock.Verify(r => r.GetByCodeAsync(input.Code), Times.Once); + _mainAreaRepositoryMock.Verify(r => r.GetByIdAsync(input.MainAreaId), Times.Once); + _areaRepositoryMock.Verify(r => r.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Domain.Tests/UseCases/Area/DeleteAreaTests.cs b/src/Application.Tests/UseCases/Area/DeleteAreaTests.cs similarity index 76% rename from src/Domain.Tests/UseCases/Area/DeleteAreaTests.cs rename to src/Application.Tests/UseCases/Area/DeleteAreaTests.cs index 934940d0..75631a43 100644 --- a/src/Domain.Tests/UseCases/Area/DeleteAreaTests.cs +++ b/src/Application.Tests/UseCases/Area/DeleteAreaTests.cs @@ -1,15 +1,13 @@ using AutoMapper; -using Domain.Contracts.Area; +using Application.Ports.Area; +using Application.UseCases.Area; +using Application.Interfaces.UseCases.Area; +using Application.Validation; using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; -using Domain.UseCases; -using Domain.Validation; using Moq; using NUnit.Framework; -using System; -using System.Threading.Tasks; -namespace Domain.Tests.UseCases.Area +namespace Application.Tests.UseCases.Area { [TestFixture] public class DeleteAreaTests @@ -37,7 +35,7 @@ public void Setup() _deletedArea = new Domain.Entities.Area(_areaId, "ABC", "Area Name"); _mappedOutput = new DetailedReadAreaOutput(); - _areaRepositoryMock.Setup(r => r.Delete(_areaId)).ReturnsAsync(_deletedArea); + _areaRepositoryMock.Setup(r => r.DeleteAsync(_areaId)).ReturnsAsync(_deletedArea); _mapperMock.Setup(m => m.Map(_deletedArea)).Returns(_mappedOutput); } @@ -48,11 +46,11 @@ public async Task Execute_WithValidId_ShouldDeleteAreaAndReturnOutput() Guid? id = _areaId; // Act - var result = await _deleteArea.Execute(id); + var result = await _deleteArea.ExecuteAsync(id); // Assert Assert.AreEqual(_mappedOutput, result); - _areaRepositoryMock.Verify(r => r.Delete(_areaId), Times.Once); + _areaRepositoryMock.Verify(r => r.DeleteAsync(_areaId), Times.Once); _mapperMock.Verify(m => m.Map(_deletedArea), Times.Once); } @@ -63,8 +61,8 @@ public void Execute_WithNullId_ShouldThrowArgumentNullException() Guid? id = null; // Act & Assert - Assert.ThrowsAsync(async () => await _deleteArea.Execute(id)); - _areaRepositoryMock.Verify(r => r.Delete(It.IsAny()), Times.Never); + Assert.ThrowsAsync(async () => await _deleteArea.ExecuteAsync(id)); + _areaRepositoryMock.Verify(r => r.DeleteAsync(It.IsAny()), Times.Never); _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); } } diff --git a/src/Application.Tests/UseCases/Area/GetAreaByIdTests.cs b/src/Application.Tests/UseCases/Area/GetAreaByIdTests.cs new file mode 100644 index 00000000..2a98bcd6 --- /dev/null +++ b/src/Application.Tests/UseCases/Area/GetAreaByIdTests.cs @@ -0,0 +1,66 @@ +using AutoMapper; +using Application.Ports.Area; +using Domain.Interfaces.Repositories; +using Application.Validation; +using Moq; +using NUnit.Framework; +using Application.Interfaces.UseCases.Area; +using Application.UseCases.Area; + +namespace Application.Tests.UseCases.Area +{ + [TestFixture] + public class GetAreaByIdTests + { + private IGetAreaById _getAreaById; + private Mock _areaRepositoryMock; + private Mock _mapperMock; + + public static Domain.Entities.Area MockValidArea() => new(Guid.NewGuid(), "ABC", "Area Name"); + + [SetUp] + public void Setup() + { + _areaRepositoryMock = new Mock(); + _mapperMock = new Mock(); + + _getAreaById = new GetAreaById( + _areaRepositoryMock.Object, + _mapperMock.Object + ); + } + + [Test] + public async Task Execute_WithValidId_ShouldReturnDetailedReadAreaOutput() + { + // Arrange + var id = Guid.NewGuid(); + var areaEntity = MockValidArea(); + var detailedOutput = new DetailedReadAreaOutput(); + + _areaRepositoryMock.Setup(r => r.GetByIdAsync(id)).ReturnsAsync(areaEntity); + _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); + + // Act + var result = await _getAreaById.ExecuteAsync(id); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(detailedOutput, result); + _areaRepositoryMock.Verify(r => r.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); + } + + [Test] + public void Execute_WithNullId_ShouldThrowException() + { + // Arrange + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await _getAreaById.ExecuteAsync(id)); + _areaRepositoryMock.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Area/UpdateAreaTests.cs b/src/Application.Tests/UseCases/Area/UpdateAreaTests.cs new file mode 100644 index 00000000..f2e4def2 --- /dev/null +++ b/src/Application.Tests/UseCases/Area/UpdateAreaTests.cs @@ -0,0 +1,102 @@ +using AutoMapper; +using Application.Ports.Area; +using Domain.Interfaces.Repositories; +using Application.Validation; +using Moq; +using NUnit.Framework; +using Application.Interfaces.UseCases.Area; +using Application.UseCases.Area; + +namespace Application.Tests.UseCases.Area +{ + [TestFixture] + public class UpdateAreaTests + { + private IUpdateArea _updateArea; + private Mock _areaRepositoryMock; + private Mock _mapperMock; + + public static Domain.Entities.Area MockValidArea() => new(Guid.NewGuid(), "ABC", "Area Name"); + + [SetUp] + public void Setup() + { + _areaRepositoryMock = new Mock(); + _mapperMock = new Mock(); + + _updateArea = new UpdateArea( + _areaRepositoryMock.Object, + _mapperMock.Object + ); + } + + [Test] + public async Task Execute_WithValidIdAndInput_ShouldReturnDetailedReadAreaOutput() + { + // Arrange + var id = Guid.NewGuid(); + var input = new UpdateAreaInput + { + Name = "New Area Name", + Code = "New Area Code", + MainAreaId = Guid.NewGuid() + }; + var areaEntity = MockValidArea(); + var detailedOutput = new DetailedReadAreaOutput(); + + _areaRepositoryMock.Setup(r => r.GetByIdAsync(id)).ReturnsAsync(areaEntity); + _areaRepositoryMock.Setup(r => r.UpdateAsync(It.IsAny())).ReturnsAsync(areaEntity); + _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); + + // Act + var result = await _updateArea.ExecuteAsync(id, input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(detailedOutput, result); + _areaRepositoryMock.Verify(r => r.GetByIdAsync(id), Times.Once); + _areaRepositoryMock.Verify(r => r.UpdateAsync(areaEntity), Times.Once); + _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); + } + + [Test] + public void Execute_WithNullId_ShouldThrowException() + { + // Arrange + Guid? id = null; + var input = new UpdateAreaInput + { + Name = "New Area Name", + Code = "New Area Code", + MainAreaId = Guid.NewGuid() + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await _updateArea.ExecuteAsync(id, input)); + _areaRepositoryMock.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); + _areaRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + + [Test] + public void Execute_WithNonExistingId_ShouldThrowException() + { + // Arrange + var id = Guid.NewGuid(); + var input = new UpdateAreaInput + { + Name = "New Area Name", + Code = "New Area Code", + MainAreaId = Guid.NewGuid() + }; + + _areaRepositoryMock.Setup(r => r.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.Area)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await _updateArea.ExecuteAsync(id, input)); + _areaRepositoryMock.Verify(r => r.GetByIdAsync(id), Times.Once); + _areaRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/UseCases/AssistanceType/CreateAssistanceTypeTests.cs b/src/Application.Tests/UseCases/AssistanceType/CreateAssistanceTypeTests.cs new file mode 100644 index 00000000..136b9772 --- /dev/null +++ b/src/Application.Tests/UseCases/AssistanceType/CreateAssistanceTypeTests.cs @@ -0,0 +1,84 @@ +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.UseCases.AssistanceType; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.AssistanceType +{ + public class CreateAssistanceTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateAssistanceType CreateUseCase() => new CreateAssistanceType(_repositoryMock.Object, _mapperMock.Object); + + private Domain.Entities.AssistanceType MockValidAssistanceType() => new("AssistanceTypeName", "AssistanceTypeDescription"); + private CreateAssistanceTypeInput MockValidAssistanceTypeInput() => new() + { + Name = "AssistanceTypeName", + Description = "AssistanceTypeDescription" + }; + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadAssistanceTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = MockValidAssistanceTypeInput(); + + _repositoryMock.Setup(repo => repo.GetAssistanceTypeByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.AssistanceType)null); + _mapperMock.Setup(mapper => mapper.Map(input)).Returns(MockValidAssistanceType()); + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(MockValidAssistanceType()); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadAssistanceTypeOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(input.Name), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(input), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_NameIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateAssistanceTypeInput + { + Name = null + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AssistanceTypeWithNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = MockValidAssistanceTypeInput(); + + _repositoryMock.Setup(repo => repo.GetAssistanceTypeByNameAsync(input.Name)).ReturnsAsync(MockValidAssistanceType()); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(input.Name), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/AssistanceType/DeleteAssistanceTypeTests.cs b/src/Application.Tests/UseCases/AssistanceType/DeleteAssistanceTypeTests.cs new file mode 100644 index 00000000..46b4563d --- /dev/null +++ b/src/Application.Tests/UseCases/AssistanceType/DeleteAssistanceTypeTests.cs @@ -0,0 +1,52 @@ +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.UseCases.AssistanceType; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.AssistanceType +{ + public class DeleteAssistanceTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteAssistanceType CreateUseCase() => new DeleteAssistanceType(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadAssistanceTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var assistanceType = new Domain.Entities.AssistanceType(id, "AssistanceTypeName", "AssistanceTypeDescription"); + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(assistanceType); + _mapperMock.Setup(mapper => mapper.Map(assistanceType)).Returns(new DetailedReadAssistanceTypeOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(assistanceType), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypeByIdTests.cs b/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypeByIdTests.cs new file mode 100644 index 00000000..3a794f5d --- /dev/null +++ b/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypeByIdTests.cs @@ -0,0 +1,55 @@ +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.UseCases.AssistanceType; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.AssistanceType +{ + public class GetAssistanceTypeByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetAssistanceTypeById CreateUseCase() + { + return new GetAssistanceTypeById(_repositoryMock.Object, _mapperMock.Object); + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadAssistanceTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var assistanceType = new Domain.Entities.AssistanceType(id, "AssistanceTypeName", "AssistanceTypeDescription"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(assistanceType); + _mapperMock.Setup(mapper => mapper.Map(assistanceType)).Returns(new DetailedReadAssistanceTypeOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(assistanceType), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypesTests.cs b/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypesTests.cs new file mode 100644 index 00000000..b4adb951 --- /dev/null +++ b/src/Application.Tests/UseCases/AssistanceType/GetAssistanceTypesTests.cs @@ -0,0 +1,49 @@ +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.UseCases.AssistanceType; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.AssistanceType +{ + public class GetAssistanceTypesTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetAssistanceTypes CreateUseCase() + { + return new GetAssistanceTypes(_repositoryMock.Object, _mapperMock.Object); + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsQueryableOfResumedReadAssistanceTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var assistanceTypes = new List + { + new(Guid.NewGuid(), "AssistanceType1", "Description"), + new(Guid.NewGuid(), "AssistanceType2", "Description"), + new(Guid.NewGuid(), "AssistanceType3", "Description") + }; + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(assistanceTypes); + _mapperMock.Setup(mapper => mapper.Map>(assistanceTypes)).Returns(assistanceTypes.Select(at => new ResumedReadAssistanceTypeOutput { Id = at.Id, Name = at.Name })); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom>(result); + Assert.Equal(3, result.Count()); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(assistanceTypes), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/AssistanceType/UpdateAssistanceTypeTests.cs b/src/Application.Tests/UseCases/AssistanceType/UpdateAssistanceTypeTests.cs new file mode 100644 index 00000000..e84755e1 --- /dev/null +++ b/src/Application.Tests/UseCases/AssistanceType/UpdateAssistanceTypeTests.cs @@ -0,0 +1,131 @@ +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.UseCases.AssistanceType; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.AssistanceType +{ + public class UpdateAssistanceTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateAssistanceType CreateUseCase() + { + return new UpdateAssistanceType(_repositoryMock.Object, _mapperMock.Object); + } + + private Domain.Entities.AssistanceType MockValidAssistanceType() => new("AssistanceTypeName1", "AssistanceTypeDescription"); + private UpdateAssistanceTypeInput MockValidAssistanceTypeInput() => new() + { + Name = "AssistanceTypeName2", + Description = "AssistanceTypeDescription" + }; + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadAssistanceTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = MockValidAssistanceTypeInput(); + var existingAssistanceType = MockValidAssistanceType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingAssistanceType); + _repositoryMock.Setup(repo => repo.GetAssistanceTypeByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.AssistanceType)null); + _repositoryMock.Setup(repo => repo.UpdateAsync(existingAssistanceType)).ReturnsAsync(existingAssistanceType); + _mapperMock.Setup(mapper => mapper.Map(existingAssistanceType)).Returns(new DetailedReadAssistanceTypeOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(existingAssistanceType), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(existingAssistanceType), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = MockValidAssistanceTypeInput(); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NameIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateAssistanceTypeInput + { + Name = null + }; + var existingAssistanceType = MockValidAssistanceType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingAssistanceType); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Never); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AssistanceTypeWithNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = MockValidAssistanceTypeInput(); + var existingAssistanceType = MockValidAssistanceType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingAssistanceType); + _repositoryMock.Setup(repo => repo.GetAssistanceTypeByNameAsync(input.Name)).ReturnsAsync(MockValidAssistanceType()); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AssistanceTypeIsDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = MockValidAssistanceTypeInput(); + var existingAssistanceType = MockValidAssistanceType(); + existingAssistanceType.DeactivateEntity(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingAssistanceType); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetAssistanceTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Auth/ConfirmEmailTests.cs b/src/Application.Tests/UseCases/Auth/ConfirmEmailTests.cs new file mode 100644 index 00000000..8da642c4 --- /dev/null +++ b/src/Application.Tests/UseCases/Auth/ConfirmEmailTests.cs @@ -0,0 +1,119 @@ +using Application.Interfaces.UseCases.Auth; +using Application.UseCases.Auth; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Auth +{ + public class ConfirmEmailTests + { + private readonly Mock _userRepositoryMock = new(); + + private IConfirmEmail CreateUseCase() => new ConfirmEmail(_userRepositoryMock.Object); + + private static Domain.Entities.User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var email = user.Email; + var token = user.ValidationCode; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync(user); + + // Act + var result = await useCase.ExecuteAsync(email, token); + + // Assert + Assert.Equal("Usuário confirmado com sucesso.", result); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(user), Times.Once); + Assert.True(user.IsConfirmed); + } + + [Fact] + public void ExecuteAsync_EmailIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + string email = null; + var token = "validtoken"; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email, token)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_TokenIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var email = "test@example.com"; + string token = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email, token)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var email = "test@example.com"; + var token = "validtoken"; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email, token)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InvalidToken_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var email = user.Email; + var token = "invalidtoken"; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync(user); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email, token)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserAlreadyConfirmed_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var token = user.ValidationCode; + var email = user.Email; + user.ConfirmUserEmail(token); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync(user); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email, token)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Auth/ForgotPasswordTests.cs b/src/Application.Tests/UseCases/Auth/ForgotPasswordTests.cs new file mode 100644 index 00000000..9d265885 --- /dev/null +++ b/src/Application.Tests/UseCases/Auth/ForgotPasswordTests.cs @@ -0,0 +1,72 @@ +using Application.Interfaces.UseCases.Auth; +using Application.UseCases.Auth; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Auth +{ + public class ForgotPasswordTests + { + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + + private IForgotPassword CreateUseCase() => new ForgotPassword(_userRepositoryMock.Object, _emailServiceMock.Object); + + private static Domain.Entities.User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var email = user.Email; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync(user); + + // Act + var result = await useCase.ExecuteAsync(email); + + // Assert + Assert.Equal("Token de recuperação gerado e enviado por e-mail com sucesso.", result); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(user), Times.Once); + Assert.NotNull(user.ResetPasswordToken); // Verifica se o token foi gerado + _emailServiceMock.Verify(service => service.SendResetPasswordEmailAsync(user.Email, user.Name, user.ResetPasswordToken), Times.Once); + } + + [Fact] + public void ExecuteAsync_EmailIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + string email = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendResetPasswordEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var email = "test@example.com"; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendResetPasswordEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Auth/LoginTests.cs b/src/Application.Tests/UseCases/Auth/LoginTests.cs new file mode 100644 index 00000000..52eaf671 --- /dev/null +++ b/src/Application.Tests/UseCases/Auth/LoginTests.cs @@ -0,0 +1,161 @@ +using Application.Interfaces.UseCases.Auth; +using Application.Ports.Auth; +using Application.UseCases.Auth; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Auth +{ + public class LoginTests + { + private readonly Mock _tokenServiceMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _hashServiceMock = new(); + + private ILogin CreateUseCase() => new Login(_tokenServiceMock.Object, _userRepositoryMock.Object, _professorRepositoryMock.Object, _studentRepositoryMock.Object, _hashServiceMock.Object); + private static Domain.Entities.User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + private static Domain.Entities.User MockValidUserWithId() => new(Guid.NewGuid(), "John Doe", "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsUserLoginOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserLoginInput + { + Email = "test@example.com", + Password = "password" + }; + var user = MockValidUserWithId(); + user.Password = "hashed_password"; + user.Email = input.Email; + user.ConfirmUserEmail(user.ValidationCode); + user.Role = ERole.PROFESSOR; + var professor = new Domain.Entities.Professor(Guid.NewGuid(), "1234567", 1234567) + { + UserId = user.Id + }; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync(user); + _professorRepositoryMock.Setup(repo => repo.GetByUserIdAsync(user.Id)).ReturnsAsync(professor); + _hashServiceMock.Setup(hashService => hashService.VerifyPassword(input.Password, user.Password)).Returns(true); + _tokenServiceMock.Setup(tokenService => tokenService.GenerateToken(user.Id, professor.Id, user.Name, user.Role.ToString())).Returns("token"); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.Equal("token", result.Token); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(input.Email), Times.Once); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(input.Password, user.Password), Times.Once); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(user.Id, professor.Id, user.Name, user.Role.ToString()), Times.Once); + } + + [Fact] + public void ExecuteAsync_EmailIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserLoginInput + { + Email = null, + Password = "password" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(It.IsAny(), It.IsAny()), Times.Never); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_PasswordIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserLoginInput + { + Email = "test@example.com", + Password = null + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(It.IsAny(), It.IsAny()), Times.Never); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserLoginInput + { + Email = "test@example.com", + Password = "password" + }; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(input.Email), Times.Once); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(It.IsAny(), It.IsAny()), Times.Never); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotConfirmed_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var input = new UserLoginInput + { + Email = user.Email, + Password = user.Password + }; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync(user); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(input.Email), Times.Once); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(It.IsAny(), It.IsAny()), Times.Never); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InvalidPassword_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var input = new UserLoginInput + { + Email = user.Email, + Password = user.Password + }; + user.ConfirmUserEmail(user.ValidationCode); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync(user); + _hashServiceMock.Setup(hashService => hashService.VerifyPassword(input.Password, user.Password)).Returns(false); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(input.Email), Times.Once); + _hashServiceMock.Verify(hashService => hashService.VerifyPassword(input.Password, user.Password), Times.Once); + _tokenServiceMock.Verify(tokenService => tokenService.GenerateToken(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Auth/ResetPasswordTests.cs b/src/Application.Tests/UseCases/Auth/ResetPasswordTests.cs new file mode 100644 index 00000000..8a8fd521 --- /dev/null +++ b/src/Application.Tests/UseCases/Auth/ResetPasswordTests.cs @@ -0,0 +1,153 @@ +using Application.Interfaces.UseCases.Auth; +using Application.Ports.Auth; +using Application.UseCases.Auth; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Auth +{ + public class ResetPasswordTests + { + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _hashServiceMock = new(); + + private IResetPassword CreateUseCase() => new ResetPassword(_userRepositoryMock.Object, _hashServiceMock.Object); + private static Domain.Entities.User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + private static Domain.Entities.User MockValidUserWithId() => new(Guid.NewGuid(), "John Doe", "ADMIN"); + + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUserWithId(); + user.GenerateResetPasswordToken(); + user.Password = "hashed_password"; + var input = new UserResetPasswordInput + { + Id = user.Id, + Password = "new_password", + Token = user.ResetPasswordToken + }; + + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(input.Id.Value)).ReturnsAsync(user); + _hashServiceMock.Setup(hashService => hashService.HashPassword(input.Password)).Returns("hashed_password"); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.Equal("Senha atualizada com sucesso.", result); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(input.Id.Value), Times.Once); + _hashServiceMock.Verify(hashService => hashService.HashPassword("new_password"), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(user), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserResetPasswordInput + { + Id = null, + Password = "new_password", + Token = "reset_token" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_PasswordIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserResetPasswordInput + { + Id = Guid.NewGuid(), + Password = null, + Token = "reset_token" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_TokenIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserResetPasswordInput + { + Id = Guid.NewGuid(), + Password = "new_password", + Token = null + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserResetPasswordInput + { + Id = Guid.NewGuid(), + Password = "new_password", + Token = "reset_token" + }; + + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(input.Id.Value)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(input.Id.Value), Times.Once); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InvalidToken_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUserWithId(); + user.GenerateResetPasswordToken(); + var input = new UserResetPasswordInput + { + Id = user.Id, + Password = "new_password", + Token = "different_token" + }; + + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(input.Id.Value)).ReturnsAsync(user); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(input.Id.Value), Times.Once); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Campus/CreateCampusTests.cs b/src/Application.Tests/UseCases/Campus/CreateCampusTests.cs new file mode 100644 index 00000000..47fcb3d1 --- /dev/null +++ b/src/Application.Tests/UseCases/Campus/CreateCampusTests.cs @@ -0,0 +1,48 @@ +using Application.Ports.Campus; +using Application.UseCases.Campus; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Campus +{ + public class CreateCampusTests + { + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCampusOutput() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + + var useCase = new CreateCampus(repositoryMock.Object, mapperMock.Object); + + var input = new CreateCampusInput + { + Name = "Test Campus" + }; + + // Simule o retorno do repositório quando GetCampusByNameAsync é chamado + repositoryMock.Setup(repo => repo.GetCampusByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.Campus)null); + + // Simule o retorno do repositório quando CreateAsync é chamado + var createdCampus = new Domain.Entities.Campus(input.Name); + repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(createdCampus); + + // Simule o mapeamento do AutoMapper + var detailedReadCampusOutput = new DetailedReadCampusOutput(); // Você pode preencher com dados relevantes + mapperMock.Setup(mapper => mapper.Map(createdCampus)).Returns(detailedReadCampusOutput); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.Same(detailedReadCampusOutput, result); + repositoryMock.Verify(repo => repo.GetCampusByNameAsync(input.Name), Times.Once); + repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + mapperMock.Verify(mapper => mapper.Map(createdCampus), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Campus/DeleteCampusTests.cs b/src/Application.Tests/UseCases/Campus/DeleteCampusTests.cs new file mode 100644 index 00000000..051186bd --- /dev/null +++ b/src/Application.Tests/UseCases/Campus/DeleteCampusTests.cs @@ -0,0 +1,53 @@ +using Application.Ports.Campus; +using Application.UseCases.Campus; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Campus +{ + public class DeleteCampusTests + { + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCampusOutput() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + var useCase = new DeleteCampus(repositoryMock.Object, mapperMock.Object); + var campusId = Guid.NewGuid(); + + var deletedCampus = new Domain.Entities.Campus("Deleted Campus"); + repositoryMock.Setup(repo => repo.DeleteAsync(campusId)).ReturnsAsync(deletedCampus); + + var detailedReadCampusOutput = new DetailedReadCampusOutput(); + mapperMock.Setup(mapper => mapper.Map(deletedCampus)).Returns(detailedReadCampusOutput); + + // Act + var result = await useCase.ExecuteAsync(campusId); + + // Assert + Assert.NotNull(result); + Assert.Same(detailedReadCampusOutput, result); + repositoryMock.Verify(repo => repo.DeleteAsync(campusId), Times.Once); + mapperMock.Verify(mapper => mapper.Map(deletedCampus), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidInput_ThrowsUseCaseException() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + var useCase = new DeleteCampus(repositoryMock.Object, mapperMock.Object); + Guid? campusId = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(campusId)); + repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Campus/GetCampusByIdTests.cs b/src/Application.Tests/UseCases/Campus/GetCampusByIdTests.cs new file mode 100644 index 00000000..edc17e9b --- /dev/null +++ b/src/Application.Tests/UseCases/Campus/GetCampusByIdTests.cs @@ -0,0 +1,59 @@ +using Application.Ports.Campus; +using Application.UseCases.Campus; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Campus +{ + public class GetCampusByIdTests + { + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCampusOutput() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + + var useCase = new GetCampusById(repositoryMock.Object, mapperMock.Object); + + var campusId = Guid.NewGuid(); + + // Simule o retorno do repositório quando GetByIdAsync é chamado + var campus = new Domain.Entities.Campus("Campus Name"); + repositoryMock.Setup(repo => repo.GetByIdAsync(campusId)).ReturnsAsync(campus); + + // Simule o mapeamento do AutoMapper + var detailedReadCampusOutput = new DetailedReadCampusOutput(); // Você pode preencher com dados relevantes + mapperMock.Setup(mapper => mapper.Map(campus)).Returns(detailedReadCampusOutput); + + // Act + var result = await useCase.ExecuteAsync(campusId); + + // Assert + Assert.NotNull(result); + Assert.Same(detailedReadCampusOutput, result); + repositoryMock.Verify(repo => repo.GetByIdAsync(campusId), Times.Once); + mapperMock.Verify(mapper => mapper.Map(campus), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidInput_ThrowsUseCaseException() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + + var useCase = new GetCampusById(repositoryMock.Object, mapperMock.Object); + + Guid? campusId = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(campusId)); + repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Campus/GetCampusesTests.cs b/src/Application.Tests/UseCases/Campus/GetCampusesTests.cs new file mode 100644 index 00000000..4331e4b1 --- /dev/null +++ b/src/Application.Tests/UseCases/Campus/GetCampusesTests.cs @@ -0,0 +1,41 @@ +using Application.Ports.Campus; +using Application.UseCases.Campus; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Campus +{ + public class GetCampusesTests + { + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsQueryableOfResumedReadCampusOutput() + { + // Arrange + var repositoryMock = new Mock(); + var mapperMock = new Mock(); + var useCase = new GetCampuses(repositoryMock.Object, mapperMock.Object); + int skip = 0; + int take = 10; + var campuses = new List + { + new("Campus 1"), + new("Campus 2"), + new("Campus 3") + }; + repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(campuses); + var resumedReadCampusOutputs = campuses.Select(campus => new ResumedReadCampusOutput { Name = campus.Name }).ToList(); + mapperMock.Setup(mapper => mapper.Map>(campuses)).Returns(resumedReadCampusOutputs); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.Equal(campuses.Count, result.Count()); + repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + mapperMock.Verify(mapper => mapper.Map>(campuses), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Campus/UpdateCampusTests.cs b/src/Application.Tests/UseCases/Campus/UpdateCampusTests.cs new file mode 100644 index 00000000..dbfb3084 --- /dev/null +++ b/src/Application.Tests/UseCases/Campus/UpdateCampusTests.cs @@ -0,0 +1,134 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Campus; + +namespace Application.Tests.UseCases.Campus +{ + public class UpdateCampusTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateCampus CreateUseCase() => new UpdateCampus(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCampusOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCampusInput + { + Name = "Updated Campus Name" + }; + var existingCampus = new Domain.Entities.Campus("Initial Campus Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCampus); + _repositoryMock.Setup(repo => repo.GetCampusByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.Campus)null); + _repositoryMock.Setup(repo => repo.UpdateAsync(existingCampus)).ReturnsAsync(existingCampus); + _mapperMock.Setup(mapper => mapper.Map(existingCampus)).Returns(new DetailedReadCampusOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCampusByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(existingCampus), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(existingCampus), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UpdateCampusInput + { + Name = "Updated Campus Name" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.GetCampusByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NameIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCampusInput + { + Name = null + }; + var existingCampus = new Domain.Entities.Campus("Initial Campus Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCampus); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Never); + _repositoryMock.Verify(repo => repo.GetCampusByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CampusWithNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCampusInput + { + Name = "Existing Campus Name" + }; + var existingCampus = new Domain.Entities.Campus("Existing Campus Name"); + var anotherCampus = new Domain.Entities.Campus("Another Campus Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(anotherCampus); + _repositoryMock.Setup(repo => repo.GetCampusByNameAsync(input.Name)).ReturnsAsync(existingCampus); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCampusByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CampusIsDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCampusInput + { + Name = "Updated Campus Name" + }; + var existingCampus = new Domain.Entities.Campus("Initial Campus Name"); + existingCampus.DeactivateEntity(); // Marcando como excluído + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCampus); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCampusByNameAsync(input.Name), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Course/CreateCourseTests.cs b/src/Application.Tests/UseCases/Course/CreateCourseTests.cs new file mode 100644 index 00000000..1fb07f57 --- /dev/null +++ b/src/Application.Tests/UseCases/Course/CreateCourseTests.cs @@ -0,0 +1,80 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Course; +using Application.Interfaces.UseCases.Course; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Course; + +namespace Application.Tests.UseCases.Course +{ + public class CreateCourseTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateCourse CreateUseCase() => new CreateCourse(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCourseOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateCourseInput + { + Name = "New Course Name" + }; + Domain.Entities.Course createdCourse = new("New Course Name"); + + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(createdCourse); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadCourseOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_NameIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateCourseInput + { + Name = null + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CourseWithNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateCourseInput + { + Name = "Existing Course Name" + }; + var existingCourse = new Domain.Entities.Course("Existing Course Name"); + + _repositoryMock.Setup(repo => repo.GetCourseByNameAsync(input.Name)).ReturnsAsync(existingCourse); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Course/DeleteCourseTests.cs b/src/Application.Tests/UseCases/Course/DeleteCourseTests.cs new file mode 100644 index 00000000..3097dd12 --- /dev/null +++ b/src/Application.Tests/UseCases/Course/DeleteCourseTests.cs @@ -0,0 +1,50 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Course; +using Application.Interfaces.UseCases.Course; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Course; + +namespace Application.Tests.UseCases.Course +{ + public class DeleteCourseTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteCourse CreateUseCase() => new DeleteCourse(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCourseOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var deletedCourse = new Domain.Entities.Course("Deleted Course"); + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(deletedCourse); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadCourseOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Course/GetCourseByIdTests.cs b/src/Application.Tests/UseCases/Course/GetCourseByIdTests.cs new file mode 100644 index 00000000..fe14f177 --- /dev/null +++ b/src/Application.Tests/UseCases/Course/GetCourseByIdTests.cs @@ -0,0 +1,50 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Course; + +namespace Application.Tests.UseCases.Course +{ + public class GetCourseByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetCourseById CreateUseCase() => new GetCourseById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCourseOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var expectedCourse = new Domain.Entities.Course("Course Name"); + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(expectedCourse); + _mapperMock.Setup(mapper => mapper.Map(expectedCourse)).Returns(new DetailedReadCourseOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(expectedCourse), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Course/GetCoursesTests.cs b/src/Application.Tests/UseCases/Course/GetCoursesTests.cs new file mode 100644 index 00000000..a6c539dc --- /dev/null +++ b/src/Application.Tests/UseCases/Course/GetCoursesTests.cs @@ -0,0 +1,50 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.UseCases.Course; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Course +{ + public class GetCoursesTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetCourses CreateUseCase() => new GetCourses(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsQueryableResumedReadCourseOutput() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var expectedCourses = new List + { + new("Course 1"), + new("Course 2"), + new("Course 3") + }; + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(expectedCourses); + _mapperMock.Setup(mapper => mapper.Map>(expectedCourses)).Returns( + new List + { + new(), + new(), + new() + }); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count()); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(expectedCourses), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Course/UpdateCourseTests.cs b/src/Application.Tests/UseCases/Course/UpdateCourseTests.cs new file mode 100644 index 00000000..f77eecc6 --- /dev/null +++ b/src/Application.Tests/UseCases/Course/UpdateCourseTests.cs @@ -0,0 +1,155 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.UseCases.Course; +using Moq; +using Xunit; +using Application.Validation; + +namespace Application.Tests.UseCases.Course +{ + public class UpdateCourseTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateCourse CreateUseCase() => new UpdateCourse(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadCourseOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCourseInput + { + Name = "Updated Course Name" + }; + var existingCourse = new Domain.Entities.Course("Existing Course Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCourse); + _repositoryMock.Setup(repo => repo.GetCourseByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.Course)null); + _repositoryMock.Setup(repo => repo.UpdateAsync(existingCourse)).ReturnsAsync(existingCourse); + _mapperMock.Setup(mapper => mapper.Map(existingCourse)).Returns(new DetailedReadCourseOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(existingCourse), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(existingCourse), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UpdateCourseInput + { + Name = "Updated Course Name" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NameIsNullOrEmpty_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCourseInput + { + Name = null + }; + var existingCourse = new Domain.Entities.Course("Existing Course Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCourse); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Never); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CourseNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCourseInput + { + Name = "Updated Course Name" + }; + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.Course)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CourseIsDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCourseInput + { + Name = "Updated Course Name" + }; + var existingCourse = new Domain.Entities.Course("Existing Course Name"); + existingCourse.DeactivateEntity(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCourse); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_CourseWithSameNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateCourseInput + { + Name = "Updated Course Name" + }; + var existingCourse = new Domain.Entities.Course("Existing Course Name"); + var existingCourseWithSameName = new Domain.Entities.Course("Updated Course Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingCourse); + _repositoryMock.Setup(repo => repo.GetCourseByNameAsync(input.Name)).ReturnsAsync(existingCourseWithSameName); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetCourseByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/MainArea/CreateMainAreaTests.cs b/src/Application.Tests/UseCases/MainArea/CreateMainAreaTests.cs new file mode 100644 index 00000000..0cd9dd4d --- /dev/null +++ b/src/Application.Tests/UseCases/MainArea/CreateMainAreaTests.cs @@ -0,0 +1,66 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.UseCases.MainArea; +using Moq; +using Xunit; +using Application.Validation; + +namespace Application.Tests.UseCases.MainArea +{ + public class CreateMainAreaTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateMainArea CreateUseCase() => new CreateMainArea(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedMainAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateMainAreaInput + { + Code = "NewCode", + Name = "NewName" + }; + Domain.Entities.MainArea existingMainArea = null; + + _repositoryMock.Setup(repo => repo.GetByCodeAsync(input.Code)).ReturnsAsync(existingMainArea); + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(new Domain.Entities.MainArea(input.Code, input.Name)); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedMainAreaOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_CodeAlreadyExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateMainAreaInput + { + Code = "ExistingCode", + Name = "NewName" + }; + var existingMainArea = new Domain.Entities.MainArea("ExistingCode", "ExistingName"); + + _repositoryMock.Setup(repo => repo.GetByCodeAsync(input.Code)).ReturnsAsync(existingMainArea); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/MainArea/DeleteMainAreaTests.cs b/src/Application.Tests/UseCases/MainArea/DeleteMainAreaTests.cs new file mode 100644 index 00000000..b891b8a3 --- /dev/null +++ b/src/Application.Tests/UseCases/MainArea/DeleteMainAreaTests.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.UseCases.MainArea; +using Application.Validation; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.MainArea +{ + public class DeleteMainAreaTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteMainArea CreateUseCase() => new DeleteMainArea(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedMainAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); // Set the ID to a valid one + Domain.Entities.MainArea deletedMainArea = new(id, "Code", "Name"); // Create a deleted main area object + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(deletedMainArea); + _mapperMock.Setup(mapper => mapper.Map(deletedMainArea)).Returns(new DetailedMainAreaOutput()); // You can create a new instance here. + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(deletedMainArea), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; // Set the ID to null + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/MainArea/GetMainAreaByIdTests.cs b/src/Application.Tests/UseCases/MainArea/GetMainAreaByIdTests.cs new file mode 100644 index 00000000..58b991b6 --- /dev/null +++ b/src/Application.Tests/UseCases/MainArea/GetMainAreaByIdTests.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.UseCases.MainArea; +using Moq; +using Xunit; +using Application.Validation; + +namespace Application.Tests.UseCases.MainArea +{ + public class GetMainAreaByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetMainAreaById CreateUseCase() => new GetMainAreaById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedMainAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var mainAreaEntity = new Domain.Entities.MainArea(id, "Code", "Name"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(mainAreaEntity); + _mapperMock.Setup(mapper => mapper.Map(mainAreaEntity)).Returns(new DetailedMainAreaOutput()); // You can create a new instance here. + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(mainAreaEntity), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/MainArea/GetMainAreasTests.cs b/src/Application.Tests/UseCases/MainArea/GetMainAreasTests.cs new file mode 100644 index 00000000..527aefeb --- /dev/null +++ b/src/Application.Tests/UseCases/MainArea/GetMainAreasTests.cs @@ -0,0 +1,39 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.UseCases.MainArea; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.MainArea +{ + public class GetMainAreasTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetMainAreas CreateUseCase() => new GetMainAreas(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadMainAreaOutputQueryable() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var mainAreaEntities = new List(); + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(mainAreaEntities); + _mapperMock.Setup(mapper => mapper.Map>(mainAreaEntities)).Returns(new List()); // You can create a new list here. + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(mainAreaEntities), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/MainArea/UpdateMainAreaTests.cs b/src/Application.Tests/UseCases/MainArea/UpdateMainAreaTests.cs new file mode 100644 index 00000000..92c64dc5 --- /dev/null +++ b/src/Application.Tests/UseCases/MainArea/UpdateMainAreaTests.cs @@ -0,0 +1,87 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.UseCases.MainArea; +using Moq; +using Xunit; +using Application.Validation; + +namespace Application.Tests.UseCases.MainArea +{ + public class UpdateMainAreaTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateMainArea CreateUseCase() => new UpdateMainArea(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedMainAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); // Set the ID to an existing main area + var input = new UpdateMainAreaInput + { + Name = "Updated Name", + Code = "Updated Code" + }; + var existingMainArea = new Domain.Entities.MainArea(id, "Existing Name", "Existing Code"); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingMainArea); + _repositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(existingMainArea); // You can modify this to return the updated main area + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedMainAreaOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + // Add assertions for the updated main area if necessary + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = (Guid?)null; + var input = new UpdateMainAreaInput + { + Name = "Updated Name", + Code = "Updated Code" + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_MainAreaNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); // Set the ID to a non-existing main area + var input = new UpdateMainAreaInput + { + Name = "Updated Name", + Code = "Updated Code" + }; + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.MainArea)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Notice/CreateNoticeTests.cs b/src/Application.Tests/UseCases/Notice/CreateNoticeTests.cs new file mode 100644 index 00000000..410baa04 --- /dev/null +++ b/src/Application.Tests/UseCases/Notice/CreateNoticeTests.cs @@ -0,0 +1,202 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.UseCases.Notice; +using Moq; +using Xunit; +using Application.Validation; +using Application.Ports.Activity; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Application.Tests.UseCases.Notice +{ + public class CreateNoticeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _activityRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _mapperMock = new(); + private readonly Mock> _loggerMock = new(); + + private ICreateNotice CreateUseCase() => new CreateNotice( + _repositoryMock.Object, + _storageFileServiceMock.Object, + _activityTypeRepositoryMock.Object, + _activityRepositoryMock.Object, + _professorRepositoryMock.Object, + _emailServiceMock.Object, + _mapperMock.Object, + _loggerMock.Object); + + private CreateNoticeInput MockValidNoticeInput() => new() + { + RegistrationStartDate = DateTime.UtcNow, + RegistrationEndDate = DateTime.UtcNow.AddDays(7), + EvaluationStartDate = DateTime.UtcNow.AddDays(8), + EvaluationEndDate = DateTime.UtcNow.AddDays(14), + AppealStartDate = DateTime.UtcNow.AddDays(15), + AppealEndDate = DateTime.UtcNow.AddDays(21), + SendingDocsStartDate = DateTime.UtcNow.AddDays(22), + SendingDocsEndDate = DateTime.UtcNow.AddDays(28), + PartialReportDeadline = DateTime.UtcNow.AddDays(29), + FinalReportDeadline = DateTime.UtcNow.AddDays(35), + Description = "Description", + SuspensionYears = 1, + File = new Mock().Object, + Activities = new List + { + new() { + Name = "Activity Type 1", + Unity = "Unity 1", + Activities = new List + { + new() { + Name = "Activity 1", + Points = 10, + Limits = 100 + } + } + } + } + }; + + private Domain.Entities.Notice MockValidNotice() => new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Description", + suspensionYears: 1 + ); + + private static Domain.Entities.ActivityType MockValidActivityType() => + new(Guid.NewGuid(), "Activity Type 1", "Unity 1", Guid.NewGuid()); + + private static Domain.Entities.Activity MockValidActivity() => + new(Guid.NewGuid(), "Activity 1", 10, 100, Guid.NewGuid()); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadNoticeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var notice = MockValidNotice(); + notice.DocUrl = "FileUrl"; + var activityType = MockValidActivityType(); + var activity = MockValidActivity(); + var input = MockValidNoticeInput(); + var professors = new List() + { + new("1234567", 12345) + }; + + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(notice); + _repositoryMock.Setup(repo => repo.GetNoticeByPeriodAsync(It.IsAny(), It.IsAny())).ReturnsAsync((Domain.Entities.Notice)null); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(input.File, null)).ReturnsAsync("FileUrl"); + _activityTypeRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(activityType); + _activityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(activity); + _professorRepositoryMock.Setup(repo => repo.GetAllActiveProfessorsAsync()).ReturnsAsync(professors); + _emailServiceMock.Setup(service => service.SendNoticeEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadNoticeOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _repositoryMock.Verify(repo => repo.GetNoticeByPeriodAsync(It.IsAny(), It.IsAny()), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(input.File, null), Times.Once); + _activityTypeRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Exactly(1)); + _activityRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Exactly(1)); + _professorRepositoryMock.Verify(repo => repo.GetAllActiveProfessorsAsync(), Times.Once); + _emailServiceMock.Verify(service => service.SendNoticeEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_NoActivitiesProvided_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var notice = MockValidNotice(); + var input = MockValidNoticeInput(); + input.Activities = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NoticeForPeriodAlreadyExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var notice = MockValidNotice(); + var input = MockValidNoticeInput(); + + _repositoryMock.Setup(repo => repo.GetNoticeByPeriodAsync((DateTime)input.RegistrationStartDate, (DateTime)input.RegistrationEndDate)) + .ReturnsAsync(notice); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_FileUploadFails_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var notice = MockValidNotice(); + var input = MockValidNoticeInput(); + + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(notice); + _repositoryMock.Setup(repo => repo.GetNoticeByPeriodAsync(It.IsAny(), It.IsAny())).ReturnsAsync((Domain.Entities.Notice)null); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(input.File, null)) + .ThrowsAsync(new Exception(It.IsAny())); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ActivityValidationFails_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var notice = MockValidNotice(); + notice.DocUrl = "FileUrl"; + var activityType = MockValidActivityType(); + var activity = MockValidActivity(); + var input = MockValidNoticeInput(); + + input.Activities![0].Name = null; + input.Activities![0].Activities![0].Name = null; + + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(notice); + _repositoryMock.Setup(repo => repo.GetNoticeByPeriodAsync(It.IsAny(), It.IsAny())).ReturnsAsync((Domain.Entities.Notice)null); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(input.File, null)).ReturnsAsync("FileUrl"); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Notice/DeleteNoticeTests.cs b/src/Application.Tests/UseCases/Notice/DeleteNoticeTests.cs new file mode 100644 index 00000000..aecc27af --- /dev/null +++ b/src/Application.Tests/UseCases/Notice/DeleteNoticeTests.cs @@ -0,0 +1,103 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Notice; + +namespace Application.Tests.UseCases.Notice +{ + public class DeleteNoticeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteNotice CreateUseCase() => new DeleteNotice(_repositoryMock.Object, _storageFileServiceMock.Object, _mapperMock.Object); + private static Domain.Entities.Notice MockValidNotice() => new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); + + [Fact] + public async Task ExecuteAsync_ValidId_DeletesNoticeAndFile() + { + // Arrange + var useCase = CreateUseCase(); + var noticeToDelete = MockValidNotice(); + var idToDelete = noticeToDelete.Id; + noticeToDelete.DocUrl = "file-url"; + + _repositoryMock.Setup(repo => repo.DeleteAsync(idToDelete)).ReturnsAsync(noticeToDelete); + _storageFileServiceMock.Setup(service => service.DeleteFileAsync(noticeToDelete.DocUrl)); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadNoticeOutput()); + + // Act + var result = await useCase.ExecuteAsync(idToDelete); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(idToDelete), Times.Once); + _storageFileServiceMock.Verify(service => service.DeleteFileAsync(noticeToDelete.DocUrl), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NoticeNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var idToDelete = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.DeleteAsync(idToDelete)).ReturnsAsync((Domain.Entities.Notice)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(idToDelete)); + _repositoryMock.Verify(repo => repo.DeleteAsync(idToDelete), Times.Once); + _storageFileServiceMock.Verify(service => service.DeleteFileAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_FileDeletionFails_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var noticeToDelete = MockValidNotice(); + var idToDelete = noticeToDelete.Id; + noticeToDelete.DocUrl = "file-url"; + + _repositoryMock.Setup(repo => repo.DeleteAsync(idToDelete)).ReturnsAsync(noticeToDelete); + _storageFileServiceMock.Setup(service => service.DeleteFileAsync(noticeToDelete.DocUrl)); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(idToDelete)); + _repositoryMock.Verify(repo => repo.DeleteAsync(idToDelete), Times.Once); + _storageFileServiceMock.Verify(service => service.DeleteFileAsync(noticeToDelete.DocUrl), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _storageFileServiceMock.Verify(service => service.DeleteFileAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Notice/GetNoticeByIdTests.cs b/src/Application.Tests/UseCases/Notice/GetNoticeByIdTests.cs new file mode 100644 index 00000000..e6cabf49 --- /dev/null +++ b/src/Application.Tests/UseCases/Notice/GetNoticeByIdTests.cs @@ -0,0 +1,81 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Notice; + +namespace Application.Tests.UseCases.Notice +{ + public class GetNoticeByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetNoticeById CreateUseCase() => new GetNoticeById(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.Notice MockValidNotice() => new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadNoticeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var noticeToRetrieve = MockValidNotice(); + var idToRetrieve = noticeToRetrieve.Id; + + _repositoryMock.Setup(repo => repo.GetByIdAsync(idToRetrieve)).ReturnsAsync(noticeToRetrieve); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadNoticeOutput()); + + // Act + var result = await useCase.ExecuteAsync(idToRetrieve); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(idToRetrieve), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NoticeNotFound_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + var idToRetrieve = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(idToRetrieve)).ReturnsAsync((Domain.Entities.Notice)null); + + // Act + var result = await useCase.ExecuteAsync(idToRetrieve); + + // Assert + Assert.Null(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(idToRetrieve), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Notice/GetNoticesTests.cs b/src/Application.Tests/UseCases/Notice/GetNoticesTests.cs new file mode 100644 index 00000000..c9eb0f7c --- /dev/null +++ b/src/Application.Tests/UseCases/Notice/GetNoticesTests.cs @@ -0,0 +1,107 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Moq; +using Xunit; +using Application.UseCases.Notice; + +namespace Application.Tests.UseCases.Notice +{ + public class GetNoticesTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetNotices CreateUseCase() => new GetNotices(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.Notice MockValidNotice() => new( + id: Guid.NewGuid(), + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadNoticeOutputList() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var notices = new List + { + MockValidNotice(), + MockValidNotice(), + MockValidNotice() + }; + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(notices); + _mapperMock.Setup(mapper => mapper.Map>(notices)).Returns(new List()); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(notices), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_RepositoryReturnsEmptyList_ReturnsEmptyList() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var notices = new List(); + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(notices); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.Empty(result); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NegativeSkipValue_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + var skip = -1; + var take = 10; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_NegativeTakeValue_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = -1; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Notice/ReportDeadlineNotificationTests.cs b/src/Application.Tests/UseCases/Notice/ReportDeadlineNotificationTests.cs new file mode 100644 index 00000000..d783a3c8 --- /dev/null +++ b/src/Application.Tests/UseCases/Notice/ReportDeadlineNotificationTests.cs @@ -0,0 +1,83 @@ +using Application.Interfaces.UseCases.Notice; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; +using Application.UseCases.Notice; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.Notice +{ + public class ReportDeadlineNotificationTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + + private IReportDeadlineNotification CreateUseCase() => new ReportDeadlineNotification(_projectRepositoryMock.Object, _emailServiceMock.Object); + + [Fact] + public async Task ExecuteAsync_ProjectsWithCloseReportDueDate_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var nextWeek = DateTime.UtcNow.AddDays(7).Date; + var nextMonth = DateTime.UtcNow.AddMonths(1).Date; + + var projects = new List + { + ProjectMock.MockValidProjectProfessorAndNotice(), + ProjectMock.MockValidProjectProfessorAndNotice() + }; + + _projectRepositoryMock.Setup(repo => repo.GetProjectsWithCloseReportDueDateAsync()).ReturnsAsync(projects); + _emailServiceMock + .Setup(service => service.SendNotificationOfReportDeadlineEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Notificação de prazo de entrega de relatório enviada com sucesso.", result); + _projectRepositoryMock.Verify(repo => repo.GetProjectsWithCloseReportDueDateAsync(), Times.Once); + _emailServiceMock.Verify( + service => service.SendNotificationOfReportDeadlineEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Exactly(2)); + } + + [Fact] + public async Task ExecuteAsync_NoProjects_ReturnsNoProjectsMessage() + { + // Arrange + var useCase = CreateUseCase(); + var projects = new List(); + + _projectRepositoryMock.Setup(repo => repo.GetProjectsWithCloseReportDueDateAsync()).ReturnsAsync(projects); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Nenhum projeto com prazo de entrega de relatório próxima.", result); + _projectRepositoryMock.Verify(repo => repo.GetProjectsWithCloseReportDueDateAsync(), Times.Once); + _emailServiceMock.Verify( + service => service.SendNotificationOfReportDeadlineEmailAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Professor/CreateProfessorTests.cs b/src/Application.Tests/UseCases/Professor/CreateProfessorTests.cs new file mode 100644 index 00000000..a9e4d853 --- /dev/null +++ b/src/Application.Tests/UseCases/Professor/CreateProfessorTests.cs @@ -0,0 +1,151 @@ +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.UseCases.Professor; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Professor +{ + public class CreateProfessorTests + { + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _hashServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateProfessor CreateUseCase() => new CreateProfessor( + _professorRepositoryMock.Object, + _userRepositoryMock.Object, + _emailServiceMock.Object, + _hashServiceMock.Object, + _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProfessorOutput() + { + // Arrange + var useCase = CreateUseCase(); + var password = "Password123"; + var input = new CreateProfessorInput + { + SIAPEEnrollment = "1234567", + IdentifyLattes = 1234567, + Password = password, + Name = "Professor Name", + Email = "professor@example.com", + CPF = "58411338029" + }; + var hashedPassword = "HashedPassword123"; + var user = new Domain.Entities.User(Guid.NewGuid(), input.Name, input.Email, hashedPassword, input.CPF, ERole.PROFESSOR); + var professor = new Domain.Entities.Professor(input.SIAPEEnrollment, input.IdentifyLattes) + { + UserId = user.Id + }; + + _hashServiceMock.Setup(hashService => hashService.HashPassword(input.Password)).Returns(hashedPassword); + _userRepositoryMock.Setup(userRepository => userRepository.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(userRepository => userRepository.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(userRepository => userRepository.CreateAsync(It.IsAny())).ReturnsAsync(user); + _professorRepositoryMock.Setup(professorRepository => professorRepository.CreateAsync(It.IsAny())).ReturnsAsync(professor); + _emailServiceMock.Setup(emailService => emailService.SendConfirmationEmailAsync(user.Email, user.Name, user.ValidationCode)).Returns(Task.CompletedTask); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProfessorOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _hashServiceMock.Verify(hashService => hashService.HashPassword(password), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByEmailAsync(input.Email), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByCPFAsync(input.CPF), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.CreateAsync(It.IsAny()), Times.Once); + _professorRepositoryMock.Verify(professorRepository => professorRepository.CreateAsync(It.IsAny()), Times.Once); + _emailServiceMock.Verify(emailService => emailService.SendConfirmationEmailAsync(user.Email, user.Name, user.ValidationCode), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_PasswordIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProfessorInput + { + Password = null + }; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByEmailAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByCPFAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.CreateAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(professorRepository => professorRepository.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(emailService => emailService.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserWithEmailExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProfessorInput + { + SIAPEEnrollment = "1234567", + IdentifyLattes = 1234567, + Email = "existing@example.com", + CPF = "58411338029", + Name = "Professor Name", + Password = "Password123" + }; + + _userRepositoryMock.Setup(userRepository => userRepository.GetUserByEmailAsync(input.Email)).ReturnsAsync(MockValidUser()); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByEmailAsync(input.Email), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByCPFAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.CreateAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(professorRepository => professorRepository.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(emailService => emailService.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserWithCPFExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProfessorInput + { + SIAPEEnrollment = "1234567", + IdentifyLattes = 1234567, + Email = "existing@example.com", + CPF = "58411338029", + Name = "Professor Name", + Password = "Password123" + }; + + _userRepositoryMock.Setup(userRepository => userRepository.GetUserByCPFAsync(input.CPF)).ReturnsAsync(MockValidUser()); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _hashServiceMock.Verify(hashService => hashService.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.GetUserByCPFAsync(input.CPF), Times.Once); + _userRepositoryMock.Verify(userRepository => userRepository.CreateAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(professorRepository => professorRepository.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(emailService => emailService.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Professor/DeleteProfessorTests.cs b/src/Application.Tests/UseCases/Professor/DeleteProfessorTests.cs new file mode 100644 index 00000000..76e8e063 --- /dev/null +++ b/src/Application.Tests/UseCases/Professor/DeleteProfessorTests.cs @@ -0,0 +1,164 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Professor; +using Domain.Entities.Enums; + +namespace Application.Tests.UseCases.Professor +{ + public class DeleteProfessorTests + { + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteProfessor CreateUseCase() => new DeleteProfessor(_professorRepositoryMock.Object, _userRepositoryMock.Object, _mapperMock.Object); + private Domain.Entities.User CreateUser() => new(Guid.NewGuid(), "John Doe", "john.doe@email.com", "123456789", "58411338029", ERole.PROFESSOR); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProfessorOutput() + { + // Arrange + var useCase = CreateUseCase(); + var professorId = Guid.NewGuid(); + var user = CreateUser(); + var professor = new Domain.Entities.Professor(professorId, "SIAPE12", 1234567) + { + UserId = user.Id + }; + + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(user.Id)).ReturnsAsync(user); + + _professorRepositoryMock.Setup(repo => repo.DeleteAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.DeleteAsync(user.Id)).ReturnsAsync(user); + + _mapperMock.Setup(mapper => mapper.Map(professor)).Returns(new DetailedReadProfessorOutput()); + + // Act + var result = await useCase.ExecuteAsync(professorId); + + // Assert + Assert.NotNull(result); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(user.Id), Times.Once); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(user.Id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(professor), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? professorId = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(professorId)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProfessorNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var professorId = Guid.NewGuid(); + + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync((Domain.Entities.Professor)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(professorId)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var professorId = Guid.NewGuid(); + var professor = new Domain.Entities.Professor(professorId, "SIAPE12", 1234567) + { + UserId = Guid.NewGuid() + }; + + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(professor.UserId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(professorId)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(professor.UserId), Times.Once); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProfessorNotDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var professorId = Guid.NewGuid(); + var user = CreateUser(); + var professor = new Domain.Entities.Professor(professorId, "SIAPE12", 1234567) + { + UserId = user.Id + }; + + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(user.Id)).ReturnsAsync(user); + + _professorRepositoryMock.Setup(repo => repo.DeleteAsync(professorId)).ReturnsAsync((Domain.Entities.Professor)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(professorId)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(user.Id), Times.Once); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(user.Id), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var professorId = Guid.NewGuid(); + var user = CreateUser(); + var professor = new Domain.Entities.Professor(professorId, "SIAPE12", 1234567) + { + UserId = user.Id + }; + + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(user.Id)).ReturnsAsync(user); + + _professorRepositoryMock.Setup(repo => repo.DeleteAsync(professorId)).ReturnsAsync(professor); + _userRepositoryMock.Setup(repo => repo.DeleteAsync(user.Id)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(professorId)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(user.Id), Times.Once); + _professorRepositoryMock.Verify(repo => repo.DeleteAsync(professorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(user.Id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Professor/GetProfessorByIdTests.cs b/src/Application.Tests/UseCases/Professor/GetProfessorByIdTests.cs new file mode 100644 index 00000000..ca8cb67a --- /dev/null +++ b/src/Application.Tests/UseCases/Professor/GetProfessorByIdTests.cs @@ -0,0 +1,50 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.Professor; + +namespace Application.Tests.UseCases.Professor +{ + public class GetProfessorByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProfessorById CreateUseCase() => new GetProfessorById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProfessorOutput() + { + // Arrange + var useCase = CreateUseCase(); + Guid professorId = Guid.NewGuid(); + var professorEntity = new Domain.Entities.Professor("1234567", 12345); // Create a Professor entity for testing + var expectedOutput = new DetailedReadProfessorOutput(); // Create an expected output + + _repositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professorEntity); + _mapperMock.Setup(mapper => mapper.Map(professorEntity)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(professorId); + + // Assert + Assert.NotNull(result); + Assert.Same(expectedOutput, result); // Check if the expected output is returned + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? professorId = null; + + // Act & Assert + await Assert.ThrowsAsync(() => useCase.ExecuteAsync(professorId)); + } + } +} diff --git a/src/Application.Tests/UseCases/Professor/GetProfessorsTests.cs b/src/Application.Tests/UseCases/Professor/GetProfessorsTests.cs new file mode 100644 index 00000000..48578bd7 --- /dev/null +++ b/src/Application.Tests/UseCases/Professor/GetProfessorsTests.cs @@ -0,0 +1,70 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.UseCases.Professor; +using Application.Ports.Professor; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.Professor; + +namespace Application.Tests.UseCases.Professor +{ + public class GetProfessorsTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + private static Domain.Entities.Professor MockValidProfessor() => new("1234567", 12345); + + private IGetProfessors CreateUseCase() => new GetProfessors(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidParameters_ReturnsQueryableOfResumedReadProfessorOutput() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 10; + var professorEntities = new List { MockValidProfessor(), MockValidProfessor() }.AsQueryable(); + var expectedOutput = professorEntities.Select(p => new ResumedReadProfessorOutput()).AsQueryable(); + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(professorEntities); + _mapperMock.Setup(mapper => mapper.Map>(professorEntities)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom>(result); + // Check if the collections have the same number of elements + Assert.Equal(expectedOutput.Count(), result.Count()); + } + + [Fact] + public void ExecuteAsync_InvalidSkip_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = -1; + int take = 10; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InvalidTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 0; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Professor/UpdateProfessorTests.cs b/src/Application.Tests/UseCases/Professor/UpdateProfessorTests.cs new file mode 100644 index 00000000..2a57e713 --- /dev/null +++ b/src/Application.Tests/UseCases/Professor/UpdateProfessorTests.cs @@ -0,0 +1,106 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.Professor; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; + +namespace Application.Tests.UseCases.Professor +{ + public class UpdateProfessorTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateProfessor CreateUseCase() => new UpdateProfessor(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProfessorOutput() + { + // Arrange + var useCase = CreateUseCase(); + Guid professorId = Guid.NewGuid(); + var updateModel = new UpdateProfessorInput + { + IdentifyLattes = 1234567, + SIAPEEnrollment = "1234567" + }; + var professorEntity = new Domain.Entities.Professor + ( + id: professorId, + identifyLattes: 7654321, + siapeEnrollment: "1234567" + ); + var expectedOutput = new DetailedReadProfessorOutput(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professorEntity); + _repositoryMock.Setup(repo => repo.UpdateAsync(professorEntity)).ReturnsAsync(professorEntity); + _mapperMock.Setup(mapper => mapper.Map(professorEntity)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(professorId, updateModel); + + // Assert + Assert.NotNull(result); + Assert.Same(expectedOutput, result); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? professorId = null; + var updateModel = new UpdateProfessorInput(); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(professorId, updateModel)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProfessorNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid professorId = Guid.NewGuid(); + var updateModel = new UpdateProfessorInput(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync((Domain.Entities.Professor)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(professorId, updateModel)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_DeletedProfessor_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid professorId = Guid.NewGuid(); + var updateModel = new UpdateProfessorInput(); + var professorEntity = new Domain.Entities.Professor + ( + id: professorId, + identifyLattes: 7654321, + siapeEnrollment: "1234567" + ); + professorEntity.DeactivateEntity(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(professorId)).ReturnsAsync(professorEntity); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(professorId, updateModel)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(professorId), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProgramType/CreateProgramTypeTests.cs b/src/Application.Tests/UseCases/ProgramType/CreateProgramTypeTests.cs new file mode 100644 index 00000000..7a025ed9 --- /dev/null +++ b/src/Application.Tests/UseCases/ProgramType/CreateProgramTypeTests.cs @@ -0,0 +1,90 @@ +using AutoMapper; +using Application.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProgramType; + +namespace Application.Tests.UseCases.ProgramType +{ + public class CreateProgramTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateProgramType CreateUseCase() => new CreateProgramType(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProgramTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProgramTypeInput + { + Name = "New Program Type Name" + }; + var createdProgramType = new Domain.Entities.ProgramType + ( + id: Guid.NewGuid(), + name: "New Program Type Name", + description: "New Program Type Description" + ); + var expectedOutput = new DetailedReadProgramTypeOutput(); + + _repositoryMock.Setup(repo => repo.GetProgramTypeByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.ProgramType)null); + _repositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(createdProgramType); + _mapperMock.Setup(mapper => mapper.Map(createdProgramType)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.Same(expectedOutput, result); + } + + [Fact] + public void ExecuteAsync_NullName_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProgramTypeInput + { + Name = null + }; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProgramTypeWithNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateProgramTypeInput + { + Name = "Existing Program Type Name" + }; + var existingProgramType = new Domain.Entities.ProgramType + ( + id: Guid.NewGuid(), + name: "Existing Program Type Name", + description: "Existing Program Type Description" + ); + + _repositoryMock.Setup(repo => repo.GetProgramTypeByNameAsync(input.Name)).ReturnsAsync(existingProgramType); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProgramType/DeleteProgramTypeTests.cs b/src/Application.Tests/UseCases/ProgramType/DeleteProgramTypeTests.cs new file mode 100644 index 00000000..a9f8363e --- /dev/null +++ b/src/Application.Tests/UseCases/ProgramType/DeleteProgramTypeTests.cs @@ -0,0 +1,57 @@ +using AutoMapper; +using Application.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProgramType; + +namespace Application.Tests.UseCases.ProgramType +{ + public class DeleteProgramTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteProgramType CreateUseCase() => new DeleteProgramType(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProgramTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var programType = new Domain.Entities.ProgramType + ( + id: Guid.NewGuid(), + name: "Program Type Name", + description: "Program Type Description" + ); + var expectedOutput = new DetailedReadProgramTypeOutput(); + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(programType); + _mapperMock.Setup(mapper => mapper.Map(programType)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + Assert.Same(expectedOutput, result); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProgramType/GetProgramTypeByIdTests.cs b/src/Application.Tests/UseCases/ProgramType/GetProgramTypeByIdTests.cs new file mode 100644 index 00000000..4201fdf3 --- /dev/null +++ b/src/Application.Tests/UseCases/ProgramType/GetProgramTypeByIdTests.cs @@ -0,0 +1,57 @@ +using AutoMapper; +using Application.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProgramType; + +namespace Application.Tests.UseCases.ProgramType +{ + public class GetProgramTypeByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProgramTypeById CreateUseCase() => new GetProgramTypeById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProgramTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var programType = new Domain.Entities.ProgramType + ( + id: Guid.NewGuid(), + name: "Program Type Name", + description: "Program Type Description" + ); + var expectedOutput = new DetailedReadProgramTypeOutput(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(programType); + _mapperMock.Setup(mapper => mapper.Map(programType)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + Assert.Same(expectedOutput, result); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProgramType/GetProgramTypesTests.cs b/src/Application.Tests/UseCases/ProgramType/GetProgramTypesTests.cs new file mode 100644 index 00000000..4bfbdcb3 --- /dev/null +++ b/src/Application.Tests/UseCases/ProgramType/GetProgramTypesTests.cs @@ -0,0 +1,69 @@ +using AutoMapper; +using Application.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProgramType; + +namespace Application.Tests.UseCases.ProgramType +{ + public class GetProgramTypesTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProgramTypes CreateUseCase() => new GetProgramTypes(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.ProgramType MockValidProgramType() => new("Program Name", "Program Description"); + + [Fact] + public async Task ExecuteAsync_ValidParameters_ReturnsQueryableOfResumedReadProgramTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 10; + var programTypeEntities = new List { MockValidProgramType(), MockValidProgramType() }.AsQueryable(); + var expectedOutput = programTypeEntities.Select(p => new ResumedReadProgramTypeOutput()).AsQueryable(); + + _repositoryMock.Setup(repo => repo.GetAllAsync(skip, take)).ReturnsAsync(programTypeEntities); + _mapperMock.Setup(mapper => mapper.Map>(programTypeEntities)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom>(result); + Assert.Equal(expectedOutput.Count(), result.Count()); + } + + [Fact] + public void ExecuteAsync_InvalidSkip_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = -1; + int take = 10; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InvalidTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 0; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetAllAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProgramType/UpdateProgramTypeTests.cs b/src/Application.Tests/UseCases/ProgramType/UpdateProgramTypeTests.cs new file mode 100644 index 00000000..152d4712 --- /dev/null +++ b/src/Application.Tests/UseCases/ProgramType/UpdateProgramTypeTests.cs @@ -0,0 +1,141 @@ +using AutoMapper; +using Application.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProgramType; + +namespace Application.Tests.UseCases.ProgramType +{ + public class UpdateProgramTypeTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateProgramType CreateUseCase() => new UpdateProgramType(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.ProgramType MockValidProgramType() => new("Program Name", "Program Description"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProgramTypeOutput() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var input = new UpdateProgramTypeInput + { + Name = "Updated Program Type Name", + Description = "Updated Program Type Description" + }; + var existingProgramType = MockValidProgramType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingProgramType); + _repositoryMock.Setup(repo => repo.GetProgramTypeByNameAsync(input.Name)).ReturnsAsync((Domain.Entities.ProgramType)null); + _repositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(existingProgramType); + _mapperMock.Setup(mapper => mapper.Map(existingProgramType)) + .Returns(new DetailedReadProgramTypeOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(existingProgramType), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + var input = new UpdateProgramTypeInput + { + Name = "Updated Program Type Name", + Description = "Updated Program Type Description" + }; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NameIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var input = new UpdateProgramTypeInput + { + Name = null, + Description = "Updated Program Type Description" + }; + var existingProgramType = MockValidProgramType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingProgramType); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Never); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProgramTypeNameExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var input = new UpdateProgramTypeInput + { + Name = "Existing Program Type Name", + Description = "Updated Program Type Description" + }; + var existingProgramType = MockValidProgramType(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingProgramType); + _repositoryMock.Setup(repo => repo.GetProgramTypeByNameAsync(input.Name)).ReturnsAsync(existingProgramType); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(input.Name), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_DeletedProgramType_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + var input = new UpdateProgramTypeInput + { + Name = "Updated Program Type Name", + Description = "Updated Program Type Description" + }; + var existingProgramType = MockValidProgramType(); + existingProgramType.DeactivateEntity(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(existingProgramType); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id, input)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.GetProgramTypeByNameAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/AppealProjectTests.cs b/src/Application.Tests/UseCases/Project/AppealProjectTests.cs new file mode 100644 index 00000000..8238a1d2 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/AppealProjectTests.cs @@ -0,0 +1,99 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; +using Moq; +using Xunit; +using Application.Tests.Mocks; +using Application.UseCases.Project; + +namespace Application.Tests.UseCases.Project +{ + public class AppealProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IAppealProject CreateUseCase() => new AppealProject( + _projectRepositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidData_ReturnsResumedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var appealDescription = "Sample appeal description"; + var project = ProjectMock.MockValidProject(); + project.Status = EProjectStatus.Rejected; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(project)).ReturnsAsync(project); + _mapperMock.Setup(mapper => mapper.Map(project)).Returns(new ResumedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId, appealDescription); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(projectId), Times.Once); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(project), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NoProjectFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var appealDescription = "Sample appeal description"; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId, appealDescription)); + } + + [Fact] + public async Task ExecuteAsync_NotInAppealPhase_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var appealDescription = "Sample appeal description"; + var project = ProjectMock.MockValidProject(); + var notice = NoticeMock.MockValidNotice(); + notice.AppealStartDate = DateTime.UtcNow.AddDays(2); // Future date + notice.AppealEndDate = DateTime.UtcNow.AddDays(1); // Future date + project.Notice = notice; + project.Status = EProjectStatus.Rejected; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId, appealDescription)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotRejected_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var appealDescription = "Sample appeal description"; + var project = ProjectMock.MockValidProject(); + project.Status = EProjectStatus.Accepted; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId, appealDescription)); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/CancelProjectTests.cs b/src/Application.Tests/UseCases/Project/CancelProjectTests.cs new file mode 100644 index 00000000..53d817d0 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/CancelProjectTests.cs @@ -0,0 +1,93 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class CancelProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICancelProject CreateUseCase() => new Application.UseCases.Project.CancelProject( + _projectRepositoryMock.Object, + _mapperMock.Object + ); + + [Theory] + [InlineData(EProjectStatus.Accepted)] + [InlineData(EProjectStatus.DocumentAnalysis)] + [InlineData(EProjectStatus.Evaluation)] + [InlineData(EProjectStatus.Opened)] + [InlineData(EProjectStatus.Pending)] + [InlineData(EProjectStatus.Rejected)] + [InlineData(EProjectStatus.Started)] + [InlineData(EProjectStatus.Submitted)] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadProjectOutput(EProjectStatus status) + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var observation = "Cancellation reason"; + + var project = ProjectMock.MockValidProjectWithId(); + project.Status = status; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(project)).ReturnsAsync(project); + _mapperMock.Setup(mapper => mapper.Map(project)).Returns(new ResumedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId, observation); + + // Assert + Assert.NotNull(result); + Assert.Equal(EProjectStatus.Canceled, project.Status); + Assert.Equal(EProjectStatus.Canceled.GetDescription(), project.StatusDescription); + Assert.Equal(observation, project.CancellationReason); + Assert.NotNull(project.CancellationDate); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(project), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var observation = "Cancellation reason"; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId, observation)); + } + + [Theory] + [InlineData(EProjectStatus.Closed)] + [InlineData(EProjectStatus.Canceled)] + public async Task ExecuteAsync_ProjectConcluded_ThrowsUseCaseException(EProjectStatus status) + { + // Arrange + var useCase = CreateUseCase(); + var observation = "Cancellation reason"; + + var project = ProjectMock.MockValidProjectWithId(); + var projectId = project.Id; + project.Status = status; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId, observation)); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/ClosePendingProjectsTests.cs b/src/Application.Tests/UseCases/Project/ClosePendingProjectsTests.cs new file mode 100644 index 00000000..705252d9 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/ClosePendingProjectsTests.cs @@ -0,0 +1,81 @@ +using Application.Interfaces.UseCases.Project; +using Application.Tests.Mocks; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class ClosePendingProjectsTests + { + + private readonly Mock _projectRepositoryMock = new(); + private IClosePendingProjects CreateUseCase() => new Application.UseCases.Project.ClosePendingProjects( + _projectRepositoryMock.Object + ); + + [Fact] + public async Task ExecuteAsync_NoPendingProjects_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var projects = new List(); + + _projectRepositoryMock.Setup(repo => repo.GetPendingAndOverdueProjectsAsync()).ReturnsAsync(projects); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Nenhum projeto pendente e com prazo vencido foi encontrado.", result); + } + + [Fact] + public async Task ExecuteAsync_CancelProjects_SuccessfullyCancelsProjects() + { + // Arrange + var useCase = CreateUseCase(); + var project1 = ProjectMock.MockValidProjectWithId(); + var project2 = ProjectMock.MockValidProjectWithId(); + var projects = new List { project1, project2 }; + + _projectRepositoryMock.Setup(repo => repo.GetPendingAndOverdueProjectsAsync()).ReturnsAsync(projects); + _projectRepositoryMock.Setup(repo => repo.UpdateManyAsync(It.IsAny>())).ReturnsAsync(projects.Count); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + _projectRepositoryMock.Verify(repo => repo.UpdateManyAsync(It.IsAny>()), Times.Once); + Assert.Equal($"{projects.Count} projetos pendentes e com prazo de resolução vencido foram cancelados com sucesso.", result); + Assert.All(projects, project => + { + Assert.Equal(EProjectStatus.Canceled, project.Status); + Assert.Equal("Projeto cancelado automaticamente por falta de ação dentro do prazo estipulado.", project.StatusDescription); + }); + } + + [Fact] + public async Task ExecuteAsync_CancelProjects_PartiallyCancelsProjects() + { + // Arrange + var useCase = CreateUseCase(); + var project1 = ProjectMock.MockValidProjectWithId(); + project1.Status = EProjectStatus.Pending; + var project2 = ProjectMock.MockValidProjectWithId(); + project2.Status = EProjectStatus.Rejected; + var projects = new List { project1, project2 }; + + _projectRepositoryMock.Setup(repo => repo.GetPendingAndOverdueProjectsAsync()).ReturnsAsync(projects); + _projectRepositoryMock.Setup(repo => repo.UpdateManyAsync(It.IsAny>())).ReturnsAsync(1); // Simulate a partial update + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + _projectRepositoryMock.Verify(repo => repo.UpdateManyAsync(It.IsAny>()), Times.Once); + Assert.Equal($"Ocorreu um erro ao cancelar os projetos pendentes e com prazo vencido. Foram cancelados 1 de {projects.Count} projetos.", result); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GenerateCertificateTests.cs b/src/Application.Tests/UseCases/Project/GenerateCertificateTests.cs new file mode 100644 index 00000000..cc30d81f --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GenerateCertificateTests.cs @@ -0,0 +1,149 @@ +using Application.Interfaces.UseCases.Project; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class GenerateCertificateTests + { + private readonly Mock _noticeRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _reportServiceMock = new(); + private readonly Mock _storageFileServiceMock = new(); + + private IGenerateCertificate CreateUseCase() => new GenerateCertificate( + projectRepository: _projectRepositoryMock.Object, + noticeRepository: _noticeRepositoryMock.Object, + projectReportRepository: _projectReportRepositoryMock.Object, + professorRepository: _professorRepositoryMock.Object, + userRepository: _userRepositoryMock.Object, + reportService: _reportServiceMock.Object, + storageFileService: _storageFileServiceMock.Object + ); + + [Fact] + public async Task ExecuteAsync_NoticeNotFound_ReturnsErrorMessage() + { + // Arrange + var useCase = CreateUseCase(); + _noticeRepositoryMock.Setup(repo => repo.GetNoticeEndingAsync()).ReturnsAsync((Domain.Entities.Notice)null); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Nenhum edital em estágio de encerramento encontrado.", result); + } + + [Fact] + public async Task ExecuteAsync_NoProjectsFound_ReturnsErrorMessage() + { + // Arrange + var useCase = CreateUseCase(); + + _noticeRepositoryMock.Setup(repo => repo.GetNoticeEndingAsync()).ReturnsAsync(NoticeMock.MockValidNotice()); + _projectRepositoryMock.Setup(repo => repo.GetProjectByNoticeAsync(It.IsAny())).ReturnsAsync(Enumerable.Empty()); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Nenhum projeto em estágio de encerramento encontrado.", result); + } + + [Fact] + public async Task ExecuteAsync_CoordinatorNotFound_ReturnsErrorMessage() + { + // Arrange + var useCase = CreateUseCase(); + var projects = new[] { ProjectMock.MockValidProjectProfessorAndNotice() }; + var notice = NoticeMock.MockValidNoticeWithId(); + + _noticeRepositoryMock.Setup(repo => repo.GetNoticeEndingAsync()).ReturnsAsync(notice); + _projectRepositoryMock.Setup(repo => repo.GetProjectByNoticeAsync(It.IsAny())).ReturnsAsync(projects); + _userRepositoryMock.Setup(repo => repo.GetCoordinatorAsync()).ReturnsAsync((Domain.Entities.User)null); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Nenhum coordenador encontrado.", result); + } + + [Fact] + public async Task ExecuteAsync_ProjectWithoutFinalReport_ClosesProjectAndSuspendsProfessor() + { + // Arrange + var useCase = CreateUseCase(); + var projects = new[] { ProjectMock.MockValidProjectProfessorAndNotice() }; + var notice = NoticeMock.MockValidNoticeWithId(); + var coordinator = UserMock.MockValidUser(); + + _noticeRepositoryMock.Setup(repo => repo.GetNoticeEndingAsync()).ReturnsAsync(notice); + _projectRepositoryMock.Setup(repo => repo.GetProjectByNoticeAsync(It.IsAny())).ReturnsAsync(projects); + _userRepositoryMock.Setup(repo => repo.GetCoordinatorAsync()).ReturnsAsync(coordinator); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync((Domain.Entities.Project project) => project); + _projectReportRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(It.IsAny())).ReturnsAsync((Domain.Entities.ProjectFinalReport)null); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync((Domain.Entities.Professor)null); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Certificados gerados com sucesso.", result); + + // Verify project status and professor suspension + _noticeRepositoryMock.Verify(repo => repo.GetNoticeEndingAsync(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetCoordinatorAsync(), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(It.IsAny()), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _professorRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ProjectWithFinalReport_GeneratesCertificateAndClosesProject() + { + // Arrange + var useCase = CreateUseCase(); + var coordinator = UserMock.MockValidUser(); + var notice = NoticeMock.MockValidNoticeWithId(); + var projects = new[] { ProjectMock.MockValidProjectProfessorAndNotice() }; + var projectFinalReport = new Domain.Entities.ProjectFinalReport(Guid.NewGuid(), Guid.NewGuid()); + var file = FileMock.CreateIFormFile(); + var path = "./Samples/sample.pdf"; + var bytes = File.ReadAllBytes(path); + + _noticeRepositoryMock.Setup(repo => repo.GetNoticeEndingAsync()).ReturnsAsync(notice); + _projectRepositoryMock.Setup(repo => repo.GetProjectByNoticeAsync(It.IsAny())).ReturnsAsync(projects); + _userRepositoryMock.Setup(repo => repo.GetCoordinatorAsync()).ReturnsAsync(coordinator); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync((Domain.Entities.Project project) => project); + _projectReportRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(It.IsAny())).ReturnsAsync(projectFinalReport); + _reportServiceMock.Setup(service => service.GenerateCertificateAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(path); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(bytes, path)).ReturnsAsync("certificate_url"); + + // Act + var result = await useCase.ExecuteAsync(); + + // Assert + Assert.Equal("Certificados gerados com sucesso.", result); + + // Verify project status and certificate generation + _noticeRepositoryMock.Verify(repo => repo.GetNoticeEndingAsync(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetProjectByNoticeAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetCoordinatorAsync(), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(It.IsAny()), Times.Once); + _reportServiceMock.Verify(service => service.GenerateCertificateAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(bytes, path), Times.Once); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GetActivitiesByProjectIdTests.cs b/src/Application.Tests/UseCases/Project/GetActivitiesByProjectIdTests.cs new file mode 100644 index 00000000..7858a290 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GetActivitiesByProjectIdTests.cs @@ -0,0 +1,78 @@ +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.Project; +using Domain.Entities; +using Application.Tests.Mocks; +using Application.UseCases.Project; + +namespace Application.Tests.UseCases.Project +{ + public class GetActivitiesByProjectIdTests + { + private readonly Mock _projectActivityRepositoryMock = new(); + + private IGetActivitiesByProjectId CreateUseCase() => new GetActivitiesByProjectId(_projectActivityRepositoryMock.Object); + + [Fact] + public async Task ExecuteAsync_ProjectIdIsNull_ReturnsEmptyList() + { + // Arrange + var useCase = CreateUseCase(); + Guid? projectId = null; + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task ExecuteAsync_NoProjectActivities_ReturnsEmptyList() + { + // Arrange + var useCase = CreateUseCase(); + Guid projectId = Guid.NewGuid(); + + _projectActivityRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(new List()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task ExecuteAsync_ProjectActivitiesExist_ReturnsMappedList() + { + // Arrange + var useCase = CreateUseCase(); + Guid projectId = Guid.NewGuid(); + var projectActivities = new List + { + ProjectActivityMock.MockValidProjectActivity() + }; + + _projectActivityRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(projectActivities); + + // Act + var result = await useCase.ExecuteAsync(projectId); + var outputs = result.ToList(); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + Assert.Single(result); + Assert.Equal(projectActivities[0].Id, outputs[0].Id); + Assert.Equal(projectActivities[0].ActivityId, outputs[0].ActivityId); + Assert.Equal(projectActivities[0].ProjectId, outputs[0].ProjectId); + Assert.Equal(projectActivities[0].InformedActivities, outputs[0].InformedActivities); + Assert.Equal(projectActivities[0].DeletedAt, outputs[0].DeletedAt); + Assert.Equal(projectActivities[0].FoundActivities, outputs[0].FoundActivities); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GetClosedProjectsTests.cs b/src/Application.Tests/UseCases/Project/GetClosedProjectsTests.cs new file mode 100644 index 00000000..f24132e5 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GetClosedProjectsTests.cs @@ -0,0 +1,97 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class GetClosedProjectsTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetClosedProjects CreateUseCase() => new GetClosedProjects( + _projectRepositoryMock.Object, + _tokenAuthenticationServiceMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadProjectOutputList() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + var professorId = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + var projects = new List + { + ProjectMock.MockValidProject(), + ProjectMock.MockValidProject() + }; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetProfessorProjectsAsync(skip, take, It.IsAny(), true)) + .ReturnsAsync(projects); + _mapperMock.Setup(mapper => mapper.Map>(projects)) + .Returns(new List { new(), new() }); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + Assert.Equal(2, result.Count); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetProfessorProjectsAsync(skip, take, It.IsAny(), true), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(projects), Times.Once); + } + + + + [Fact] + public async Task ExecuteAsync_InvalidSkipAndTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + var skip = -1; + var take = 0; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + + // Additional assertions + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Never); + _projectRepositoryMock.Verify(repo => repo.GetProfessorProjectsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_UnauthorizedUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(null as Dictionary); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + + // Additional assertions + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetProfessorProjectsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GetOpenProjectsTests.cs b/src/Application.Tests/UseCases/Project/GetOpenProjectsTests.cs new file mode 100644 index 00000000..dbe7eec5 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GetOpenProjectsTests.cs @@ -0,0 +1,90 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class GetOpenProjectsTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetOpenProjects CreateUseCase() => new GetOpenProjects( + _projectRepositoryMock.Object, + _tokenAuthenticationServiceMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_WithValidInput_ReturnsProjectList() + { + // Arrange + var userClaims = ClaimsMock.MockValidClaims(); + var useCase = CreateUseCase(); + + var skip = 0; + var take = 10; + var onlyMyProjects = true; + + _projectRepositoryMock.Setup(repo => repo.GetProfessorProjectsAsync(skip, take, It.IsAny(), false)) + .ReturnsAsync(new List()); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()) + .Returns(userClaims); + + _mapperMock.Setup(mapper => mapper.Map>(It.IsAny>())) + .Returns(new List()); + + // Act + var result = await useCase.ExecuteAsync(skip, take, onlyMyProjects); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + _projectRepositoryMock.Verify(repo => repo.GetProfessorProjectsAsync(skip, take, It.IsAny(), false), Times.Once); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidSkipOrTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + + var skip = -1; // Invalid skip value + var take = 0; // Invalid take value + var onlyMyProjects = true; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take, onlyMyProjects)); + } + + [Fact] + public async Task ExecuteAsync_UnauthorizedUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var skip = 0; + var take = 10; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(null as Dictionary); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + + // Additional assertions + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetProfessorProjectsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GetProjectByIdTests.cs b/src/Application.Tests/UseCases/Project/GetProjectByIdTests.cs new file mode 100644 index 00000000..a8812774 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GetProjectByIdTests.cs @@ -0,0 +1,58 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; +using Moq; +using System; +using System.Threading.Tasks; +using Xunit; +using Application.Interfaces.UseCases.Project; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.Project +{ + public class GetProjectByIdTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectById CreateUseCase() => new GetProjectById(_projectRepositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var projectId = project.Id; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + _mapperMock.Setup(mapper => mapper.Map(project)).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/GetProjectsToEvaluateTests.cs b/src/Application.Tests/UseCases/Project/GetProjectsToEvaluateTests.cs new file mode 100644 index 00000000..6fa575ab --- /dev/null +++ b/src/Application.Tests/UseCases/Project/GetProjectsToEvaluateTests.cs @@ -0,0 +1,71 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class GetProjectsToEvaluateTests + { + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectsToEvaluate CreateUseCase() => new GetProjectsToEvaluate( + _tokenAuthenticationServiceMock.Object, + _projectRepositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_InvalidParameters_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(-1, 0)); + } + + [Fact] + public async Task ExecuteAsync_UserNotAdminOrProfessor_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var userClaims = ClaimsMock.MockValidClaims(); + userClaims.First().Value.Role = Domain.Entities.Enums.ERole.STUDENT; + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(0, 1)); + } + + [Fact] + public async Task ExecuteAsync_ValidUser_GetProjectsToEvaluate() + { + // Arrange + var useCase = CreateUseCase(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetProjectsToEvaluateAsync(0, 1, It.IsAny())) + .ReturnsAsync(new List()); + _mapperMock.Setup(mapper => mapper.Map>(It.IsAny>())) + .Returns(new List()); + + // Act + var result = await useCase.ExecuteAsync(0, 1); + + // Assert + _projectRepositoryMock.Verify(repo => repo.GetProjectsToEvaluateAsync(0, 1, It.IsAny()), Times.Once); + Assert.NotNull(result); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/OpenProjectTests.cs b/src/Application.Tests/UseCases/Project/OpenProjectTests.cs new file mode 100644 index 00000000..745c461b --- /dev/null +++ b/src/Application.Tests/UseCases/Project/OpenProjectTests.cs @@ -0,0 +1,257 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Ports.ProjectActivity; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class OpenProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _noticeRepositoryMock = new(); + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _programTypeRepositoryMock = new(); + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _projectActivityRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IOpenProject CreateUseCase() => new OpenProject( + _projectRepositoryMock.Object, + _studentRepositoryMock.Object, + _professorRepositoryMock.Object, + _noticeRepositoryMock.Object, + _subAreaRepositoryMock.Object, + _programTypeRepositoryMock.Object, + _activityTypeRepositoryMock.Object, + _projectActivityRepositoryMock.Object, + _mapperMock.Object + ); + + public static OpenProjectInput GetValidInput() + { + return new OpenProjectInput + { + Title = "My Project Title", + KeyWord1 = "Keyword1", + KeyWord2 = "Keyword2", + KeyWord3 = "Keyword3", + IsScholarshipCandidate = true, + Objective = "Project Objective", + Methodology = "Project Methodology", + ExpectedResults = "Expected Results", + ActivitiesExecutionSchedule = "Activity Schedule", + Activities = new List + { + new() { + ActivityId = Guid.NewGuid(), + InformedActivities = 10 + }, + new() { + ActivityId = Guid.NewGuid(), + InformedActivities = 10 + } + }, + ProgramTypeId = Guid.NewGuid(), + ProfessorId = Guid.NewGuid(), + SubAreaId = Guid.NewGuid(), + NoticeId = Guid.NewGuid(), + StudentId = Guid.NewGuid() + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var notice = NoticeMock.MockValidNoticeWithId(); + var student = StudentMock.MockValidStudentWithId(); + var activityType = new List { ActivityTypeMock.MockValidActivityType() }; + var projectActivity = ProjectActivityMock.MockValidProjectActivity(); + + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityType); + _projectRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(project); + _projectActivityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectActivity); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new ResumedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + + _noticeRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _programTypeRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false), Times.Once); + _activityTypeRepositoryMock.Verify(repo => repo.GetByNoticeIdAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _projectActivityRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.AtMostOnce); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProjectOutsideRegistrationPeriod_ThrowsBusinessRuleViolation() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var notice = NoticeMock.MockValidNotice(); + notice.RegistrationStartDate = DateTime.Now.AddDays(-2); + notice.RegistrationEndDate = DateTime.Now.AddDays(-1); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(notice); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _noticeRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_SubAreaNotFound_ThrowsNotFoundEntityById() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync((Domain.Entities.SubArea)null); + // Set up other necessary mock setups + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _programTypeRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ProgramTypeNotFound_ThrowsNotFoundEntityById() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProgramTypeId)).ReturnsAsync((Domain.Entities.ProgramType)null); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _programTypeRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ProfessorNotFound_ThrowsNotFoundEntityById() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProgramTypeId)).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProfessorId)).ReturnsAsync((Domain.Entities.Professor)null); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _professorRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_StudentNotFound_ThrowsNotFoundEntityById() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProgramTypeId)).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProfessorId)).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(input.StudentId)).ReturnsAsync((Domain.Entities.Student)null); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_StudentAlreadyHasProject_ThrowsBusinessRuleViolation() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var student = StudentMock.MockValidStudentWithId(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProgramTypeId)).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProfessorId)).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(input.StudentId)).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List { project }); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false), Times.Once); + _projectRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ActivityTypeNotFound_ThrowsNotFoundEntityById() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var student = StudentMock.MockValidStudentWithId(); + + // Setup your mocks here + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.NoticeId)).ReturnsAsync(project.Notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.SubAreaId)).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProgramTypeId)).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProfessorId)).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(input.StudentId)).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(new List()); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _activityTypeRepositoryMock.Verify(repo => repo.GetByNoticeIdAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/SubmitProjectTests.cs b/src/Application.Tests/UseCases/Project/SubmitProjectTests.cs new file mode 100644 index 00000000..e0afe742 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/SubmitProjectTests.cs @@ -0,0 +1,118 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class SubmitProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ISubmitProject CreateUseCase() => new SubmitProject(_projectRepositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidProjectId_ReturnsResumedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Opened; + project.Student = StudentMock.MockValidStudent(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(project)).ReturnsAsync(project); + _mapperMock.Setup(mapper => mapper.Map(project)).Returns(new ResumedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + Assert.Equal(EProjectStatus.Submitted, project.Status); + Assert.Equal(EProjectStatus.Submitted.GetDescription(), project.StatusDescription); + Assert.NotNull(project.SubmissionDate); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(project), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProjectIdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? projectId = null; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + + [Fact] + public async Task ExecuteAsync_NoticeNotInRegistrationPhase_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Notice.RegistrationStartDate = DateTime.UtcNow.AddHours(1); + project.Notice.RegistrationEndDate = DateTime.UtcNow.AddHours(2); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + + [Fact] + public async Task ExecuteAsync_ProjectWithoutStudent_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.StudentId = null; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotOpened_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Submitted; + project.Student = StudentMock.MockValidStudent(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + } +} diff --git a/src/Application.Tests/UseCases/Project/UpdateProjectTests.cs b/src/Application.Tests/UseCases/Project/UpdateProjectTests.cs new file mode 100644 index 00000000..6a883439 --- /dev/null +++ b/src/Application.Tests/UseCases/Project/UpdateProjectTests.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Ports.ProjectActivity; +using Application.Tests.Mocks; +using Application.UseCases.Project; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Microsoft.VisualBasic; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Project +{ + public class UpdateProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _noticeRepositoryMock = new(); + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _programTypeRepositoryMock = new(); + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _projectActivityRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateProject CreateUseCase() => new UpdateProject( + _projectRepositoryMock.Object, + _studentRepositoryMock.Object, + _professorRepositoryMock.Object, + _noticeRepositoryMock.Object, + _subAreaRepositoryMock.Object, + _programTypeRepositoryMock.Object, + _activityTypeRepositoryMock.Object, + _projectActivityRepositoryMock.Object, + _mapperMock.Object + ); + + public static UpdateProjectInput GetValidInput() + { + return new UpdateProjectInput + { + Title = "Sample Project Title", + KeyWord1 = "Keyword1", + KeyWord2 = "Keyword2", + KeyWord3 = "Keyword3", + IsScholarshipCandidate = true, + Objective = "Sample project objective.", + Methodology = "Sample project methodology.", + ExpectedResults = "Sample expected results.", + ActivitiesExecutionSchedule = "Sample execution schedule", + Activities = new List + { + new UpdateProjectActivityInput + { + ActivityId = Guid.NewGuid(), + InformedActivities = 10 + }, + new UpdateProjectActivityInput + { + ActivityId = Guid.NewGuid(), + InformedActivities = 10 + } + }, + ProgramTypeId = Guid.NewGuid(), + SubAreaId = Guid.NewGuid(), + StudentId = Guid.NewGuid() + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Opened; + var notice = NoticeMock.MockValidNoticeWithId(); + var student = StudentMock.MockValidStudentWithId(); + var activityType = new List { ActivityTypeMock.MockValidActivityType() }; + var projectActivity = ProjectActivityMock.MockValidProjectActivity(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityType); + _projectRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(project); + _projectActivityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectActivity); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new ResumedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + // Add more assertions for the returned result and repository interactions + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_OutsideRegistrationPeriod_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Notice.RegistrationStartDate = DateTime.UtcNow.AddMinutes(30); // Registration period has ended + project.Notice.RegistrationEndDate = DateTime.UtcNow.AddMinutes(30); // Registration period has ended + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Never); + } + + [Theory] + [InlineData(EProjectStatus.Closed)] + [InlineData(EProjectStatus.Canceled)] + public async Task ExecuteAsync_ProjectConcluded_ThrowsUseCaseException(EProjectStatus status) + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = status; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_SubAreaNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var notice = NoticeMock.MockValidNoticeWithId(); + var student = StudentMock.MockValidStudentWithId(); + var activityType = new List { ActivityTypeMock.MockValidActivityType() }; + var projectActivity = ProjectActivityMock.MockValidProjectActivity(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync((Domain.Entities.SubArea)null); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityType); + _projectRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(project); + _projectActivityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectActivity); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProgramTypeNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var notice = NoticeMock.MockValidNoticeWithId(); + var student = StudentMock.MockValidStudentWithId(); + var activityType = new List { ActivityTypeMock.MockValidActivityType() }; + var projectActivity = ProjectActivityMock.MockValidProjectActivity(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync((Domain.Entities.ProgramType)null); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(student); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityType); + _projectRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(project); + _projectActivityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectActivity); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Once); + _programTypeRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProgramTypeId), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_StudentNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var notice = NoticeMock.MockValidNoticeWithId(); + var activityType = new List { ActivityTypeMock.MockValidActivityType() }; + var projectActivity = ProjectActivityMock.MockValidProjectActivity(); + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(project); + _noticeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(notice); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.SubArea); + _programTypeRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.ProgramType); + _professorRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project.Professor); + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync((Domain.Entities.Student)null); + _projectRepositoryMock.Setup(repo => repo.GetStudentProjectsAsync(0, 1, It.IsAny(), false)).ReturnsAsync(new List()); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityType); + _projectRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(project); + _projectActivityRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectActivity); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.SubAreaId), Times.Once); + _programTypeRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProgramTypeId), Times.Once); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(input.StudentId.Value), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateAppealProjectTests.cs b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateAppealProjectTests.cs new file mode 100644 index 00000000..30faeaee --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateAppealProjectTests.cs @@ -0,0 +1,120 @@ +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectEvaluation +{ + public class EvaluateAppealProjectTests + { + private readonly Mock _mapperMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _projectEvaluationRepositoryMock = new(); + + private IEvaluateAppealProject CreateUseCase() + { + return new Application.UseCases.ProjectEvaluation.EvaluateAppealProject( + _mapperMock.Object, + _projectRepositoryMock.Object, + _emailServiceMock.Object, + _tokenAuthenticationServiceMock.Object, + _projectEvaluationRepositoryMock.Object + ); + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new EvaluateAppealProjectInput + { + AppealEvaluationStatus = 4, // Accepted + AppealEvaluationDescription = "Accepted appeal description", + ProjectId = Guid.NewGuid(), + }; + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Evaluation; + project.Notice.AppealStartDate = DateTime.Now.AddDays(-1); + project.Notice.AppealEndDate = DateTime.Now.AddDays(1); + + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + projectEvaluation.Project = project; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _emailServiceMock.Setup(service => service.SendProjectNotificationEmailAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + _projectEvaluationRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(projectEvaluation); + _projectRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(project); + _mapperMock.Setup(mapper => mapper.Map(project)).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectEvaluationRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(input.ProjectId), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _emailServiceMock.Verify(service => service.SendProjectNotificationEmailAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _projectEvaluationRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _projectRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(project), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_UserNotEvaluator_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new EvaluateAppealProjectInput + { + AppealEvaluationStatus = 4, // Accepted + AppealEvaluationDescription = "Accepted appeal description", + ProjectId = Guid.NewGuid(), + }; + + var userClaims = ClaimsMock.MockValidClaims(); + userClaims.Values.First().Role = ERole.STUDENT; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_AppealStatusNotInformed_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new EvaluateAppealProjectInput + { + AppealEvaluationStatus = null, + AppealEvaluationDescription = "Appeal description", + ProjectId = Guid.NewGuid(), + }; + + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateStudentDocumentsTests.cs b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateStudentDocumentsTests.cs new file mode 100644 index 00000000..9e86b910 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateStudentDocumentsTests.cs @@ -0,0 +1,253 @@ +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Tests.Mocks; +using Application.UseCases.ProjectEvaluation; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectEvaluation +{ + public class EvaluateStudentDocumentsTests + { + private readonly Mock _mapperMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _projectEvaluationRepositoryMock = new(); + + private IEvaluateStudentDocuments CreateUseCase() => new EvaluateStudentDocuments( + _mapperMock.Object, + _projectRepositoryMock.Object, + _emailServiceMock.Object, + _tokenAuthenticationServiceMock.Object, + _projectEvaluationRepositoryMock.Object + ); + + public static EvaluateStudentDocumentsInput GetValidInput() + { + var input = new EvaluateStudentDocumentsInput + { + IsDocumentsApproved = true, + DocumentsEvaluationDescription = "Documents approved", + ProjectId = Guid.NewGuid(), + }; + + return input; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.DocumentAnalysis; + project.Notice.SendingDocsStartDate = DateTime.UtcNow.AddDays(-1); + project.Notice.SendingDocsEndDate = DateTime.UtcNow.AddDays(1); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + projectEvaluation.Project = project; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + _emailServiceMock.Setup(service => service.SendProjectNotificationEmailAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + // Add additional assertions here + } + + [Fact] + public async Task ExecuteAsync_UserNotEvaluator_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NotInformedIsDocumentsApproved_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + input.IsDocumentsApproved = null; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_DocumentsEvaluationDescriptionNotInformed_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + input.DocumentsEvaluationDescription = ""; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_AvaliadorIsProfessorOrientador_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + projectEvaluation.Project = project; + projectEvaluation.Project.ProfessorId = userClaims.Values.First().Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotInDocumentAnalysisPhase_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + var project = ProjectMock.MockValidProject(); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + project.Status = EProjectStatus.Canceled; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_NotInDocumentAnalysisPhase_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + project.Notice.SendingDocsStartDate = DateTime.UtcNow.AddDays(1); // Set a future date + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotInDatabase_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_SuccessfulEvaluationApprovedStatus_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.DocumentAnalysis; + project.Notice.SendingDocsStartDate = DateTime.UtcNow.AddDays(-1); + project.Notice.SendingDocsEndDate = DateTime.UtcNow.AddDays(1); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + projectEvaluation.Project = project; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + _emailServiceMock.Setup(service => service.SendProjectNotificationEmailAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task ExecuteAsync_SuccessfulEvaluationNotApprovedStatus_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.DocumentAnalysis; + project.Notice.SendingDocsStartDate = DateTime.UtcNow.AddDays(-1); + project.Notice.SendingDocsEndDate = DateTime.UtcNow.AddDays(1); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + projectEvaluation.Project = project; + input.IsDocumentsApproved = false; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + _emailServiceMock.Setup(service => service.SendProjectNotificationEmailAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateSubmissionProjectTests.cs b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateSubmissionProjectTests.cs new file mode 100644 index 00000000..bab61c47 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectEvaluation/EvaluateSubmissionProjectTests.cs @@ -0,0 +1,128 @@ +using Xunit; +using Moq; +using AutoMapper; +using System; +using System.Collections.Generic; +using Application.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Tests.Mocks; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.ProjectActivity; +using Domain.Entities; + +namespace Application.Tests.UseCases.ProjectEvaluation +{ + public class EvaluateSubmissionProjectTests + { + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _projectActivityRepositoryMock = new(); + private readonly Mock _activityTypeRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _projectEvaluationRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IEvaluateSubmissionProject CreateUseCase() => new EvaluateSubmissionProject( + _mapperMock.Object, + _projectRepositoryMock.Object, + _tokenAuthenticationServiceMock.Object, + _projectActivityRepositoryMock.Object, + _activityTypeRepositoryMock.Object, + _emailServiceMock.Object, + _projectEvaluationRepositoryMock.Object + ); + + public static EvaluateSubmissionProjectInput GetValidInput() + { + return new EvaluateSubmissionProjectInput + { + ProjectId = Guid.NewGuid(), + IsProductivityFellow = true, + SubmissionEvaluationStatus = 1, + SubmissionEvaluationDescription = "This is a valid description.", + Activities = new List + { + new EvaluateProjectActivityInput + { + ActivityId = Guid.NewGuid(), + FoundActivities = 3 + }, + }, + Qualification = 1, + ProjectProposalObjectives = 1, + AcademicScientificProductionCoherence = 2, + ProposalMethodologyAdaptation = 3, + EffectiveContributionToResearch = 4 + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Notice.EvaluationStartDate = DateTime.Now.AddDays(-1); + project.Notice.EvaluationEndDate = DateTime.Now.AddDays(1); + project.Status = EProjectStatus.Submitted; + var activityTypes = new List() { ActivityTypeMock.MockValidActivityType() }; + var projectActivities = new List() { ProjectActivityMock.MockValidProjectActivity() }; + + // Setup Mocks as needed + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.ProjectEvaluation)null); + _projectRepositoryMock.Setup>(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _activityTypeRepositoryMock.Setup(repo => repo.GetByNoticeIdAsync(It.IsAny())).ReturnsAsync(activityTypes); + _projectActivityRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(It.IsAny())).ReturnsAsync(projectActivities); + _projectActivityRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(ProjectActivityMock.MockValidProjectActivity()); + _projectEvaluationRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(ProjectEvaluationMock.MockValidProjectEvaluation()); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task ExecuteAsync_UserNotEvaluator_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + // Set user claims to a non-evaluator role + var userClaims = ClaimsMock.MockValidClaims(); + userClaims.Values.FirstOrDefault().Role = ERole.STUDENT; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectAlreadyEvaluated_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var projectEvaluation = ProjectEvaluationMock.MockValidProjectEvaluation(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectEvaluationRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(projectEvaluation); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectEvaluation/GetEvaluationByProjectIdTests.cs b/src/Application.Tests/UseCases/ProjectEvaluation/GetEvaluationByProjectIdTests.cs new file mode 100644 index 00000000..5e393312 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectEvaluation/GetEvaluationByProjectIdTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading.Tasks; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.ProjectEvaluation; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectEvaluation +{ + public class GetEvaluationByProjectIdTests + { + private readonly Mock _mapperMock = new Mock(); + private readonly Mock _repositoryMock = new Mock(); + + private IGetEvaluationByProjectId CreateUseCase() => new Application.UseCases.ProjectEvaluation.GetEvaluationByProjectId( + _mapperMock.Object, + _repositoryMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidProjectId_ReturnsDetailedReadProjectEvaluationOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var projectEvaluationEntity = ProjectEvaluationMock.MockValidProjectEvaluation(); + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(projectEvaluationEntity); + _mapperMock.Setup(mapper => mapper.Map(projectEvaluationEntity)).Returns(new DetailedReadProjectEvaluationOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectEvaluationEntity), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullProjectId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? projectId = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync((Domain.Entities.ProjectEvaluation)null); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.Null(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectFinalReport/CreateProjectFinalReportTests.cs b/src/Application.Tests/UseCases/ProjectFinalReport/CreateProjectFinalReportTests.cs new file mode 100644 index 00000000..f8465669 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectFinalReport/CreateProjectFinalReportTests.cs @@ -0,0 +1,223 @@ +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Tests.Mocks; +using Application.UseCases.ProjectFinalReport; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Microsoft.AspNetCore.Http; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectFinalReport +{ + public class CreateProjectFinalReportTests + { + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateProjectFinalReport CreateUseCase() => new CreateProjectFinalReport( + _projectReportRepositoryMock.Object, + _projectRepositoryMock.Object, + _storageFileServiceMock.Object, + _tokenAuthenticationServiceMock.Object, + _mapperMock.Object + ); + + public static CreateProjectFinalReportInput GetValidInput() + { + // Create and return a valid input for testing + var input = new CreateProjectFinalReportInput + { + ProjectId = Guid.NewGuid(), + ReportFile = FileMock.CreateIFormFile() + }; + return input; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectFinalReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var userClaim = userClaims.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + var projectFinalReport = ProjectFinalReportMock.MockValidProjectFinalReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Started; + project.ProfessorId = actorId; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(input.ReportFile, null)).ReturnsAsync("file-url"); + _projectReportRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(projectFinalReport); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectFinalReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(input.ReportFile, null), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Never); + + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ProjectDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.DeactivateEntity(); + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Never); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotStarted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Opened; + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Never); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ActorNotStudentOrProfessor_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Started; + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Never); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ReportOutsideDeadline_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Started; + project.Notice!.FinalReportDeadline = DateTime.UtcNow.AddMonths(-7); + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Never); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_ReportFileInvalid_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + input.ReportFile = null; + + var userClaims = ClaimsMock.MockValidClaims(); + var userClaim = userClaims.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + var projectFinalReport = ProjectFinalReportMock.MockValidProjectFinalReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Started; + project.ProfessorId = actorId; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(input.ReportFile, null)).ReturnsAsync((string)null); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(input.ReportFile, null), Times.Never); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectFinalReport/DeleteProjectFinalReportTests.cs b/src/Application.Tests/UseCases/ProjectFinalReport/DeleteProjectFinalReportTests.cs new file mode 100644 index 00000000..d10e6149 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectFinalReport/DeleteProjectFinalReportTests.cs @@ -0,0 +1,58 @@ +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Tests.Mocks; +using Application.UseCases.ProjectFinalReport; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectFinalReport +{ + public class DeleteProjectFinalReportTests + { + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteProjectFinalReport CreateUseCase() => new DeleteProjectFinalReport( + _projectReportRepositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProjectFinalReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var projectFinalReport = ProjectFinalReportMock.MockValidProjectFinalReport(); + projectFinalReport.ProjectId = projectId; + var detailedReadOutput = new DetailedReadProjectFinalReportOutput(); + + _projectReportRepositoryMock.Setup(repo => repo.DeleteAsync(projectId)).ReturnsAsync(projectFinalReport); + _mapperMock.Setup(mapper => mapper.Map(projectFinalReport)).Returns(detailedReadOutput); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + Assert.Equal(detailedReadOutput, result); + _projectReportRepositoryMock.Verify(repo => repo.DeleteAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectFinalReport), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + _projectReportRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByIdTests.cs b/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByIdTests.cs new file mode 100644 index 00000000..474588c5 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByIdTests.cs @@ -0,0 +1,70 @@ +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectFinalReport +{ + public class GetProjectFinalReportByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectFinalReportById CreateUseCase() => + new Application.UseCases.ProjectFinalReport.GetProjectFinalReportById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProjectFinalReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectFinalReport = ProjectFinalReportMock.MockValidProjectFinalReportWithId(); + var id = projectFinalReport.Id; + var detailedReadProjectFinalReportOutput = new DetailedReadProjectFinalReportOutput(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectFinalReport); + _mapperMock.Setup(mapper => mapper.Map(projectFinalReport)) + .Returns(detailedReadProjectFinalReportOutput); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + Assert.Same(detailedReadProjectFinalReportOutput, result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectFinalReport), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + } + + [Fact] + public async Task ExecuteAsync_NonExistentId_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.ProjectFinalReport)null); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.Null(result); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectIdTests.cs b/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectIdTests.cs new file mode 100644 index 00000000..5379b7b3 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectIdTests.cs @@ -0,0 +1,58 @@ +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectFinalReport +{ + public class GetProjectFinalReportByProjectIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectFinalReportByProjectId CreateUseCase() => new Application.UseCases.ProjectFinalReport.GetProjectFinalReportByProjectId( + _repositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidProjectId_ReturnsDetailedReadProjectFinalReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var report = ProjectFinalReportMock.MockValidProjectFinalReportWithId(); + var projectId = report.ProjectId; + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(report); + _mapperMock.Setup(mapper => mapper.Map(report)).Returns(new DetailedReadProjectFinalReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(report), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullProjectId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? projectId = null; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + + // Assert + UseCaseException.NotInformedParam(It.IsAny(), It.IsAny()); // Make sure this method is called with any boolean and string. + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectFinalReport/UpdateProjectFinalReportTests.cs b/src/Application.Tests/UseCases/ProjectFinalReport/UpdateProjectFinalReportTests.cs new file mode 100644 index 00000000..daa260d3 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectFinalReport/UpdateProjectFinalReportTests.cs @@ -0,0 +1,103 @@ +using Xunit; +using Moq; +using Application.UseCases.ProjectFinalReport; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using System; +using System.Threading.Tasks; +using Application.Validation; +using AutoMapper; +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Domain.Entities.Enums; +using Application.Tests.Mocks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; + +namespace Application.Tests.UseCases.ProjectFinalReport +{ + public class UpdateProjectFinalReportTests + { + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateProjectFinalReport CreateUseCase() => new UpdateProjectFinalReport( + _projectReportRepositoryMock.Object, + _projectRepositoryMock.Object, + _storageFileServiceMock.Object, + _tokenAuthenticationServiceMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectFinalReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateProjectFinalReportInput + { + ReportFile = FileMock.CreateIFormFile() + }; + var report = ProjectFinalReportMock.MockValidProjectFinalReportWithId(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = EProjectStatus.Started; + + var userClaims = ClaimsMock.MockValidClaims(); + project.ProfessorId = userClaims.Keys.FirstOrDefault(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(report); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(project); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadProjectFinalReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), It.IsAny()), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NoId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + var input = new UpdateProjectFinalReportInput + { + ReportFile = FileMock.CreateIFormFile() + }; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_NoReportFile_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateProjectFinalReportInput + { + ReportFile = null // No report file provided + }; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectPartialReport/CreateProjectPartialReportTests.cs b/src/Application.Tests/UseCases/ProjectPartialReport/CreateProjectPartialReportTests.cs new file mode 100644 index 00000000..f74dd615 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectPartialReport/CreateProjectPartialReportTests.cs @@ -0,0 +1,162 @@ +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Tests.Mocks; +using Application.UseCases.ProjectPartialReport; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectPartialReport +{ + public class CreateProjectPartialReportTests + { + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateProjectPartialReport CreateUseCase() => + new CreateProjectPartialReport(_projectReportRepositoryMock.Object, _projectRepositoryMock.Object, _tokenAuthenticationServiceMock.Object, _mapperMock.Object); + + private CreateProjectPartialReportInput GetValidInput() + { + return new CreateProjectPartialReportInput + { + ProjectId = Guid.NewGuid(), + CurrentDevelopmentStage = 1, + ScholarPerformance = 0, + AdditionalInfo = "Additional Information", + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.StudentId = userClaims.Keys.FirstOrDefault(); + project.Status = EProjectStatus.Started; + input.ProjectId = project.Id; + var report = ProjectPartialReportMock.MockValidProjectPartialReport(); + report.UserId = userClaims.Values.FirstOrDefault().Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _projectReportRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())) + .ReturnsAsync(report); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadProjectPartialReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.DeactivateEntity(); + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotStarted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.StudentId = userClaims.Keys.FirstOrDefault(); + project.Status = EProjectStatus.Opened; + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_NotAllowedActor_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + input.ProjectId = project.Id; + project.Status = EProjectStatus.Started; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + + [Fact] + public async Task ExecuteAsync_ReportOutsideDeadline_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + + var userClaims = ClaimsMock.MockValidClaims(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Notice.PartialReportDeadline = DateTime.UtcNow.AddMonths(12); + project.StudentId = userClaims.Keys.FirstOrDefault(); + project.Status = EProjectStatus.Started; + input.ProjectId = project.Id; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectPartialReport/DeleteProjectPartialReportTests.cs b/src/Application.Tests/UseCases/ProjectPartialReport/DeleteProjectPartialReportTests.cs new file mode 100644 index 00000000..184fced0 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectPartialReport/DeleteProjectPartialReportTests.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Validation; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.ProjectPartialReport +{ + public class DeleteProjectPartialReportTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteProjectPartialReport CreateUseCase() => + new DeleteProjectPartialReport(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var projectPartialReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(projectPartialReport); + _mapperMock.Setup(mapper => mapper.Map(projectPartialReport)) + .Returns(new DetailedReadProjectPartialReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectPartialReport), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + } + } +} \ No newline at end of file diff --git a/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByIdTests.cs b/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByIdTests.cs new file mode 100644 index 00000000..5fc8349f --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByIdTests.cs @@ -0,0 +1,70 @@ +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectPartialReport +{ + public class GetProjectPartialReportByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectPartialReportById CreateUseCase() => new Application.UseCases.ProjectPartialReport.GetProjectPartialReportById(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var projectPartialReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var detailedReadProjectPartialReportOutput = new DetailedReadProjectPartialReportOutput(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectPartialReport); + _mapperMock.Setup(mapper => mapper.Map(projectPartialReport)).Returns(detailedReadProjectPartialReportOutput); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectPartialReport), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + } + + [Fact] + public async Task ExecuteAsync_IdNotFound_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.ProjectPartialReport)null); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadProjectPartialReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectIdTests.cs b/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectIdTests.cs new file mode 100644 index 00000000..e0b9ca34 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectIdTests.cs @@ -0,0 +1,49 @@ +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectPartialReport +{ + public class GetProjectPartialReportByProjectIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetProjectPartialReportByProjectId CreateUseCase() => new Application.UseCases.ProjectPartialReport.GetProjectPartialReportByProjectId(_repositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_ValidProjectId_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var projectId = Guid.NewGuid(); + var useCase = CreateUseCase(); + var projectPartialReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(projectPartialReport); + _mapperMock.Setup(mapper => mapper.Map(projectPartialReport)).Returns(new DetailedReadProjectPartialReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(projectPartialReport), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullProjectId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + } + } +} diff --git a/src/Application.Tests/UseCases/ProjectPartialReport/UpdateProjectPartialReportTests.cs b/src/Application.Tests/UseCases/ProjectPartialReport/UpdateProjectPartialReportTests.cs new file mode 100644 index 00000000..66b98475 --- /dev/null +++ b/src/Application.Tests/UseCases/ProjectPartialReport/UpdateProjectPartialReportTests.cs @@ -0,0 +1,237 @@ +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Tests.Mocks; +using Application.UseCases.ProjectPartialReport; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.ProjectPartialReport +{ + public class UpdateProjectPartialReportTests + { + private readonly Mock _projectReportRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateProjectPartialReport CreateUseCase() => new UpdateProjectPartialReport( + _projectReportRepositoryMock.Object, + _projectRepositoryMock.Object, + _tokenAuthenticationServiceMock.Object, + _mapperMock.Object + ); + + private static UpdateProjectPartialReportInput GetValidInput() + { + return new UpdateProjectPartialReportInput + { + CurrentDevelopmentStage = 1, + ScholarPerformance = 2, + AdditionalInfo = "Additional info" + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadProjectPartialReportOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = Domain.Entities.Enums.EProjectStatus.Started; + project.StudentId = userClaims.Keys.FirstOrDefault(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + _projectReportRepositoryMock.Setup(repo => repo.UpdateAsync(projectReport)).ReturnsAsync(projectReport); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadProjectPartialReportOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(projectReport.ProjectId), Times.Once); + _projectReportRepositoryMock.Verify(repo => repo.UpdateAsync(projectReport), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_ReportNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.ProjectPartialReport)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_ReportDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + projectReport.DeactivateEntity(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProject(); + project.DeactivateEntity(); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_ProjectNotStarted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProject(); + project.Status = Domain.Entities.Enums.EProjectStatus.Opened; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_UserNotAuthorized_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + input.CurrentDevelopmentStage = 0; + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = Domain.Entities.Enums.EProjectStatus.Started; + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_StageOutOfRange_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + input.CurrentDevelopmentStage = 0; + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = Domain.Entities.Enums.EProjectStatus.Started; + project.StudentId = userClaims.Keys.FirstOrDefault(); + project.Notice.PartialReportDeadline = DateTime.Now.AddMonths(7); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_ReportSentAfterDeadline_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + input.CurrentDevelopmentStage = 0; + var id = Guid.NewGuid(); + var userClaims = ClaimsMock.MockValidClaims(); + + var projectReport = ProjectPartialReportMock.MockValidProjectPartialReport(); + var project = ProjectMock.MockValidProjectProfessorAndNotice(); + project.Status = Domain.Entities.Enums.EProjectStatus.Started; + project.StudentId = userClaims.Keys.FirstOrDefault(); + project.Notice.PartialReportDeadline = DateTime.Now.AddMonths(-7); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _projectReportRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(projectReport); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(projectReport.ProjectId)).ReturnsAsync(project); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/CreateStudentTests.cs b/src/Application.Tests/UseCases/Student/CreateStudentTests.cs new file mode 100644 index 00000000..f000ce0f --- /dev/null +++ b/src/Application.Tests/UseCases/Student/CreateStudentTests.cs @@ -0,0 +1,271 @@ +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Student +{ + public class CreateStudentTests + { + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _campusRepositoryMock = new(); + private readonly Mock _courseRepositoryMock = new(); + private readonly Mock _emailServiceMock = new(); + private readonly Mock _hashServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateStudent CreateUseCase() => new Application.UseCases.Student.CreateStudent( + _studentRepositoryMock.Object, + _userRepositoryMock.Object, + _campusRepositoryMock.Object, + _courseRepositoryMock.Object, + _emailServiceMock.Object, + _hashServiceMock.Object, + _mapperMock.Object + ); + public static CreateStudentInput GetValidInput() + { + return new CreateStudentInput + { + // Required Properties + Name = "John Doe", + CPF = "76500130065", + Email = "john.doe@example.com", + Password = "SecurePassword", + RegistrationCode = "ABC123", + BirthDate = new DateTime(1990, 1, 1), + RG = 1234567, + IssuingAgency = "Agency", + DispatchDate = new DateTime(2000, 1, 1), + Gender = 0, // Assuming 1 represents a gender value + Race = 0, // Assuming 2 represents a race value + HomeAddress = "123 Main St", + City = "City", + UF = "CA", + CEP = 123456, + CampusId = Guid.NewGuid(), + CourseId = Guid.NewGuid(), + StartYear = "2023", + AssistanceTypeId = Guid.NewGuid(), + + // Optional Properties + PhoneDDD = 123, + Phone = 987654321, + CellPhoneDDD = 456, + CellPhone = 654321987 + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadStudentOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync(CourseMock.MockValidCourse()); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync(CampusMock.MockValidCampus()); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Once); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_UserWithEmailExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync(user); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync(CourseMock.MockValidCourse()); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync(CampusMock.MockValidCampus()); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + + // Assert + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Never); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_UserWithCPFExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync(user); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync(CourseMock.MockValidCourse()); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync(CampusMock.MockValidCampus()); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + + // Assert + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Once); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_CourseNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync((Domain.Entities.Course)null); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync(CampusMock.MockValidCampus()); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + + // Assert + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Once); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_CampusNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync(CourseMock.MockValidCourse()); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync((Domain.Entities.Campus)null); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + + // Assert + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Once); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_NullPassword_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = GetValidInput(); + var user = UserMock.MockValidUser(); + var student = StudentMock.MockValidStudent(); + input.Password = null; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(input.Email)).ReturnsAsync((Domain.Entities.User)null); + _userRepositoryMock.Setup(repo => repo.GetUserByCPFAsync(input.CPF)).ReturnsAsync((Domain.Entities.User)null); + _courseRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CourseId)).ReturnsAsync(CourseMock.MockValidCourse()); + _campusRepositoryMock.Setup(repo => repo.GetByIdAsync(input.CampusId)).ReturnsAsync(CampusMock.MockValidCampus()); + _hashServiceMock.Setup(service => service.HashPassword(input.Password)).Returns("hashedPassword"); + _userRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(student); + _emailServiceMock.Setup(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny())); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(input)); + + // Assert + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetUserByCPFAsync(It.IsAny()), Times.Once); + _courseRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _campusRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _hashServiceMock.Verify(service => service.HashPassword(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _studentRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendConfirmationEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/DeleteStudentTests.cs b/src/Application.Tests/UseCases/Student/DeleteStudentTests.cs new file mode 100644 index 00000000..1bb1a3ec --- /dev/null +++ b/src/Application.Tests/UseCases/Student/DeleteStudentTests.cs @@ -0,0 +1,133 @@ +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Tests.Mocks; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Student +{ + public class DeleteStudentTests + { + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteStudent CreateUseCase() => new Application.UseCases.Student.DeleteStudent( + _studentRepositoryMock.Object, + _userRepositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidId_DeletesStudentAndUser() + { + // Arrange + var useCase = CreateUseCase(); + var student = StudentMock.MockValidStudentWithId(); + var user = UserMock.MockValidUser(); + var studentId = student.Id; + var userId = student.UserId; + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync(user); + _studentRepositoryMock.Setup(repo => repo.DeleteAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.DeleteAsync(userId)).ReturnsAsync(user); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + var result = await useCase.ExecuteAsync(studentId); + + // Assert + Assert.NotNull(result); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(studentId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userId), Times.Once); + _studentRepositoryMock.Verify(repo => repo.DeleteAsync(studentId), Times.Once); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(userId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + } + + [Fact] + public async Task ExecuteAsync_InvalidStudentId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var studentId = Guid.NewGuid(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync((Domain.Entities.Student)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(studentId)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_InvalidUserId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var student = StudentMock.MockValidStudentWithId(); + var user = UserMock.MockValidUser(); + var studentId = student.Id; + var userId = student.UserId; + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync((Domain.Entities.User)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(studentId)); + _studentRepositoryMock.Verify(repo => repo.DeleteAsync(studentId), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_StudentNotDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var student = StudentMock.MockValidStudentWithId(); + var user = UserMock.MockValidUser(); + var studentId = student.Id; + var userId = student.UserId; + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync(UserMock.MockValidUser()); + _studentRepositoryMock.Setup(repo => repo.DeleteAsync(studentId)).ReturnsAsync((Domain.Entities.Student)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(studentId)); + _userRepositoryMock.Verify(repo => repo.DeleteAsync(userId), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_UserNotDeleted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var student = StudentMock.MockValidStudentWithId(); + var user = UserMock.MockValidUser(); + var studentId = student.Id; + var userId = student.UserId; + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync(UserMock.MockValidUser()); + _studentRepositoryMock.Setup(repo => repo.DeleteAsync(studentId)).ReturnsAsync(student); + _userRepositoryMock.Setup(repo => repo.DeleteAsync(userId)).ReturnsAsync((Domain.Entities.User)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(studentId)); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/GetStudentByIdTests.cs b/src/Application.Tests/UseCases/Student/GetStudentByIdTests.cs new file mode 100644 index 00000000..5cdaaf48 --- /dev/null +++ b/src/Application.Tests/UseCases/Student/GetStudentByIdTests.cs @@ -0,0 +1,70 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; +using Application.Tests.Mocks; +using Application.Interfaces.UseCases.Student; + +namespace Application.Tests.UseCases.Student +{ + public class GetStudentByIdTests + { + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetStudentById CreateUseCase() => new GetStudentById( + _studentRepositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadStudentOutput() + { + // Arrange + var useCase = CreateUseCase(); + var studentId = Guid.NewGuid(); + var student = StudentMock.MockValidStudent(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync(student); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + var result = await useCase.ExecuteAsync(studentId); + + // Assert + Assert.NotNull(result); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(studentId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + } + + [Fact] + public async Task ExecuteAsync_StudentNotFound_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + var studentId = Guid.NewGuid(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(studentId)).ReturnsAsync((Domain.Entities.Student)null); + + // Act + var result = await useCase.ExecuteAsync(studentId); + + // Assert + Assert.Null(result); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(studentId), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/GetStudentByRegistrationCodeTests.cs b/src/Application.Tests/UseCases/Student/GetStudentByRegistrationCodeTests.cs new file mode 100644 index 00000000..52117884 --- /dev/null +++ b/src/Application.Tests/UseCases/Student/GetStudentByRegistrationCodeTests.cs @@ -0,0 +1,69 @@ +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Tests.Mocks; +using Application.UseCases.Student; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Student +{ + public class GetStudentByRegistrationCodeTests + { + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetStudentByRegistrationCode CreateUseCase() => + new GetStudentByRegistrationCode(_studentRepositoryMock.Object, _mapperMock.Object); + + [Fact] + public async Task ExecuteAsync_WithValidRegistrationCode_ReturnsDetailedReadStudentOutput() + { + // Arrange + var useCase = CreateUseCase(); + var registrationCode = "ABC123"; + var student = StudentMock.MockValidStudent(); + var expectedOutput = new DetailedReadStudentOutput(); + + _studentRepositoryMock.Setup(repo => repo.GetByRegistrationCodeAsync(registrationCode)).ReturnsAsync(student); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(expectedOutput); + + // Act + var result = await useCase.ExecuteAsync(registrationCode); + + // Assert + Assert.NotNull(result); + _studentRepositoryMock.Verify(repo => repo.GetByRegistrationCodeAsync(registrationCode), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_WithEmptyRegistrationCode_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + string registrationCode = string.Empty; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(registrationCode)); + _studentRepositoryMock.Verify(repo => repo.GetByRegistrationCodeAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_WithInvalidRegistrationCode_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + string registrationCode = "InvalidCode"; + _studentRepositoryMock.Setup(repo => repo.GetByRegistrationCodeAsync(registrationCode)).ReturnsAsync((Domain.Entities.Student)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(registrationCode)); + _studentRepositoryMock.Verify(repo => repo.GetByRegistrationCodeAsync(registrationCode), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/GetStudentsTests.cs b/src/Application.Tests/UseCases/Student/GetStudentsTests.cs new file mode 100644 index 00000000..7edaf815 --- /dev/null +++ b/src/Application.Tests/UseCases/Student/GetStudentsTests.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Moq; +using Xunit; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.Student +{ + public class GetStudentsTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetStudents CreateUseCase() => new Application.UseCases.Student.GetStudents( + _repositoryMock.Object, + _mapperMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidParameters_ReturnsQueryable() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 10; + var students = new List { StudentMock.MockValidStudent() }; + var mappedStudents = new List { new() }; + + _repositoryMock.Setup(repo => repo.GetAllAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(students); + _mapperMock.Setup(mapper => mapper.Map>(It.IsAny>())) + .Returns(mappedStudents.AsEnumerable()); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetAllAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(students), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidSkipValue_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = -1; + int take = 10; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + + // No need to verify any repository or mapper calls in this case. + } + + [Fact] + public async Task ExecuteAsync_InvalidTakeValue_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 0; + + // Act & Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(skip, take)); + + // No need to verify any repository or mapper calls in this case. + } + } +} diff --git a/src/Application.Tests/UseCases/Student/RequestStudentRegisterTests.cs b/src/Application.Tests/UseCases/Student/RequestStudentRegisterTests.cs new file mode 100644 index 00000000..b951915b --- /dev/null +++ b/src/Application.Tests/UseCases/Student/RequestStudentRegisterTests.cs @@ -0,0 +1,100 @@ +using Application.Interfaces.UseCases.Student; +using Application.Tests.Mocks; +using Application.UseCases.Student; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.Student +{ + public class RequestStudentRegisterTests + { + private readonly Mock _emailServiceMock = new(); + private readonly Mock _userRepositoryMock = new(); + + private IRequestStudentRegister CreateUseCase() => new RequestStudentRegister( + _emailServiceMock.Object, + _userRepositoryMock.Object + ); + + [Fact] + public async Task ExecuteAsync_ValidEmail_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var email = "john.doe@aluno.cefet-rj.br"; + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync((Domain.Entities.User)null); + + // Act + var result = await useCase.ExecuteAsync(email); + + // Assert + Assert.Equal("Solicitação de registro enviada com sucesso.", result); + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _emailServiceMock.Verify(service => service.SendRequestStudentRegisterEmailAsync(email), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_EmptyEmail_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + string email = null; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendRequestStudentRegisterEmailAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_InvalidEmail_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var email = "invalid.email"; + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendRequestStudentRegisterEmailAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_EmailAlreadyExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var email = "john.doe@aluno.cefet-rj.br"; + var user = UserMock.MockValidUser(); + + _userRepositoryMock.Setup(repo => repo.GetUserByEmailAsync(email)).ReturnsAsync(user); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(email), Times.Once); + _emailServiceMock.Verify(service => service.SendRequestStudentRegisterEmailAsync(It.IsAny()), Times.Never); + } + + [Theory] + [InlineData("jane.doe@domain.com")] + [InlineData("12345678912@invalid-domain.com")] + public async Task ExecuteAsync_InvalidStudentEmail_ThrowsUseCaseException(string email) + { + // Arrange + var useCase = CreateUseCase(); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(email)); + + _userRepositoryMock.Verify(repo => repo.GetUserByEmailAsync(It.IsAny()), Times.Never); + _emailServiceMock.Verify(service => service.SendRequestStudentRegisterEmailAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/Student/UpdateStudentTests.cs b/src/Application.Tests/UseCases/Student/UpdateStudentTests.cs new file mode 100644 index 00000000..fdb834c6 --- /dev/null +++ b/src/Application.Tests/UseCases/Student/UpdateStudentTests.cs @@ -0,0 +1,116 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.UseCases.Student; +using Application.Validation; +using Moq; +using Xunit; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.Student +{ + public class UpdateStudentTests + { + private readonly Mock _studentRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateStudent CreateUseCase() => new UpdateStudent(_studentRepositoryMock.Object, _mapperMock.Object); + public static UpdateStudentInput GetValidInput() + { + return new UpdateStudentInput + { + // Required Properties + RegistrationCode = "ABC123", + BirthDate = new DateTime(1990, 1, 1), + RG = 1234567, + IssuingAgency = "Agency", + DispatchDate = new DateTime(2000, 1, 1), + Gender = 0, // Assuming 1 represents a gender value + Race = 0, // Assuming 2 represents a race value + HomeAddress = "123 Main St", + City = "City", + UF = "CA", + CEP = 123456, + CampusId = Guid.NewGuid(), + CourseId = Guid.NewGuid(), + StartYear = "2023", + AssistanceTypeId = Guid.NewGuid(), + + // Optional Properties + PhoneDDD = 123, + Phone = 987654321, + CellPhoneDDD = 456, + CellPhone = 654321987 + }; + } + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadStudentOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = GetValidInput(); + var student = StudentMock.MockValidStudent(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(student); + _studentRepositoryMock.Setup(repo => repo.UpdateAsync(student)).ReturnsAsync(student); + _mapperMock.Setup(mapper => mapper.Map(student)).Returns(new DetailedReadStudentOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + _studentRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _studentRepositoryMock.Verify(repo => repo.UpdateAsync(student), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(student), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + var input = new UpdateStudentInput(); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + } + + [Fact] + public async Task ExecuteAsync_StudentNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateStudentInput(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.Student)null); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _studentRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public async Task ExecuteAsync_DeletedStudent_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateStudentInput(); + var student = StudentMock.MockValidStudent(); + student.DeactivateEntity(); + + _studentRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(student); + + // Act and Assert + await Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _studentRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/StudentDocuments/CreateStudentDocumentsTests.cs b/src/Application.Tests/UseCases/StudentDocuments/CreateStudentDocumentsTests.cs new file mode 100644 index 00000000..36ba5baf --- /dev/null +++ b/src/Application.Tests/UseCases/StudentDocuments/CreateStudentDocumentsTests.cs @@ -0,0 +1,150 @@ +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Tests.Mocks; +using Application.UseCases.StudentDocuments; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Microsoft.AspNetCore.Http; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.StudentDocuments +{ + public class CreateStudentDocumentsTests + { + private readonly Mock _studentDocumentRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateStudentDocuments CreateUseCase() => new CreateStudentDocuments(_studentDocumentRepositoryMock.Object, _projectRepositoryMock.Object, + _storageFileServiceMock.Object, _mapperMock.Object); + private static Domain.Entities.StudentDocuments MockValidStudentDocuments() => new(Guid.NewGuid(), "123456", "7890"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadStudentDocumentsOutput() + { + // Arrange + var useCase = CreateUseCase(); + var project = ProjectMock.MockValidProject(); + project.Status = EProjectStatus.Accepted; + var input = new CreateStudentDocumentsInput + { + ProjectId = Guid.NewGuid(), + AgencyNumber = "123456", + AccountNumber = "7890", + IdentityDocument = FileMock.CreateIFormFile(), + CPF = FileMock.CreateIFormFile(), + Photo3x4 = FileMock.CreateIFormFile(), + SchoolHistory = FileMock.CreateIFormFile(), + ScholarCommitmentAgreement = FileMock.CreateIFormFile(), + AccountOpeningProof = FileMock.CreateIFormFile() + }; + + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + _studentDocumentRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + _storageFileServiceMock.Setup(service => service.UploadFileAsync(It.IsAny(), null)).ReturnsAsync("file_url"); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadStudentDocumentsOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), null), Times.Exactly(6)); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_DocumentsExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateStudentDocumentsInput + { + ProjectId = Guid.NewGuid(), + IdentityDocument = FileMock.CreateIFormFile(), + CPF = FileMock.CreateIFormFile(), + Photo3x4 = FileMock.CreateIFormFile(), + SchoolHistory = FileMock.CreateIFormFile(), + ScholarCommitmentAgreement = FileMock.CreateIFormFile(), + AccountOpeningProof = FileMock.CreateIFormFile() + }; + + var existingDocuments = MockValidStudentDocuments(); + existingDocuments.ProjectId = input.ProjectId; + + _studentDocumentRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync(existingDocuments); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(input.ProjectId), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Never); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), ""), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + + [Fact] + public void ExecuteAsync_ProjectNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateStudentDocumentsInput + { + ProjectId = Guid.NewGuid(), + IdentityDocument = FileMock.CreateIFormFile(), + CPF = FileMock.CreateIFormFile(), + Photo3x4 = FileMock.CreateIFormFile(), + SchoolHistory = FileMock.CreateIFormFile(), + ScholarCommitmentAgreement = FileMock.CreateIFormFile(), + AccountOpeningProof = FileMock.CreateIFormFile() + }; + + _studentDocumentRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.Project)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(input.ProjectId), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), ""), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_ProjectNotAccepted_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var project = ProjectMock.MockValidProject(); + project.Status = EProjectStatus.Pending; // Not Accepted + var input = new CreateStudentDocumentsInput + { + ProjectId = Guid.NewGuid(), + IdentityDocument = FileMock.CreateIFormFile(), + CPF = FileMock.CreateIFormFile(), + Photo3x4 = FileMock.CreateIFormFile(), + SchoolHistory = FileMock.CreateIFormFile(), + ScholarCommitmentAgreement = FileMock.CreateIFormFile(), + AccountOpeningProof = FileMock.CreateIFormFile() + }; + + _studentDocumentRepositoryMock.Setup(repo => repo.GetByProjectIdAsync(input.ProjectId)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + _projectRepositoryMock.Setup(repo => repo.GetByIdAsync(input.ProjectId)).ReturnsAsync(project); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByProjectIdAsync(input.ProjectId), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(input.ProjectId), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), ""), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/StudentDocuments/DeleteStudentDocumentsTests.cs b/src/Application.Tests/UseCases/StudentDocuments/DeleteStudentDocumentsTests.cs new file mode 100644 index 00000000..2e4a3388 --- /dev/null +++ b/src/Application.Tests/UseCases/StudentDocuments/DeleteStudentDocumentsTests.cs @@ -0,0 +1,68 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.StudentDocuments; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.StudentDocuments; + +namespace Application.Tests.UseCases.StudentDocuments +{ + public class DeleteStudentDocumentsTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteStudentDocuments CreateUseCase() => new DeleteStudentDocuments(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.StudentDocuments MockValidStudentDocuments() => new(Guid.NewGuid(), "123456", "7890"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadStudentDocumentsOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var deletedStudentDocuments = MockValidStudentDocuments(); + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync(deletedStudentDocuments); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new DetailedReadStudentDocumentsOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_RepositoryReturnsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.DeleteAsync(id)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.DeleteAsync(id), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByProjectIdTests.cs b/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByProjectIdTests.cs new file mode 100644 index 00000000..abe49ab2 --- /dev/null +++ b/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByProjectIdTests.cs @@ -0,0 +1,71 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.StudentDocuments; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.StudentDocuments; + +namespace Application.Tests.UseCases.StudentDocuments +{ + public class GetStudentDocumentsByProjectIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetStudentDocumentsByProjectId CreateUseCase() => new GetStudentDocumentsByProjectId(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.StudentDocuments MockValidStudentDocuments() => new(Guid.NewGuid(), "123456", "7890"); + + [Fact] + public async Task ExecuteAsync_ValidProjectId_ReturnsResumedReadStudentDocumentsOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + var studentDocuments = MockValidStudentDocuments(); + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync(studentDocuments); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new ResumedReadStudentDocumentsOutput()); + + // Act + var result = await useCase.ExecuteAsync(projectId); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_ProjectIdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? projectId = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(projectId)); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_RepositoryReturnsNull_ReturnsNullOutput() + { + // Arrange + var useCase = CreateUseCase(); + var projectId = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByProjectIdAsync(projectId)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + + // Act + var result = useCase.ExecuteAsync(projectId).Result; + + // Assert + Assert.Null(result); + _repositoryMock.Verify(repo => repo.GetByProjectIdAsync(projectId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByStudentIdTests.cs b/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByStudentIdTests.cs new file mode 100644 index 00000000..b62d8cb8 --- /dev/null +++ b/src/Application.Tests/UseCases/StudentDocuments/GetStudentDocumentsByStudentIdTests.cs @@ -0,0 +1,71 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.StudentDocuments; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Validation; +using Moq; +using Xunit; +using Application.UseCases.StudentDocuments; + +namespace Application.Tests.UseCases.StudentDocuments +{ + public class GetStudentDocumentsByStudentIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetStudentDocumentsByStudentId CreateUseCase() => new GetStudentDocumentsByStudentId(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.StudentDocuments MockValidStudentDocuments() => new(Guid.NewGuid(), "123456", "7890"); + + [Fact] + public async Task ExecuteAsync_ValidStudentId_ReturnsResumedReadStudentDocumentsOutput() + { + // Arrange + var useCase = CreateUseCase(); + var studentId = Guid.NewGuid(); + var studentDocuments = MockValidStudentDocuments(); + + _repositoryMock.Setup(repo => repo.GetByStudentIdAsync(studentId)).ReturnsAsync(studentDocuments); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new ResumedReadStudentDocumentsOutput()); + + // Act + var result = await useCase.ExecuteAsync(studentId); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByStudentIdAsync(studentId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_StudentIdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? studentId = null; + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(studentId)); + _repositoryMock.Verify(repo => repo.GetByStudentIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_RepositoryReturnsNull_ReturnsNullOutput() + { + // Arrange + var useCase = CreateUseCase(); + var studentId = Guid.NewGuid(); + + _repositoryMock.Setup(repo => repo.GetByStudentIdAsync(studentId)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + + // Act + var result = useCase.ExecuteAsync(studentId).Result; + + // Assert + Assert.Null(result); + _repositoryMock.Verify(repo => repo.GetByStudentIdAsync(studentId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/StudentDocuments/UpdateStudentDocumentsTests.cs b/src/Application.Tests/UseCases/StudentDocuments/UpdateStudentDocumentsTests.cs new file mode 100644 index 00000000..fd77d6a5 --- /dev/null +++ b/src/Application.Tests/UseCases/StudentDocuments/UpdateStudentDocumentsTests.cs @@ -0,0 +1,97 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; +using Microsoft.AspNetCore.Http; +using Moq; +using Xunit; +using Application.UseCases.StudentDocuments; +using Application.Tests.Mocks; + +namespace Application.Tests.UseCases.StudentDocuments +{ + public class UpdateStudentDocumentsTests + { + private readonly Mock _studentDocumentRepositoryMock = new(); + private readonly Mock _projectRepositoryMock = new(); + private readonly Mock _storageFileServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateStudentDocuments CreateUseCase() => + new UpdateStudentDocuments( + _studentDocumentRepositoryMock.Object, + _projectRepositoryMock.Object, + _storageFileServiceMock.Object, + _mapperMock.Object); + private static Domain.Entities.StudentDocuments MockValidStudentDocuments() => new(Guid.NewGuid(), "123456", "7890"); + + [Fact] + public async Task ExecuteAsync_ValidIdAndInput_ReturnsDetailedReadStudentDocumentsOutput() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateStudentDocumentsInput + { + AgencyNumber = "1234", + AccountNumber = "5678", + }; + var project = ProjectMock.MockValidProject(); + var studentDocuments = MockValidStudentDocuments(); + project.Status = Domain.Entities.Enums.EProjectStatus.Accepted; + studentDocuments.Project = project; + + _studentDocumentRepositoryMock.Setup(r => r.GetByIdAsync(id)).ReturnsAsync(studentDocuments); + _studentDocumentRepositoryMock.Setup(r => r.UpdateAsync(studentDocuments)).ReturnsAsync(studentDocuments); + _storageFileServiceMock.Setup(s => s.UploadFileAsync(It.IsAny(), It.IsAny())).ReturnsAsync("file_url"); + _projectRepositoryMock.Setup(r => r.GetByIdAsync(It.IsAny())).ReturnsAsync(project); + _mapperMock.Setup(m => m.Map(It.IsAny())).Returns(new DetailedReadStudentDocumentsOutput()); + + // Act + var result = await useCase.ExecuteAsync(id, input); + + // Assert + Assert.NotNull(result); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.AtMostOnce); + _studentDocumentRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Once); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), It.IsAny()), Times.AtMost(7)); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + var input = new UpdateStudentDocumentsInput(); + + // Act & Assert + var exception = Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Never); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _studentDocumentRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_StudentDocumentsNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var id = Guid.NewGuid(); + var input = new UpdateStudentDocumentsInput(); + + _studentDocumentRepositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.StudentDocuments)null); + + // Act & Assert + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(id, input)); + _studentDocumentRepositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _projectRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _studentDocumentRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _storageFileServiceMock.Verify(service => service.UploadFileAsync(It.IsAny(), It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/SubArea/CreateSubAreaTests.cs b/src/Application.Tests/UseCases/SubArea/CreateSubAreaTests.cs new file mode 100644 index 00000000..415ffa2c --- /dev/null +++ b/src/Application.Tests/UseCases/SubArea/CreateSubAreaTests.cs @@ -0,0 +1,125 @@ +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.UseCases.SubArea; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.SubArea +{ + public class CreateSubAreaTests + { + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _areaRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private ICreateSubArea CreateUseCase() => new CreateSubArea(_subAreaRepositoryMock.Object, _areaRepositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.SubArea MockValidSubArea() => new(Guid.NewGuid(), "ABC", "SubArea Name"); + private static Domain.Entities.Area MockValidArea(CreateSubAreaInput input) => new(input.AreaId, "ABC", "Area Name"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadSubAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateSubAreaInput + { + Code = "NewCode", + AreaId = Guid.NewGuid() + }; + Domain.Entities.SubArea? existingSubArea = null; + Domain.Entities.Area area = MockValidArea(input); + var createdSubArea = MockValidSubArea(); + createdSubArea.AreaId = input.AreaId; + createdSubArea.Code = input.Code; + + _subAreaRepositoryMock.Setup(repo => repo.GetByCodeAsync(input.Code)).ReturnsAsync(existingSubArea); + _areaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.AreaId)).ReturnsAsync(area); + _subAreaRepositoryMock.Setup(repo => repo.CreateAsync(It.IsAny())).ReturnsAsync(createdSubArea); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadSubAreaOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + _subAreaRepositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Once); + _areaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.AreaId), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_SubAreaWithSameCodeExists_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateSubAreaInput + { + Code = "ExistingCode", + AreaId = Guid.NewGuid() + }; + var existingSubArea = MockValidSubArea(); + existingSubArea.Code = input.Code; + existingSubArea.AreaId = input.AreaId; + var area = MockValidArea(input); + + _subAreaRepositoryMock.Setup(repo => repo.GetByCodeAsync(input.Code)).ReturnsAsync(existingSubArea); + _areaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.AreaId)).ReturnsAsync(area); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _subAreaRepositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Once); + _areaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.AreaId), Times.Never); + _subAreaRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NullAreaId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateSubAreaInput + { + Code = "NewCode", + AreaId = null + }; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _subAreaRepositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Never); + _areaRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _subAreaRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_InactiveArea_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new CreateSubAreaInput + { + Code = "NewCode", + AreaId = Guid.NewGuid() + }; + Domain.Entities.SubArea? existingSubArea = null; + var area = MockValidArea(input); + area.DeactivateEntity(); + + _subAreaRepositoryMock.Setup(repo => repo.GetByCodeAsync(input.Code)).ReturnsAsync(existingSubArea); + _areaRepositoryMock.Setup(repo => repo.GetByIdAsync(input.AreaId)).ReturnsAsync(area); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _subAreaRepositoryMock.Verify(repo => repo.GetByCodeAsync(input.Code), Times.Once); + _areaRepositoryMock.Verify(repo => repo.GetByIdAsync(input.AreaId), Times.Once); + _subAreaRepositoryMock.Verify(repo => repo.CreateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/SubArea/DeleteSubAreaTests.cs b/src/Application.Tests/UseCases/SubArea/DeleteSubAreaTests.cs new file mode 100644 index 00000000..6c01a409 --- /dev/null +++ b/src/Application.Tests/UseCases/SubArea/DeleteSubAreaTests.cs @@ -0,0 +1,53 @@ +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.UseCases.SubArea; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.SubArea +{ + public class DeleteSubAreaTests + { + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeleteSubArea CreateUseCase() => new DeleteSubArea(_subAreaRepositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.SubArea MockValidSubArea() => new(Guid.NewGuid(), "ABC", "SubArea Name"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsDetailedReadSubAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var subAreaToDelete = MockValidSubArea(); + var subAreaId = Guid.NewGuid(); + + _subAreaRepositoryMock.Setup(repo => repo.DeleteAsync(subAreaId)).ReturnsAsync(subAreaToDelete); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadSubAreaOutput()); + + // Act + var result = await useCase.ExecuteAsync(subAreaId); + + // Assert + Assert.NotNull(result); + _subAreaRepositoryMock.Verify(repo => repo.DeleteAsync(subAreaId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public void ExecuteAsync_IdIsNull_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(null)); + _subAreaRepositoryMock.Verify(repo => repo.DeleteAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/SubArea/GetSubAreaByIdTests.cs b/src/Application.Tests/UseCases/SubArea/GetSubAreaByIdTests.cs new file mode 100644 index 00000000..b2576429 --- /dev/null +++ b/src/Application.Tests/UseCases/SubArea/GetSubAreaByIdTests.cs @@ -0,0 +1,72 @@ +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.UseCases.SubArea; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.SubArea +{ + public class GetSubAreaByIdTests + { + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetSubAreaById CreateUseCase() => new GetSubAreaById(_subAreaRepositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.SubArea MockValidSubArea() => new(Guid.NewGuid(), "ABC", "SubArea Name"); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsDetailedReadSubAreaOutput() + { + // Arrange + var useCase = CreateUseCase(); + var subAreaId = Guid.NewGuid(); + var subArea = MockValidSubArea(); + + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(subAreaId)).ReturnsAsync(subArea); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())) + .Returns(new DetailedReadSubAreaOutput()); + + // Act + var result = await useCase.ExecuteAsync(subAreaId); + + // Assert + Assert.NotNull(result); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(subAreaId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_InvalidId_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + Guid subAreaId = Guid.NewGuid(); + _subAreaRepositoryMock.Setup(repo => repo.GetByIdAsync(subAreaId)).ReturnsAsync((Domain.Entities.SubArea)null); + + // Act + var result = await useCase.ExecuteAsync(subAreaId); + + // Assert + Assert.Null(result); + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(subAreaId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + + [Fact] + public async Task ExecuteAsync_NullId_ReturnsNull() + { + // Arrange + var useCase = CreateUseCase(); + + // Act + Assert.ThrowsAsync(async () => await useCase.ExecuteAsync(null)); + + // Assert + _subAreaRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/SubArea/GetSubAreasByAreaTests.cs b/src/Application.Tests/UseCases/SubArea/GetSubAreasByAreaTests.cs new file mode 100644 index 00000000..5dc435a1 --- /dev/null +++ b/src/Application.Tests/UseCases/SubArea/GetSubAreasByAreaTests.cs @@ -0,0 +1,75 @@ +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.UseCases.SubArea; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.SubArea +{ + public class GetSubAreasByAreaTests + { + private readonly Mock _subAreaRepositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetSubAreasByArea CreateUseCase() => new GetSubAreasByArea(_subAreaRepositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.SubArea MockValidSubArea() => new(Guid.NewGuid(), "ABC", "SubArea Name"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsResumedReadSubAreaOutputs() + { + // Arrange + var useCase = CreateUseCase(); + Guid areaId = Guid.NewGuid(); + int skip = 0; + int take = 5; + var subAreas = new List { MockValidSubArea(), MockValidSubArea() }; + + _subAreaRepositoryMock.Setup(repo => repo.GetSubAreasByAreaAsync(areaId, skip, take)) + .ReturnsAsync(subAreas); + _mapperMock.Setup(mapper => mapper.Map>(It.IsAny>())) + .Returns(subAreas.Select(sa => new ResumedReadSubAreaOutput())); + + // Act + var result = await useCase.ExecuteAsync(areaId, skip, take); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); // Check the expected count + _subAreaRepositoryMock.Verify(repo => repo.GetSubAreasByAreaAsync(areaId, skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidSkipOrTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + Guid areaId = Guid.NewGuid(); + int skip = -1; // Invalid skip value + int take = 5; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(areaId, skip, take)); + _subAreaRepositoryMock.Verify(repo => repo.GetSubAreasByAreaAsync(areaId, skip, take), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NullAreaId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid areaId = Guid.NewGuid(); + int skip = 0; + int take = 5; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(null, skip, take)); + _subAreaRepositoryMock.Verify(repo => repo.GetSubAreasByAreaAsync(areaId, skip, take), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/ActivateUserTests.cs b/src/Application.Tests/UseCases/User/ActivateUserTests.cs new file mode 100644 index 00000000..b04cfe52 --- /dev/null +++ b/src/Application.Tests/UseCases/User/ActivateUserTests.cs @@ -0,0 +1,70 @@ +using AutoMapper; +using Application.UseCases.User; +using Application.Ports.User; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.User; + +namespace Application.Tests.UseCases.User +{ + public class ActivateUserTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IActivateUser CreateUseCase() => new ActivateUser(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsUserReadOutput() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var id = user.Id; + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(user); + _repositoryMock.Setup(repo => repo.UpdateAsync(user)).ReturnsAsync(user); + _mapperMock.Setup(mapper => mapper.Map(user)).Returns(new UserReadOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(user), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(user), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/DeactivateUserTests.cs b/src/Application.Tests/UseCases/User/DeactivateUserTests.cs new file mode 100644 index 00000000..b728abdb --- /dev/null +++ b/src/Application.Tests/UseCases/User/DeactivateUserTests.cs @@ -0,0 +1,71 @@ +using AutoMapper; +using Application.UseCases.User; +using Application.Ports.User; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.User; + +namespace Application.Tests.UseCases.User +{ + public class DeactivateUserTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IDeactivateUser CreateUseCase() => new DeactivateUser(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsUserReadOutput() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var id = user.Id; + + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync(user); + _repositoryMock.Setup(repo => repo.UpdateAsync(user)).ReturnsAsync(user); + _mapperMock.Setup(mapper => mapper.Map(user)).Returns(new UserReadOutput()); + + // Act + var result = await useCase.ExecuteAsync(id); + + // Assert + Assert.NotNull(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(user), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(user), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? id = null; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserNotFound_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid id = Guid.NewGuid(); + _repositoryMock.Setup(repo => repo.GetByIdAsync(id)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(id)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(id), Times.Once); + _repositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/GetActiveUsersTests.cs b/src/Application.Tests/UseCases/User/GetActiveUsersTests.cs new file mode 100644 index 00000000..59912f68 --- /dev/null +++ b/src/Application.Tests/UseCases/User/GetActiveUsersTests.cs @@ -0,0 +1,55 @@ +using AutoMapper; +using Application.UseCases.User; +using Application.Ports.User; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.User; + +namespace Application.Tests.UseCases.User +{ + public class GetActiveUsersTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetActiveUsers CreateUseCase() => new GetActiveUsers(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsListOfUserReadOutput() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 10; + var users = new List { MockValidUser(), MockValidUser() }; + _repositoryMock.Setup(repo => repo.GetActiveUsersAsync(skip, take)).ReturnsAsync(users); + _mapperMock.Setup(mapper => mapper.Map>(users)).Returns(users.Select(u => new UserReadOutput()).ToList()); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom>(result); + Assert.Equal(users.Count, result.Count()); + _repositoryMock.Verify(repo => repo.GetActiveUsersAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(users), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidSkipAndTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = -1; + int take = 0; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetActiveUsersAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/GetInactiveUsersTests.cs b/src/Application.Tests/UseCases/User/GetInactiveUsersTests.cs new file mode 100644 index 00000000..d4736393 --- /dev/null +++ b/src/Application.Tests/UseCases/User/GetInactiveUsersTests.cs @@ -0,0 +1,55 @@ +using AutoMapper; +using Application.UseCases.User; +using Application.Ports.User; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.User; + +namespace Application.Tests.UseCases.User +{ + public class GetInactiveUsersTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetInactiveUsers CreateUseCase() => new GetInactiveUsers(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsListOfUserReadOutput() + { + // Arrange + var useCase = CreateUseCase(); + int skip = 0; + int take = 10; + var users = new List { MockValidUser(), MockValidUser() }; + _repositoryMock.Setup(repo => repo.GetInactiveUsersAsync(skip, take)).ReturnsAsync(users); + _mapperMock.Setup(mapper => mapper.Map>(users)).Returns(users.Select(u => new UserReadOutput()).ToList()); + + // Act + var result = await useCase.ExecuteAsync(skip, take); + + // Assert + Assert.NotNull(result); + Assert.IsAssignableFrom>(result); + Assert.Equal(users.Count, result.Count()); + _repositoryMock.Verify(repo => repo.GetInactiveUsersAsync(skip, take), Times.Once); + _mapperMock.Verify(mapper => mapper.Map>(users), Times.Once); + } + + [Fact] + public void ExecuteAsync_InvalidSkipAndTake_ThrowsArgumentException() + { + // Arrange + var useCase = CreateUseCase(); + int skip = -1; + int take = 0; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(skip, take)); + _repositoryMock.Verify(repo => repo.GetInactiveUsersAsync(It.IsAny(), It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map>(It.IsAny>()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/GetUserByIdTests.cs b/src/Application.Tests/UseCases/User/GetUserByIdTests.cs new file mode 100644 index 00000000..8c2a209b --- /dev/null +++ b/src/Application.Tests/UseCases/User/GetUserByIdTests.cs @@ -0,0 +1,67 @@ +using AutoMapper; +using Application.UseCases.User; +using Application.Ports.User; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Moq; +using Xunit; +using Application.Interfaces.UseCases.User; + +namespace Application.Tests.UseCases.User +{ + public class GetUserByIdTests + { + private readonly Mock _repositoryMock = new(); + private readonly Mock _mapperMock = new(); + + private IGetUserById CreateUseCase() => new GetUserById(_repositoryMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + + [Fact] + public async Task ExecuteAsync_ValidId_ReturnsUserReadOutput() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + var userId = user.Id; + _repositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync(user); + _mapperMock.Setup(mapper => mapper.Map(user)).Returns(new UserReadOutput()); + + // Act + var result = await useCase.ExecuteAsync(userId); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + _repositoryMock.Verify(repo => repo.GetByIdAsync(userId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(user), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Guid? userId = null; + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userId)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NonexistentId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var userId = Guid.NewGuid(); + _repositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userId)); + _repositoryMock.Verify(repo => repo.GetByIdAsync(userId), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Once); + } + } +} diff --git a/src/Application.Tests/UseCases/User/MakeAdminTests.cs b/src/Application.Tests/UseCases/User/MakeAdminTests.cs new file mode 100644 index 00000000..71331b60 --- /dev/null +++ b/src/Application.Tests/UseCases/User/MakeAdminTests.cs @@ -0,0 +1,160 @@ +using Application.Interfaces.UseCases.User; +using Application.UseCases.User; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.User +{ + public class MakeAdminTests + { + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _professorRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + + private IMakeAdmin CreateUseCase() => + new MakeAdmin(_userRepositoryMock.Object, _professorRepositoryMock.Object, _tokenAuthenticationServiceMock.Object); + private static Domain.Entities.Professor MockValidProfessor() => new("1234567", 12345); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + private static Dictionary MockUserClaims(Domain.Entities.User user) => new() { { (Guid)user.Id, user } }; + + [Fact] + public async Task ExecuteAsync_ValidUserId_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + var userToMakeAdmin = MockValidUser(); + userToMakeAdmin.Role = ERole.PROFESSOR; + + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeAdmin.Id)).ReturnsAsync(userToMakeAdmin); + _professorRepositoryMock.Setup(repo => repo.GetByUserIdAsync(userToMakeAdmin.Id)).ReturnsAsync(MockValidProfessor()); + + // Act + var result = await useCase.ExecuteAsync(userToMakeAdmin.Id); + + // Assert + Assert.Equal($"Usuário {adminUser.Id} tornou administrador o usuário {userToMakeAdmin.Id}", result); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(userToMakeAdmin), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullUserId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(null)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UnauthenticatedUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Dictionary userClaims = null; + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(Guid.NewGuid())); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NonAdminUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var user = MockValidUser(); + user.Role = ERole.PROFESSOR; + + var userClaims = MockUserClaims(user); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(Guid.NewGuid())); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AdminUserDoesNotExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var userToMakeAdmin = MockValidUser(); + userToMakeAdmin.Role = ERole.PROFESSOR; + + var adminUser = MockValidUser(); + var adminUserId = adminUser.Id; + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUserId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeAdmin.Id)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUserId), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + + [Fact] + public void ExecuteAsync_UserToMakeAdminDoesNotExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + var userToMakeAdminId = Guid.NewGuid(); + var userClaims = MockUserClaims(adminUser); + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeAdminId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeAdminId)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUser.Id), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userToMakeAdminId), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserToMakeAdminIsNotProfessor_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + var userToMakeAdmin = MockValidUser(); + userToMakeAdmin.Role = ERole.PROFESSOR; + var userClaims = MockUserClaims(adminUser); + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeAdmin.Id)).ReturnsAsync(userToMakeAdmin); + _professorRepositoryMock.Setup(repo => repo.GetByUserIdAsync(userToMakeAdmin.Id)).ReturnsAsync((Domain.Entities.Professor)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeAdmin.Id)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUser.Id), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userToMakeAdmin.Id), Times.Once); + _professorRepositoryMock.Verify(repo => repo.GetByUserIdAsync(userToMakeAdmin.Id), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/MakeCoordinatorTests.cs b/src/Application.Tests/UseCases/User/MakeCoordinatorTests.cs new file mode 100644 index 00000000..677390f5 --- /dev/null +++ b/src/Application.Tests/UseCases/User/MakeCoordinatorTests.cs @@ -0,0 +1,155 @@ +using Application.Interfaces.UseCases.User; +using Application.UseCases.User; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.User +{ + public class MakeCoordinatorTests + { + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + + private IMakeCoordinator CreateUseCase() => new MakeCoordinator(_userRepositoryMock.Object, _tokenAuthenticationServiceMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + private static Dictionary MockUserClaims(Domain.Entities.User user) => new() { { (Guid)user.Id, user } }; + + [Fact] + public async Task ExecuteAsync_ValidUserId_ReturnsSuccessMessage() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + adminUser.IsCoordinator = true; + var userToMakeCoordinator = MockValidUser(); + userToMakeCoordinator.IsCoordinator = false; + var userClaims = MockUserClaims(adminUser); + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeCoordinator.Id)).ReturnsAsync(userToMakeCoordinator); + + // Act + var result = await useCase.ExecuteAsync(userToMakeCoordinator.Id); + + // Assert + Assert.Equal($"Usuário {adminUser.Id} tornou coordenador o usuário {userToMakeCoordinator.Id}", result); + Assert.False(adminUser.IsCoordinator); + Assert.True(userToMakeCoordinator.IsCoordinator); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(adminUser), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(userToMakeCoordinator), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullUserId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(null)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UnauthenticatedUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + Dictionary userClaims = null; + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(Guid.NewGuid())); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_NonAdminUser_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + adminUser.Role = ERole.PROFESSOR; + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(Guid.NewGuid())); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AdminUserDoesNotExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + adminUser.IsCoordinator = true; + var adminUserId = adminUser.Id; + var userToMakeCoordinator = MockValidUser(); + userToMakeCoordinator.IsCoordinator = false; + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUserId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeCoordinator.Id)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUserId), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserToMakeCoordinatorDoesNotExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + adminUser.IsCoordinator = true; + var userToMakeCoordinatorId = Guid.NewGuid(); + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeCoordinatorId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeCoordinatorId)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUser.Id), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userToMakeCoordinatorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_AdminUserIsNotCoordinator_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var adminUser = MockValidUser(); + adminUser.IsCoordinator = false; + + var userToMakeCoordinator = MockValidUser(); + userToMakeCoordinator.IsCoordinator = false; + + var userClaims = MockUserClaims(adminUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(adminUser.Id)).ReturnsAsync(adminUser); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userToMakeCoordinator.Id)).ReturnsAsync(userToMakeCoordinator); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(userToMakeCoordinator.Id)); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(adminUser.Id), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userToMakeCoordinator.Id), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application.Tests/UseCases/User/UpdateUserTests.cs b/src/Application.Tests/UseCases/User/UpdateUserTests.cs new file mode 100644 index 00000000..5ae3d5e6 --- /dev/null +++ b/src/Application.Tests/UseCases/User/UpdateUserTests.cs @@ -0,0 +1,100 @@ +using Application.Interfaces.UseCases.User; +using Application.Ports.User; +using Application.UseCases.User; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Moq; +using Xunit; + +namespace Application.Tests.UseCases.User +{ + public class UpdateUserTests + { + private readonly Mock _userRepositoryMock = new(); + private readonly Mock _tokenAuthenticationServiceMock = new(); + private readonly Mock _mapperMock = new(); + + private IUpdateUser CreateUseCase() => + new UpdateUser(_userRepositoryMock.Object, _tokenAuthenticationServiceMock.Object, _mapperMock.Object); + private static Domain.Entities.User MockValidUser() => new(id: Guid.NewGuid(), name: "Test", role: "ADMIN"); + private static Dictionary MockUserClaims(Domain.Entities.User user) => new() { { (Guid)user.Id, user } }; + + [Fact] + public async Task ExecuteAsync_ValidInput_ReturnsUpdatedUser() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserUpdateInput + { + Name = "New Name", + CPF = "83278087020" + }; + var existingUser = MockValidUser(); + var userClaims = MockUserClaims(existingUser); + + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(It.IsAny())).ReturnsAsync(existingUser); + _userRepositoryMock.Setup(repo => repo.UpdateAsync(It.IsAny())).ReturnsAsync(existingUser); + _mapperMock.Setup(mapper => mapper.Map(It.IsAny())).Returns(new UserReadOutput()); + + // Act + var result = await useCase.ExecuteAsync(input); + + // Assert + Assert.NotNull(result); + Assert.Equal(input.Name, existingUser.Name); + Assert.Equal(input.CPF, existingUser.CPF); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(existingUser), Times.Once); + _mapperMock.Verify(mapper => mapper.Map(existingUser), Times.Once); + } + + [Fact] + public void ExecuteAsync_NullUserId_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserUpdateInput + { + Name = "New Name", + CPF = "12345678901" + }; + Dictionary userClaims = null; + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(It.IsAny()), Times.Never); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + + [Fact] + public void ExecuteAsync_UserDoesNotExist_ThrowsUseCaseException() + { + // Arrange + var useCase = CreateUseCase(); + var input = new UserUpdateInput + { + Name = "New Name", + CPF = "12345678901" + }; + var user = MockValidUser(); + var userId = user.Id; + var userClaims = MockUserClaims(user); + _tokenAuthenticationServiceMock.Setup(service => service.GetUserAuthenticatedClaims()).Returns(userClaims); + _userRepositoryMock.Setup(repo => repo.GetByIdAsync(userId)).ReturnsAsync((Domain.Entities.User)null); + + // Act & Assert + Assert.ThrowsAsync(() => useCase.ExecuteAsync(input)); + _tokenAuthenticationServiceMock.Verify(service => service.GetUserAuthenticatedClaims(), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetByIdAsync(userId), Times.Once); + _userRepositoryMock.Verify(repo => repo.UpdateAsync(It.IsAny()), Times.Never); + _mapperMock.Verify(mapper => mapper.Map(It.IsAny()), Times.Never); + } + } +} diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj new file mode 100644 index 00000000..e637f60d --- /dev/null +++ b/src/Application/Application.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + 0.1.0 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ActivityType/IGetActivitiesByNoticeId.cs b/src/Application/Interfaces/UseCases/ActivityType/IGetActivitiesByNoticeId.cs new file mode 100644 index 00000000..b79c23b4 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ActivityType/IGetActivitiesByNoticeId.cs @@ -0,0 +1,9 @@ +using Application.Ports.Activity; + +namespace Application.Interfaces.UseCases.ActivityType +{ + public interface IGetActivitiesByNoticeId + { + Task> ExecuteAsync(Guid? noticeId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ActivityType/IGetLastNoticeActivities.cs b/src/Application/Interfaces/UseCases/ActivityType/IGetLastNoticeActivities.cs new file mode 100644 index 00000000..c54b81c6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ActivityType/IGetLastNoticeActivities.cs @@ -0,0 +1,9 @@ +using Application.Ports.Activity; + +namespace Application.Interfaces.UseCases.ActivityType +{ + public interface IGetLastNoticeActivities + { + Task> ExecuteAsync(); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Area/ICreateArea.cs b/src/Application/Interfaces/UseCases/Area/ICreateArea.cs new file mode 100644 index 00000000..201fa759 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Area/ICreateArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.Area; + +namespace Application.Interfaces.UseCases.Area +{ + public interface ICreateArea + { + Task ExecuteAsync(CreateAreaInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Area/IDeleteArea.cs b/src/Application/Interfaces/UseCases/Area/IDeleteArea.cs new file mode 100644 index 00000000..e6392cbf --- /dev/null +++ b/src/Application/Interfaces/UseCases/Area/IDeleteArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.Area; + +namespace Application.Interfaces.UseCases.Area +{ + public interface IDeleteArea + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Area/IGetAreaById.cs b/src/Application/Interfaces/UseCases/Area/IGetAreaById.cs new file mode 100644 index 00000000..ea2a8f46 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Area/IGetAreaById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Area; + +namespace Application.Interfaces.UseCases.Area +{ + public interface IGetAreaById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Area/IGetAreasByMainArea.cs b/src/Application/Interfaces/UseCases/Area/IGetAreasByMainArea.cs new file mode 100644 index 00000000..766bf42c --- /dev/null +++ b/src/Application/Interfaces/UseCases/Area/IGetAreasByMainArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.Area; + +namespace Application.Interfaces.UseCases.Area +{ + public interface IGetAreasByMainArea + { + Task> ExecuteAsync(Guid? mainAreaId, int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Area/IUpdateArea.cs b/src/Application/Interfaces/UseCases/Area/IUpdateArea.cs new file mode 100644 index 00000000..d3105999 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Area/IUpdateArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.Area; + +namespace Application.Interfaces.UseCases.Area +{ + public interface IUpdateArea + { + Task ExecuteAsync(Guid? id, UpdateAreaInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/AssistanceType/ICreateAssistanceType.cs b/src/Application/Interfaces/UseCases/AssistanceType/ICreateAssistanceType.cs new file mode 100644 index 00000000..6702eba1 --- /dev/null +++ b/src/Application/Interfaces/UseCases/AssistanceType/ICreateAssistanceType.cs @@ -0,0 +1,9 @@ +using Application.Ports.AssistanceType; + +namespace Application.Interfaces.UseCases.AssistanceType +{ + public interface ICreateAssistanceType + { + Task ExecuteAsync(CreateAssistanceTypeInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/AssistanceType/IDeleteAssistanceType.cs b/src/Application/Interfaces/UseCases/AssistanceType/IDeleteAssistanceType.cs new file mode 100644 index 00000000..6ad9571b --- /dev/null +++ b/src/Application/Interfaces/UseCases/AssistanceType/IDeleteAssistanceType.cs @@ -0,0 +1,9 @@ +using Application.Ports.AssistanceType; + +namespace Application.Interfaces.UseCases.AssistanceType +{ + public interface IDeleteAssistanceType + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypeById.cs b/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypeById.cs new file mode 100644 index 00000000..c92686e6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypeById.cs @@ -0,0 +1,9 @@ +using Application.Ports.AssistanceType; + +namespace Application.Interfaces.UseCases.AssistanceType +{ + public interface IGetAssistanceTypeById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypes.cs b/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypes.cs new file mode 100644 index 00000000..758c9a75 --- /dev/null +++ b/src/Application/Interfaces/UseCases/AssistanceType/IGetAssistanceTypes.cs @@ -0,0 +1,9 @@ +using Application.Ports.AssistanceType; + +namespace Application.Interfaces.UseCases.AssistanceType +{ + public interface IGetAssistanceTypes + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/AssistanceType/IUpdateAssistanceType.cs b/src/Application/Interfaces/UseCases/AssistanceType/IUpdateAssistanceType.cs new file mode 100644 index 00000000..eec2308d --- /dev/null +++ b/src/Application/Interfaces/UseCases/AssistanceType/IUpdateAssistanceType.cs @@ -0,0 +1,9 @@ +using Application.Ports.AssistanceType; + +namespace Application.Interfaces.UseCases.AssistanceType +{ + public interface IUpdateAssistanceType + { + Task ExecuteAsync(Guid? id, UpdateAssistanceTypeInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Auth/IConfirmEmail.cs b/src/Application/Interfaces/UseCases/Auth/IConfirmEmail.cs new file mode 100644 index 00000000..0d049361 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Auth/IConfirmEmail.cs @@ -0,0 +1,7 @@ +namespace Application.Interfaces.UseCases.Auth +{ + public interface IConfirmEmail + { + Task ExecuteAsync(string? email, string? token); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Auth/IForgotPassword.cs b/src/Application/Interfaces/UseCases/Auth/IForgotPassword.cs new file mode 100644 index 00000000..023788b2 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Auth/IForgotPassword.cs @@ -0,0 +1,7 @@ +namespace Application.Interfaces.UseCases.Auth +{ + public interface IForgotPassword + { + Task ExecuteAsync(string? email); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Auth/ILogin.cs b/src/Application/Interfaces/UseCases/Auth/ILogin.cs new file mode 100644 index 00000000..aaf80378 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Auth/ILogin.cs @@ -0,0 +1,9 @@ +using Application.Ports.Auth; + +namespace Application.Interfaces.UseCases.Auth +{ + public interface ILogin + { + Task ExecuteAsync(UserLoginInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Auth/IResetPassword.cs b/src/Application/Interfaces/UseCases/Auth/IResetPassword.cs new file mode 100644 index 00000000..538ef471 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Auth/IResetPassword.cs @@ -0,0 +1,9 @@ +using Application.Ports.Auth; + +namespace Application.Interfaces.UseCases.Auth +{ + public interface IResetPassword + { + Task ExecuteAsync(UserResetPasswordInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Campus/ICreateCampus.cs b/src/Application/Interfaces/UseCases/Campus/ICreateCampus.cs new file mode 100644 index 00000000..818d6980 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Campus/ICreateCampus.cs @@ -0,0 +1,9 @@ +using Application.Ports.Campus; + +namespace Application.Interfaces.UseCases.Campus +{ + public interface ICreateCampus + { + Task ExecuteAsync(CreateCampusInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Campus/IDeleteCampus.cs b/src/Application/Interfaces/UseCases/Campus/IDeleteCampus.cs new file mode 100644 index 00000000..3d38dbfb --- /dev/null +++ b/src/Application/Interfaces/UseCases/Campus/IDeleteCampus.cs @@ -0,0 +1,9 @@ +using Application.Ports.Campus; + +namespace Application.Interfaces.UseCases.Campus +{ + public interface IDeleteCampus + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Campus/IGetCampusById.cs b/src/Application/Interfaces/UseCases/Campus/IGetCampusById.cs new file mode 100644 index 00000000..c091fcfd --- /dev/null +++ b/src/Application/Interfaces/UseCases/Campus/IGetCampusById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Campus; + +namespace Application.Interfaces.UseCases.Campus +{ + public interface IGetCampusById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Campus/IGetCampuses.cs b/src/Application/Interfaces/UseCases/Campus/IGetCampuses.cs new file mode 100644 index 00000000..056edeee --- /dev/null +++ b/src/Application/Interfaces/UseCases/Campus/IGetCampuses.cs @@ -0,0 +1,9 @@ +using Application.Ports.Campus; + +namespace Application.Interfaces.UseCases.Campus +{ + public interface IGetCampuses + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Campus/IUpdateCampus.cs b/src/Application/Interfaces/UseCases/Campus/IUpdateCampus.cs new file mode 100644 index 00000000..216ca10d --- /dev/null +++ b/src/Application/Interfaces/UseCases/Campus/IUpdateCampus.cs @@ -0,0 +1,9 @@ +using Application.Ports.Campus; + +namespace Application.Interfaces.UseCases.Campus +{ + public interface IUpdateCampus + { + Task ExecuteAsync(Guid? id, UpdateCampusInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Course/ICreateCourse.cs b/src/Application/Interfaces/UseCases/Course/ICreateCourse.cs new file mode 100644 index 00000000..c48a8ee6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Course/ICreateCourse.cs @@ -0,0 +1,9 @@ +using Application.Ports.Course; + +namespace Application.Interfaces.UseCases.Course +{ + public interface ICreateCourse + { + Task ExecuteAsync(CreateCourseInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Course/IDeleteCourse.cs b/src/Application/Interfaces/UseCases/Course/IDeleteCourse.cs new file mode 100644 index 00000000..4fa291b9 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Course/IDeleteCourse.cs @@ -0,0 +1,9 @@ +using Application.Ports.Course; + +namespace Application.Interfaces.UseCases.Course +{ + public interface IDeleteCourse + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Course/IGetCourseById.cs b/src/Application/Interfaces/UseCases/Course/IGetCourseById.cs new file mode 100644 index 00000000..c2128b84 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Course/IGetCourseById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Course; + +namespace Application.Interfaces.UseCases.Course +{ + public interface IGetCourseById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Course/IGetCourses.cs b/src/Application/Interfaces/UseCases/Course/IGetCourses.cs new file mode 100644 index 00000000..f47a03a6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Course/IGetCourses.cs @@ -0,0 +1,9 @@ +using Application.Ports.Course; + +namespace Application.Interfaces.UseCases.Course +{ + public interface IGetCourses + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Course/IUpdateCourse.cs b/src/Application/Interfaces/UseCases/Course/IUpdateCourse.cs new file mode 100644 index 00000000..d63213fb --- /dev/null +++ b/src/Application/Interfaces/UseCases/Course/IUpdateCourse.cs @@ -0,0 +1,9 @@ +using Application.Ports.Course; + +namespace Application.Interfaces.UseCases.Course +{ + public interface IUpdateCourse + { + Task ExecuteAsync(Guid? id, UpdateCourseInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/MainArea/ICreateMainArea.cs b/src/Application/Interfaces/UseCases/MainArea/ICreateMainArea.cs new file mode 100644 index 00000000..ec074cf9 --- /dev/null +++ b/src/Application/Interfaces/UseCases/MainArea/ICreateMainArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.MainArea; + +namespace Application.Interfaces.UseCases.MainArea +{ + public interface ICreateMainArea + { + Task ExecuteAsync(CreateMainAreaInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/MainArea/IDeleteMainArea.cs b/src/Application/Interfaces/UseCases/MainArea/IDeleteMainArea.cs new file mode 100644 index 00000000..fa7aa805 --- /dev/null +++ b/src/Application/Interfaces/UseCases/MainArea/IDeleteMainArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.MainArea; + +namespace Application.Interfaces.UseCases.MainArea +{ + public interface IDeleteMainArea + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/MainArea/IGetMainAreaById.cs b/src/Application/Interfaces/UseCases/MainArea/IGetMainAreaById.cs new file mode 100644 index 00000000..24e6a2a1 --- /dev/null +++ b/src/Application/Interfaces/UseCases/MainArea/IGetMainAreaById.cs @@ -0,0 +1,9 @@ +using Application.Ports.MainArea; + +namespace Application.Interfaces.UseCases.MainArea +{ + public interface IGetMainAreaById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/MainArea/IGetMainAreas.cs b/src/Application/Interfaces/UseCases/MainArea/IGetMainAreas.cs new file mode 100644 index 00000000..0d49c3e8 --- /dev/null +++ b/src/Application/Interfaces/UseCases/MainArea/IGetMainAreas.cs @@ -0,0 +1,9 @@ +using Application.Ports.MainArea; + +namespace Application.Interfaces.UseCases.MainArea +{ + public interface IGetMainAreas + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/MainArea/IUpdateMainArea.cs b/src/Application/Interfaces/UseCases/MainArea/IUpdateMainArea.cs new file mode 100644 index 00000000..24e7592f --- /dev/null +++ b/src/Application/Interfaces/UseCases/MainArea/IUpdateMainArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.MainArea; + +namespace Application.Interfaces.UseCases.MainArea +{ + public interface IUpdateMainArea + { + Task ExecuteAsync(Guid? id, UpdateMainAreaInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/ICreateNotice.cs b/src/Application/Interfaces/UseCases/Notice/ICreateNotice.cs new file mode 100644 index 00000000..dc5dbe25 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/ICreateNotice.cs @@ -0,0 +1,9 @@ +using Application.Ports.Notice; + +namespace Application.Interfaces.UseCases.Notice +{ + public interface ICreateNotice + { + Task ExecuteAsync(CreateNoticeInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/IDeleteNotice.cs b/src/Application/Interfaces/UseCases/Notice/IDeleteNotice.cs new file mode 100644 index 00000000..8473eb71 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/IDeleteNotice.cs @@ -0,0 +1,9 @@ +using Application.Ports.Notice; + +namespace Application.Interfaces.UseCases.Notice +{ + public interface IDeleteNotice + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/IGetNoticeById.cs b/src/Application/Interfaces/UseCases/Notice/IGetNoticeById.cs new file mode 100644 index 00000000..56af3191 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/IGetNoticeById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Notice; + +namespace Application.Interfaces.UseCases.Notice +{ + public interface IGetNoticeById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/IGetNotices.cs b/src/Application/Interfaces/UseCases/Notice/IGetNotices.cs new file mode 100644 index 00000000..03dccdbe --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/IGetNotices.cs @@ -0,0 +1,9 @@ +using Application.Ports.Notice; + +namespace Application.Interfaces.UseCases.Notice +{ + public interface IGetNotices + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/IReportDeadlineNotification.cs b/src/Application/Interfaces/UseCases/Notice/IReportDeadlineNotification.cs new file mode 100644 index 00000000..e48f8eb1 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/IReportDeadlineNotification.cs @@ -0,0 +1,11 @@ +namespace Application.Interfaces.UseCases.Notice +{ + public interface IReportDeadlineNotification + { + /// + /// Envia notificação para os professores de que está próximo o prazo de entrega dos relatórios. + /// + /// Resultado do processo de notificação + Task ExecuteAsync(); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Notice/IUpdateNotice.cs b/src/Application/Interfaces/UseCases/Notice/IUpdateNotice.cs new file mode 100644 index 00000000..7589999f --- /dev/null +++ b/src/Application/Interfaces/UseCases/Notice/IUpdateNotice.cs @@ -0,0 +1,9 @@ +using Application.Ports.Notice; + +namespace Application.Interfaces.UseCases.Notice +{ + public interface IUpdateNotice + { + Task ExecuteAsync(Guid? id, UpdateNoticeInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Professor/ICreateProfessor.cs b/src/Application/Interfaces/UseCases/Professor/ICreateProfessor.cs new file mode 100644 index 00000000..7b30b278 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Professor/ICreateProfessor.cs @@ -0,0 +1,9 @@ +using Application.Ports.Professor; + +namespace Application.Interfaces.UseCases.Professor +{ + public interface ICreateProfessor + { + Task ExecuteAsync(CreateProfessorInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Professor/IDeleteProfessor.cs b/src/Application/Interfaces/UseCases/Professor/IDeleteProfessor.cs new file mode 100644 index 00000000..85a1108e --- /dev/null +++ b/src/Application/Interfaces/UseCases/Professor/IDeleteProfessor.cs @@ -0,0 +1,9 @@ +using Application.Ports.Professor; + +namespace Application.Interfaces.UseCases.Professor +{ + public interface IDeleteProfessor + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Professor/IGetProfessorById.cs b/src/Application/Interfaces/UseCases/Professor/IGetProfessorById.cs new file mode 100644 index 00000000..c11532e6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Professor/IGetProfessorById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Professor; + +namespace Application.Interfaces.UseCases.Professor +{ + public interface IGetProfessorById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Professor/IGetProfessors.cs b/src/Application/Interfaces/UseCases/Professor/IGetProfessors.cs new file mode 100644 index 00000000..4c330042 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Professor/IGetProfessors.cs @@ -0,0 +1,9 @@ +using Application.Ports.Professor; + +namespace Application.Interfaces.UseCases.Professor +{ + public interface IGetProfessors + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Professor/IUpdateProfessor.cs b/src/Application/Interfaces/UseCases/Professor/IUpdateProfessor.cs new file mode 100644 index 00000000..200d5774 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Professor/IUpdateProfessor.cs @@ -0,0 +1,9 @@ +using Application.Ports.Professor; + +namespace Application.Interfaces.UseCases.Professor +{ + public interface IUpdateProfessor + { + Task ExecuteAsync(Guid? id, UpdateProfessorInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProgramType/ICreateProgramType.cs b/src/Application/Interfaces/UseCases/ProgramType/ICreateProgramType.cs new file mode 100644 index 00000000..e02b804a --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProgramType/ICreateProgramType.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProgramType; + +namespace Application.Interfaces.UseCases.ProgramType +{ + public interface ICreateProgramType + { + Task ExecuteAsync(CreateProgramTypeInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs b/src/Application/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs new file mode 100644 index 00000000..242c34f4 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProgramType; + +namespace Application.Interfaces.UseCases.ProgramType +{ + public interface IDeleteProgramType + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs b/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs new file mode 100644 index 00000000..29feeda2 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProgramType; + +namespace Application.Interfaces.UseCases.ProgramType +{ + public interface IGetProgramTypeById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs b/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs new file mode 100644 index 00000000..74cc7f7d --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProgramType; + +namespace Application.Interfaces.UseCases.ProgramType +{ + public interface IGetProgramTypes + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs b/src/Application/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs new file mode 100644 index 00000000..545b55d2 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProgramType; + +namespace Application.Interfaces.UseCases.ProgramType +{ + public interface IUpdateProgramType + { + Task ExecuteAsync(Guid? id, UpdateProgramTypeInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IAppealProject.cs b/src/Application/Interfaces/UseCases/Project/IAppealProject.cs new file mode 100644 index 00000000..57b0eb6a --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IAppealProject.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IAppealProject + { + Task ExecuteAsync(Guid? projectId, string? appealDescription); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/ICancelProject.cs b/src/Application/Interfaces/UseCases/Project/ICancelProject.cs new file mode 100644 index 00000000..5802fe1c --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/ICancelProject.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface ICancelProject + { + Task ExecuteAsync(Guid? id, string? observation); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IClosePendingProjects.cs b/src/Application/Interfaces/UseCases/Project/IClosePendingProjects.cs new file mode 100644 index 00000000..5ce83eab --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IClosePendingProjects.cs @@ -0,0 +1,11 @@ +namespace Application.Interfaces.UseCases.Project +{ + public interface IClosePendingProjects + { + /// + /// Encerra todos os projetos que estão com alguma pendência e cujo prazo de resolução da pendência já tenha expirado. + /// + /// Resultado do processo de encerramento dos projetos + Task ExecuteAsync(); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGenerateCertificate.cs b/src/Application/Interfaces/UseCases/Project/IGenerateCertificate.cs new file mode 100644 index 00000000..c54e8f70 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGenerateCertificate.cs @@ -0,0 +1,11 @@ +namespace Application.Interfaces.UseCases.Project +{ + public interface IGenerateCertificate + { + /// + /// Gera o certificado para todos os projetos que estão para ser encerrados. + /// + /// Resultado do processo de geração dos certificados + Task ExecuteAsync(); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGetActivitiesByProjectId.cs b/src/Application/Interfaces/UseCases/Project/IGetActivitiesByProjectId.cs new file mode 100644 index 00000000..9cd3e3a1 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGetActivitiesByProjectId.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectActivity; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IGetActivitiesByProjectId + { + Task> ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGetClosedProjects.cs b/src/Application/Interfaces/UseCases/Project/IGetClosedProjects.cs new file mode 100644 index 00000000..aaf0f3eb --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGetClosedProjects.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IGetClosedProjects + { + Task> ExecuteAsync(int skip, int take, bool onlyMyProjects = true); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGetOpenProjects.cs b/src/Application/Interfaces/UseCases/Project/IGetOpenProjects.cs new file mode 100644 index 00000000..fea26580 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGetOpenProjects.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IGetOpenProjects + { + Task> ExecuteAsync(int skip, int take, bool onlyMyProjects = true); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGetProjectById.cs b/src/Application/Interfaces/UseCases/Project/IGetProjectById.cs new file mode 100644 index 00000000..74118d8d --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGetProjectById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IGetProjectById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IGetProjectsToEvaluate.cs b/src/Application/Interfaces/UseCases/Project/IGetProjectsToEvaluate.cs new file mode 100644 index 00000000..664112e6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IGetProjectsToEvaluate.cs @@ -0,0 +1,13 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IGetProjectsToEvaluate + { + /// + /// Retorna todos os projetos que foram submetidos ou cuja documentação foi fornecida e estão aguardando avaliação. + /// Os projetos retornados são apenas os que estão na fase de avaliação (Submitted, Evaluation, DocumentAnalysis). + /// + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IOpenProject.cs b/src/Application/Interfaces/UseCases/Project/IOpenProject.cs new file mode 100644 index 00000000..910a038d --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IOpenProject.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IOpenProject + { + Task ExecuteAsync(OpenProjectInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/ISubmitProject.cs b/src/Application/Interfaces/UseCases/Project/ISubmitProject.cs new file mode 100644 index 00000000..ca1e13d9 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/ISubmitProject.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface ISubmitProject + { + Task ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Project/IUpdateProject.cs b/src/Application/Interfaces/UseCases/Project/IUpdateProject.cs new file mode 100644 index 00000000..ac90a048 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Project/IUpdateProject.cs @@ -0,0 +1,9 @@ +using Application.Ports.Project; + +namespace Application.Interfaces.UseCases.Project +{ + public interface IUpdateProject + { + Task ExecuteAsync(Guid? id, UpdateProjectInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs new file mode 100644 index 00000000..22b2c1d9 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs @@ -0,0 +1,15 @@ +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; + +namespace Application.Interfaces.UseCases.ProjectEvaluation +{ + public interface IEvaluateAppealProject + { + /// + /// Avalia o recurso do projeto. + /// + /// Dados de entrada para a avaliação do recurso. + /// Projeto com o recurso avaliado. + Task ExecuteAsync(EvaluateAppealProjectInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateStudentDocuments.cs b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateStudentDocuments.cs new file mode 100644 index 00000000..11d0be33 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateStudentDocuments.cs @@ -0,0 +1,15 @@ +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; + +namespace Application.Interfaces.UseCases.ProjectEvaluation +{ + public interface IEvaluateStudentDocuments + { + /// + /// Avalia os documentos informados pelo estudante associado ao projeto. + /// + /// Dados de entrada para a avaliação dos documentos. + /// Projeto com os documentos avaliados. + Task ExecuteAsync(EvaluateStudentDocumentsInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs new file mode 100644 index 00000000..0ad45ee7 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs @@ -0,0 +1,15 @@ +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; + +namespace Application.Interfaces.UseCases.ProjectEvaluation +{ + public interface IEvaluateSubmissionProject + { + /// + /// Avalia o projeto submetido. + /// + /// Dados de entrada para a avaliação do projeto. + /// Projeto com a avaliação do projeto submetido. + Task ExecuteAsync(EvaluateSubmissionProjectInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs b/src/Application/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs new file mode 100644 index 00000000..d57f8ef3 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs @@ -0,0 +1,14 @@ +using Application.Ports.ProjectEvaluation; + +namespace Application.Interfaces.UseCases.ProjectEvaluation +{ + public interface IGetEvaluationByProjectId + { + /// + /// Obtém a avaliação do projeto. + /// + /// Identificador do projeto. + /// Avaliação do projeto. + Task ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectFinalReport/ICreateProjectFinalReport.cs b/src/Application/Interfaces/UseCases/ProjectFinalReport/ICreateProjectFinalReport.cs new file mode 100644 index 00000000..fd910385 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectFinalReport/ICreateProjectFinalReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectFinalReport; + +namespace Application.Interfaces.UseCases.ProjectFinalReport +{ + public interface ICreateProjectFinalReport + { + Task ExecuteAsync(CreateProjectFinalReportInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectFinalReport/IDeleteProjectFinalReport.cs b/src/Application/Interfaces/UseCases/ProjectFinalReport/IDeleteProjectFinalReport.cs new file mode 100644 index 00000000..71f5e9a5 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectFinalReport/IDeleteProjectFinalReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectFinalReport; + +namespace Application.Interfaces.UseCases.ProjectFinalReport +{ + public interface IDeleteProjectFinalReport + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportById.cs b/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportById.cs new file mode 100644 index 00000000..90622683 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportById.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectFinalReport; + +namespace Application.Interfaces.UseCases.ProjectFinalReport +{ + public interface IGetProjectFinalReportById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportByProjectId.cs b/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportByProjectId.cs new file mode 100644 index 00000000..cb091fb8 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectFinalReport/IGetProjectFinalReportByProjectId.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectFinalReport; + +namespace Application.Interfaces.UseCases.ProjectFinalReport +{ + public interface IGetProjectFinalReportByProjectId + { + Task ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectFinalReport/IUpdateProjectFinalReport.cs b/src/Application/Interfaces/UseCases/ProjectFinalReport/IUpdateProjectFinalReport.cs new file mode 100644 index 00000000..ad63c630 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectFinalReport/IUpdateProjectFinalReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectFinalReport; + +namespace Application.Interfaces.UseCases.ProjectFinalReport +{ + public interface IUpdateProjectFinalReport + { + Task ExecuteAsync(Guid? id, UpdateProjectFinalReportInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectPartialReport/ICreateProjectPartialReport.cs b/src/Application/Interfaces/UseCases/ProjectPartialReport/ICreateProjectPartialReport.cs new file mode 100644 index 00000000..c4073223 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectPartialReport/ICreateProjectPartialReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectPartialReport; + +namespace Application.Interfaces.UseCases.ProjectPartialReport +{ + public interface ICreateProjectPartialReport + { + Task ExecuteAsync(CreateProjectPartialReportInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectPartialReport/IDeleteProjectPartialReport.cs b/src/Application/Interfaces/UseCases/ProjectPartialReport/IDeleteProjectPartialReport.cs new file mode 100644 index 00000000..a1187a44 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectPartialReport/IDeleteProjectPartialReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectPartialReport; + +namespace Application.Interfaces.UseCases.ProjectPartialReport +{ + public interface IDeleteProjectPartialReport + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportById.cs b/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportById.cs new file mode 100644 index 00000000..296eb631 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportById.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectPartialReport; + +namespace Application.Interfaces.UseCases.ProjectPartialReport +{ + public interface IGetProjectPartialReportById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportByProjectId.cs b/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportByProjectId.cs new file mode 100644 index 00000000..c99c06d6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectPartialReport/IGetProjectPartialReportByProjectId.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectPartialReport; + +namespace Application.Interfaces.UseCases.ProjectPartialReport +{ + public interface IGetProjectPartialReportByProjectId + { + Task ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/ProjectPartialReport/IUpdateProjectPartialReport.cs b/src/Application/Interfaces/UseCases/ProjectPartialReport/IUpdateProjectPartialReport.cs new file mode 100644 index 00000000..e9a1ce61 --- /dev/null +++ b/src/Application/Interfaces/UseCases/ProjectPartialReport/IUpdateProjectPartialReport.cs @@ -0,0 +1,9 @@ +using Application.Ports.ProjectPartialReport; + +namespace Application.Interfaces.UseCases.ProjectPartialReport +{ + public interface IUpdateProjectPartialReport + { + Task ExecuteAsync(Guid? id, UpdateProjectPartialReportInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/ICreateStudent.cs b/src/Application/Interfaces/UseCases/Student/ICreateStudent.cs new file mode 100644 index 00000000..9cff403d --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/ICreateStudent.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface ICreateStudent + { + Task ExecuteAsync(CreateStudentInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IDeleteStudent.cs b/src/Application/Interfaces/UseCases/Student/IDeleteStudent.cs new file mode 100644 index 00000000..68d312b5 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IDeleteStudent.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface IDeleteStudent + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IGetStudentById.cs b/src/Application/Interfaces/UseCases/Student/IGetStudentById.cs new file mode 100644 index 00000000..5779e002 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IGetStudentById.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface IGetStudentById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IGetStudentByRegistrationCode.cs b/src/Application/Interfaces/UseCases/Student/IGetStudentByRegistrationCode.cs new file mode 100644 index 00000000..b91f13df --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IGetStudentByRegistrationCode.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface IGetStudentByRegistrationCode + { + Task ExecuteAsync(string? registrationCode); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IGetStudents.cs b/src/Application/Interfaces/UseCases/Student/IGetStudents.cs new file mode 100644 index 00000000..00dc7d7c --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IGetStudents.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface IGetStudents + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IRequestStudentRegister.cs b/src/Application/Interfaces/UseCases/Student/IRequestStudentRegister.cs new file mode 100644 index 00000000..63385866 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IRequestStudentRegister.cs @@ -0,0 +1,7 @@ +namespace Application.Interfaces.UseCases.Student +{ + public interface IRequestStudentRegister + { + Task ExecuteAsync(string? email); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/Student/IUpdateStudent.cs b/src/Application/Interfaces/UseCases/Student/IUpdateStudent.cs new file mode 100644 index 00000000..450e0126 --- /dev/null +++ b/src/Application/Interfaces/UseCases/Student/IUpdateStudent.cs @@ -0,0 +1,9 @@ +using Application.Ports.Student; + +namespace Application.Interfaces.UseCases.Student +{ + public interface IUpdateStudent + { + Task ExecuteAsync(Guid? id, UpdateStudentInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs b/src/Application/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs new file mode 100644 index 00000000..72581ff9 --- /dev/null +++ b/src/Application/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs @@ -0,0 +1,9 @@ +using Application.Ports.StudentDocuments; + +namespace Application.Interfaces.UseCases.StudentDocuments +{ + public interface ICreateStudentDocuments + { + Task ExecuteAsync(CreateStudentDocumentsInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs b/src/Application/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs new file mode 100644 index 00000000..5456fa24 --- /dev/null +++ b/src/Application/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs @@ -0,0 +1,9 @@ +using Application.Ports.StudentDocuments; + +namespace Application.Interfaces.UseCases.StudentDocuments +{ + public interface IDeleteStudentDocuments + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs b/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs new file mode 100644 index 00000000..5e2a1fe6 --- /dev/null +++ b/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs @@ -0,0 +1,9 @@ +using Application.Ports.StudentDocuments; + +namespace Application.Interfaces.UseCases.StudentDocuments +{ + public interface IGetStudentDocumentsByProjectId + { + Task ExecuteAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs b/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs new file mode 100644 index 00000000..1b9eae36 --- /dev/null +++ b/src/Application/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs @@ -0,0 +1,9 @@ +using Application.Ports.StudentDocuments; + +namespace Application.Interfaces.UseCases.StudentDocuments +{ + public interface IGetStudentDocumentsByStudentId + { + Task ExecuteAsync(Guid? studentId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs b/src/Application/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs new file mode 100644 index 00000000..c064f307 --- /dev/null +++ b/src/Application/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs @@ -0,0 +1,9 @@ +using Application.Ports.StudentDocuments; + +namespace Application.Interfaces.UseCases.StudentDocuments +{ + public interface IUpdateStudentDocuments + { + Task ExecuteAsync(Guid? id, UpdateStudentDocumentsInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/SubArea/ICreateSubArea.cs b/src/Application/Interfaces/UseCases/SubArea/ICreateSubArea.cs new file mode 100644 index 00000000..1dd9a203 --- /dev/null +++ b/src/Application/Interfaces/UseCases/SubArea/ICreateSubArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.SubArea; + +namespace Application.Interfaces.UseCases.SubArea +{ + public interface ICreateSubArea + { + Task ExecuteAsync(CreateSubAreaInput model); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/SubArea/IDeleteSubArea.cs b/src/Application/Interfaces/UseCases/SubArea/IDeleteSubArea.cs new file mode 100644 index 00000000..520ad262 --- /dev/null +++ b/src/Application/Interfaces/UseCases/SubArea/IDeleteSubArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.SubArea; + +namespace Application.Interfaces.UseCases.SubArea +{ + public interface IDeleteSubArea + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/SubArea/IGetSubAreaById.cs b/src/Application/Interfaces/UseCases/SubArea/IGetSubAreaById.cs new file mode 100644 index 00000000..5e061d7b --- /dev/null +++ b/src/Application/Interfaces/UseCases/SubArea/IGetSubAreaById.cs @@ -0,0 +1,9 @@ +using Application.Ports.SubArea; + +namespace Application.Interfaces.UseCases.SubArea +{ + public interface IGetSubAreaById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs b/src/Application/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs new file mode 100644 index 00000000..703a0f86 --- /dev/null +++ b/src/Application/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.SubArea; + +namespace Application.Interfaces.UseCases.SubArea +{ + public interface IGetSubAreasByArea + { + Task> ExecuteAsync(Guid? areaId, int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/SubArea/IUpdateSubArea.cs b/src/Application/Interfaces/UseCases/SubArea/IUpdateSubArea.cs new file mode 100644 index 00000000..f7446578 --- /dev/null +++ b/src/Application/Interfaces/UseCases/SubArea/IUpdateSubArea.cs @@ -0,0 +1,9 @@ +using Application.Ports.SubArea; + +namespace Application.Interfaces.UseCases.SubArea +{ + public interface IUpdateSubArea + { + Task ExecuteAsync(Guid? id, UpdateSubAreaInput input); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IActivateUser.cs b/src/Application/Interfaces/UseCases/User/IActivateUser.cs new file mode 100644 index 00000000..18c8401e --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IActivateUser.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IActivateUser + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IDeactivateUser.cs b/src/Application/Interfaces/UseCases/User/IDeactivateUser.cs new file mode 100644 index 00000000..6d7aecbe --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IDeactivateUser.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IDeactivateUser + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IGetActiveUsers.cs b/src/Application/Interfaces/UseCases/User/IGetActiveUsers.cs new file mode 100644 index 00000000..a8a152cf --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IGetActiveUsers.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IGetActiveUsers + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IGetInactiveUsers.cs b/src/Application/Interfaces/UseCases/User/IGetInactiveUsers.cs new file mode 100644 index 00000000..055cb3eb --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IGetInactiveUsers.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IGetInactiveUsers + { + Task> ExecuteAsync(int skip, int take); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IGetUserById.cs b/src/Application/Interfaces/UseCases/User/IGetUserById.cs new file mode 100644 index 00000000..bb2c58ae --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IGetUserById.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IGetUserById + { + Task ExecuteAsync(Guid? id); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IMakeAdmin.cs b/src/Application/Interfaces/UseCases/User/IMakeAdmin.cs new file mode 100644 index 00000000..1ba98137 --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IMakeAdmin.cs @@ -0,0 +1,7 @@ +namespace Application.Interfaces.UseCases.User +{ + public interface IMakeAdmin + { + Task ExecuteAsync(Guid? userId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IMakeCoordinator.cs b/src/Application/Interfaces/UseCases/User/IMakeCoordinator.cs new file mode 100644 index 00000000..b4fc73dd --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IMakeCoordinator.cs @@ -0,0 +1,12 @@ +namespace Application.Interfaces.UseCases.User +{ + public interface IMakeCoordinator + { + /// + /// Torna o usuário um coordenador e faz com que o coordenador anterior seja desativado. + /// + /// Id do usuário que será tornando coordenador. + /// Retorna uma mensagem de sucesso ou erro. + Task ExecuteAsync(Guid? userId); + } +} \ No newline at end of file diff --git a/src/Application/Interfaces/UseCases/User/IUpdateUser.cs b/src/Application/Interfaces/UseCases/User/IUpdateUser.cs new file mode 100644 index 00000000..4163c3c0 --- /dev/null +++ b/src/Application/Interfaces/UseCases/User/IUpdateUser.cs @@ -0,0 +1,9 @@ +using Application.Ports.User; + +namespace Application.Interfaces.UseCases.User +{ + public interface IUpdateUser + { + Task ExecuteAsync(UserUpdateInput input); + } +} \ No newline at end of file diff --git a/src/Application/Mappings/ActivityMappings.cs b/src/Application/Mappings/ActivityMappings.cs new file mode 100644 index 00000000..c0022061 --- /dev/null +++ b/src/Application/Mappings/ActivityMappings.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Activity; + +namespace Domain.Mappings +{ + public class ActivityMappings : Profile + { + public ActivityMappings() + { + _ = CreateMap(); + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.DeletedAt, opt => opt.MapFrom(src => src.DeletedAt)); + + _ = CreateMap(); + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.DeletedAt, opt => opt.MapFrom(src => src.DeletedAt)) + .ForMember(dest => dest.Activities, opt => opt.MapFrom(src => src.Activities)); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/AreaMappings.cs b/src/Application/Mappings/AreaMappings.cs new file mode 100644 index 00000000..1c4b1148 --- /dev/null +++ b/src/Application/Mappings/AreaMappings.cs @@ -0,0 +1,19 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Area; + +namespace Domain.Mappings +{ + public class AreaMappings : Profile + { + public AreaMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap() + .ForMember(dest => dest.MainArea, opt => opt.MapFrom(src => src.MainArea)) + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/AssistanceTypeMappings.cs b/src/Application/Mappings/AssistanceTypeMappings.cs new file mode 100644 index 00000000..c43ba436 --- /dev/null +++ b/src/Application/Mappings/AssistanceTypeMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.AssistanceType; + +namespace Domain.Mappings +{ + public class AssistanceTypeMappings : Profile + { + public AssistanceTypeMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/CampusMappings.cs b/src/Application/Mappings/CampusMappings.cs new file mode 100644 index 00000000..08ea17b3 --- /dev/null +++ b/src/Application/Mappings/CampusMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Campus; + +namespace Domain.Mappings +{ + public class CampusMappings : Profile + { + public CampusMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/CourseMappings.cs b/src/Application/Mappings/CourseMappings.cs new file mode 100644 index 00000000..e428840b --- /dev/null +++ b/src/Application/Mappings/CourseMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Course; + +namespace Domain.Mappings +{ + public class CourseMappings : Profile + { + public CourseMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/MainAreaMappings.cs b/src/Application/Mappings/MainAreaMappings.cs new file mode 100644 index 00000000..cacc66f6 --- /dev/null +++ b/src/Application/Mappings/MainAreaMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.MainArea; + +namespace Domain.Mappings +{ + public class MainAreaMappings : Profile + { + public MainAreaMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/NoticeMappings.cs b/src/Application/Mappings/NoticeMappings.cs new file mode 100644 index 00000000..fa5f9737 --- /dev/null +++ b/src/Application/Mappings/NoticeMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Notice; + +namespace Domain.Mappings +{ + public class NoticeMappings : Profile + { + public NoticeMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/ProfessorMappings.cs b/src/Application/Mappings/ProfessorMappings.cs new file mode 100644 index 00000000..5c3002b7 --- /dev/null +++ b/src/Application/Mappings/ProfessorMappings.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Professor; + +namespace Domain.Mappings +{ + public class ProfessorMappings : Profile + { + public ProfessorMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.Name, + opt => opt.MapFrom(src => src.User != null ? src.User.Name : null)) + .ForMember(dest => dest.Email, + opt => opt.MapFrom(src => src.User != null ? src.User.Email : null)) + .ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.User, + opt => opt.MapFrom(src => src.User)) + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/ProgramTypeMappings.cs b/src/Application/Mappings/ProgramTypeMappings.cs new file mode 100644 index 00000000..4ab76f8d --- /dev/null +++ b/src/Application/Mappings/ProgramTypeMappings.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.ProgramType; + +namespace Domain.Mappings +{ + public class ProgramTypeMappings : Profile + { + public ProgramTypeMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/ProjectEvaluationMappings.cs b/src/Application/Mappings/ProjectEvaluationMappings.cs new file mode 100644 index 00000000..538d31b1 --- /dev/null +++ b/src/Application/Mappings/ProjectEvaluationMappings.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.ProjectEvaluation; + +namespace Domain.Mappings +{ + public class ProjectEvaluationMappings : Profile + { + public ProjectEvaluationMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/ProjectMappings.cs b/src/Application/Mappings/ProjectMappings.cs new file mode 100644 index 00000000..3f461681 --- /dev/null +++ b/src/Application/Mappings/ProjectMappings.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Project; + +namespace Domain.Mappings +{ + public class ProjectMappings : Profile + { + public ProjectMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap() + .ForMember(dest => dest.ProfessorName, opt => opt.MapFrom(src => src.Professor.User.Name)) + .ForMember(dest => dest.StudentName, opt => opt.MapFrom(src => src.Student.User.Name)) + .ReverseMap(); + + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/StudentDocumentsMappings.cs b/src/Application/Mappings/StudentDocumentsMappings.cs new file mode 100644 index 00000000..7fda8144 --- /dev/null +++ b/src/Application/Mappings/StudentDocumentsMappings.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.StudentDocuments; + +namespace Domain.Mappings +{ + public class StudentDocumentsMappings : Profile + { + public StudentDocumentsMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/StudentMappings.cs b/src/Application/Mappings/StudentMappings.cs new file mode 100644 index 00000000..a54e21ae --- /dev/null +++ b/src/Application/Mappings/StudentMappings.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.Student; + +namespace Domain.Mappings +{ + public class StudentMappings : Profile + { + public StudentMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.Name, + opt => opt.MapFrom(src => src.User != null ? src.User.Name : null)) + .ForMember(dest => dest.Email, + opt => opt.MapFrom(src => src.User != null ? src.User.Email : null)) + .ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.User, + opt => opt.MapFrom(src => src.User)) + .ForMember(dest => dest.Campus, + opt => opt.MapFrom(src => src.Campus)) + .ForMember(dest => dest.Course, + opt => opt.MapFrom(src => src.Course)) + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/SubAreaMappings.cs b/src/Application/Mappings/SubAreaMappings.cs new file mode 100644 index 00000000..1ea92615 --- /dev/null +++ b/src/Application/Mappings/SubAreaMappings.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.SubArea; + +namespace Domain.Mappings +{ + public class SubAreaMappings : Profile + { + public SubAreaMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + _ = CreateMap() + .ForMember(dest => dest.Area, opt => opt.MapFrom(src => src.Area)) + .ForPath(dest => dest.Area!.MainArea, opt => opt.MapFrom(src => src.Area!.MainArea)) + .ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Mappings/UserMappings.cs b/src/Application/Mappings/UserMappings.cs new file mode 100644 index 00000000..aaa8892d --- /dev/null +++ b/src/Application/Mappings/UserMappings.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Domain.Entities; +using Application.Ports.User; + +namespace Domain.Mappings +{ + public class UserMappings : Profile + { + public UserMappings() + { + _ = CreateMap().ReverseMap(); + _ = CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/ActivityOutput.cs b/src/Application/Ports/Activity/ActivityOutput.cs new file mode 100644 index 00000000..7370f78c --- /dev/null +++ b/src/Application/Ports/Activity/ActivityOutput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Activity; +public class ActivityOutput : BaseActivity +{ + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/ActivityTypeOutput.cs b/src/Application/Ports/Activity/ActivityTypeOutput.cs new file mode 100644 index 00000000..547f3e47 --- /dev/null +++ b/src/Application/Ports/Activity/ActivityTypeOutput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.Activity +{ + public class ActivityTypeOutput : BaseActivityType + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + public new IList? Activities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/BaseActivity.cs b/src/Application/Ports/Activity/BaseActivity.cs new file mode 100644 index 00000000..391d17ff --- /dev/null +++ b/src/Application/Ports/Activity/BaseActivity.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Activity +{ + public class BaseActivity + { + [Required] + public string? Name { get; set; } + [Required] + public double? Points { get; set; } + public double? Limits { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/BaseActivityType.cs b/src/Application/Ports/Activity/BaseActivityType.cs new file mode 100644 index 00000000..8e89129a --- /dev/null +++ b/src/Application/Ports/Activity/BaseActivityType.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Activity +{ + public class BaseActivityType + { + [Required] + public string? Name { get; set; } + [Required] + public string? Unity { get; set; } + [Required] + public virtual IList? Activities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/CreateActivityInput.cs b/src/Application/Ports/Activity/CreateActivityInput.cs new file mode 100644 index 00000000..dbe9ea8b --- /dev/null +++ b/src/Application/Ports/Activity/CreateActivityInput.cs @@ -0,0 +1,4 @@ +namespace Application.Ports.Activity +{ + public class CreateActivityInput : BaseActivity { } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/CreateActivityTypeInput.cs b/src/Application/Ports/Activity/CreateActivityTypeInput.cs new file mode 100644 index 00000000..83c6386c --- /dev/null +++ b/src/Application/Ports/Activity/CreateActivityTypeInput.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Activity +{ + public class CreateActivityTypeInput + { + [Required] + public string? Name { get; set; } + [Required] + public string? Unity { get; set; } + [Required] + public virtual IList? Activities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/UpdateActivityInput.cs b/src/Application/Ports/Activity/UpdateActivityInput.cs new file mode 100644 index 00000000..3c6a48de --- /dev/null +++ b/src/Application/Ports/Activity/UpdateActivityInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Activity; +public class UpdateActivityInput : BaseActivity +{ + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } +} \ No newline at end of file diff --git a/src/Application/Ports/Activity/UpdateActivityTypeInput.cs b/src/Application/Ports/Activity/UpdateActivityTypeInput.cs new file mode 100644 index 00000000..0daa4a02 --- /dev/null +++ b/src/Application/Ports/Activity/UpdateActivityTypeInput.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Activity +{ + public class UpdateActivityTypeInput + { + [Required] + public string? Name { get; set; } + [Required] + public string? Unity { get; set; } + [Required] + public IList? Activities { get; set; } + + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Area/BaseAreaContract.cs b/src/Application/Ports/Area/BaseAreaContract.cs similarity index 87% rename from src/Domain/Contracts/Area/BaseAreaContract.cs rename to src/Application/Ports/Area/BaseAreaContract.cs index 9399d760..bc05760a 100644 --- a/src/Domain/Contracts/Area/BaseAreaContract.cs +++ b/src/Application/Ports/Area/BaseAreaContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Area +namespace Application.Ports.Area { public abstract class BaseAreaContract { diff --git a/src/Domain/Contracts/Area/CreateAreaInput.cs b/src/Application/Ports/Area/CreateAreaInput.cs similarity index 84% rename from src/Domain/Contracts/Area/CreateAreaInput.cs rename to src/Application/Ports/Area/CreateAreaInput.cs index d4e28aed..9d402fd2 100644 --- a/src/Domain/Contracts/Area/CreateAreaInput.cs +++ b/src/Application/Ports/Area/CreateAreaInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Area +namespace Application.Ports.Area { public class CreateAreaInput : BaseAreaContract { diff --git a/src/Domain/Contracts/Area/DetailedReadAreaOutput.cs b/src/Application/Ports/Area/DetailedReadAreaOutput.cs similarity index 76% rename from src/Domain/Contracts/Area/DetailedReadAreaOutput.cs rename to src/Application/Ports/Area/DetailedReadAreaOutput.cs index 47f450db..7ef4f72f 100644 --- a/src/Domain/Contracts/Area/DetailedReadAreaOutput.cs +++ b/src/Application/Ports/Area/DetailedReadAreaOutput.cs @@ -1,6 +1,6 @@ -using Domain.Contracts.MainArea; +using Application.Ports.MainArea; -namespace Domain.Contracts.Area +namespace Application.Ports.Area { public class DetailedReadAreaOutput : BaseAreaContract { diff --git a/src/Domain/Contracts/Area/ResumedReadAreaOutput.cs b/src/Application/Ports/Area/ResumedReadAreaOutput.cs similarity index 75% rename from src/Domain/Contracts/Area/ResumedReadAreaOutput.cs rename to src/Application/Ports/Area/ResumedReadAreaOutput.cs index 4de34b28..8137828f 100644 --- a/src/Domain/Contracts/Area/ResumedReadAreaOutput.cs +++ b/src/Application/Ports/Area/ResumedReadAreaOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Area +namespace Application.Ports.Area { public class ResumedReadAreaOutput : BaseAreaContract { diff --git a/src/Application/Ports/Area/UpdateAreaInput.cs b/src/Application/Ports/Area/UpdateAreaInput.cs new file mode 100644 index 00000000..9d6f7a38 --- /dev/null +++ b/src/Application/Ports/Area/UpdateAreaInput.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Area +{ + public class UpdateAreaInput : BaseAreaContract + { + [Required] + public Guid? MainAreaId { get; set; } + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/AssistanceType/BaseAssistanceTypeContract.cs b/src/Application/Ports/AssistanceType/BaseAssistanceTypeContract.cs new file mode 100644 index 00000000..4fc39f1f --- /dev/null +++ b/src/Application/Ports/AssistanceType/BaseAssistanceTypeContract.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.AssistanceType +{ + public abstract class BaseAssistanceTypeContract + { + [Required] + public string? Name { get; set; } + public string? Description { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/AssistanceType/CreateAssistanceTypeInput.cs b/src/Application/Ports/AssistanceType/CreateAssistanceTypeInput.cs new file mode 100644 index 00000000..7ed6c4b5 --- /dev/null +++ b/src/Application/Ports/AssistanceType/CreateAssistanceTypeInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.AssistanceType +{ + public class CreateAssistanceTypeInput : BaseAssistanceTypeContract + { + } +} \ No newline at end of file diff --git a/src/Application/Ports/AssistanceType/DetailedReadAssistanceTypeOutput.cs b/src/Application/Ports/AssistanceType/DetailedReadAssistanceTypeOutput.cs new file mode 100644 index 00000000..242d72be --- /dev/null +++ b/src/Application/Ports/AssistanceType/DetailedReadAssistanceTypeOutput.cs @@ -0,0 +1,8 @@ +namespace Application.Ports.AssistanceType +{ + public class DetailedReadAssistanceTypeOutput : BaseAssistanceTypeContract + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/AssistanceType/ResumedReadAssistanceTypeOutput.cs b/src/Application/Ports/AssistanceType/ResumedReadAssistanceTypeOutput.cs new file mode 100644 index 00000000..e056570e --- /dev/null +++ b/src/Application/Ports/AssistanceType/ResumedReadAssistanceTypeOutput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.AssistanceType +{ + public class ResumedReadAssistanceTypeOutput : BaseAssistanceTypeContract + { + public Guid? Id { get; set; } + } +} diff --git a/src/Application/Ports/AssistanceType/UpdateAssistanceTypeInput.cs b/src/Application/Ports/AssistanceType/UpdateAssistanceTypeInput.cs new file mode 100644 index 00000000..d3e279c3 --- /dev/null +++ b/src/Application/Ports/AssistanceType/UpdateAssistanceTypeInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.AssistanceType +{ + public class UpdateAssistanceTypeInput : BaseAssistanceTypeContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Auth/UserClaimsOutput.cs b/src/Application/Ports/Auth/UserClaimsOutput.cs similarity index 83% rename from src/Domain/Contracts/Auth/UserClaimsOutput.cs rename to src/Application/Ports/Auth/UserClaimsOutput.cs index 6cdf13e4..30cf391b 100644 --- a/src/Domain/Contracts/Auth/UserClaimsOutput.cs +++ b/src/Application/Ports/Auth/UserClaimsOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Auth +namespace Application.Ports.Auth { public class UserClaimsOutput { diff --git a/src/Application/Ports/Auth/UserLoginInput.cs b/src/Application/Ports/Auth/UserLoginInput.cs new file mode 100644 index 00000000..23bf67b9 --- /dev/null +++ b/src/Application/Ports/Auth/UserLoginInput.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Auth +{ + public class UserLoginInput + { + [Required] + public string? Email { get; set; } + [Required] + public string? Password { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Auth/UserLoginOutput.cs b/src/Application/Ports/Auth/UserLoginOutput.cs new file mode 100644 index 00000000..766652a6 --- /dev/null +++ b/src/Application/Ports/Auth/UserLoginOutput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Auth +{ + public class UserLoginOutput + { + public string? Token { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Auth/UserResetPasswordInput.cs b/src/Application/Ports/Auth/UserResetPasswordInput.cs similarity index 89% rename from src/Domain/Contracts/Auth/UserResetPasswordInput.cs rename to src/Application/Ports/Auth/UserResetPasswordInput.cs index 7cd4b556..a2b80c93 100644 --- a/src/Domain/Contracts/Auth/UserResetPasswordInput.cs +++ b/src/Application/Ports/Auth/UserResetPasswordInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Auth +namespace Application.Ports.Auth { public class UserResetPasswordInput { diff --git a/src/Domain/Contracts/Campus/BaseCampusContract.cs b/src/Application/Ports/Campus/BaseCampusContract.cs similarity index 82% rename from src/Domain/Contracts/Campus/BaseCampusContract.cs rename to src/Application/Ports/Campus/BaseCampusContract.cs index c401aa85..524a1904 100644 --- a/src/Domain/Contracts/Campus/BaseCampusContract.cs +++ b/src/Application/Ports/Campus/BaseCampusContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Campus +namespace Application.Ports.Campus { public abstract class BaseCampusContract { diff --git a/src/Application/Ports/Campus/CreateCampusInput.cs b/src/Application/Ports/Campus/CreateCampusInput.cs new file mode 100644 index 00000000..d98480ad --- /dev/null +++ b/src/Application/Ports/Campus/CreateCampusInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Campus +{ + public class CreateCampusInput : BaseCampusContract + { + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Campus/DetailedReadCampusOutput.cs b/src/Application/Ports/Campus/DetailedReadCampusOutput.cs similarity index 81% rename from src/Domain/Contracts/Campus/DetailedReadCampusOutput.cs rename to src/Application/Ports/Campus/DetailedReadCampusOutput.cs index 569e3842..61884d53 100644 --- a/src/Domain/Contracts/Campus/DetailedReadCampusOutput.cs +++ b/src/Application/Ports/Campus/DetailedReadCampusOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Campus +namespace Application.Ports.Campus { public class DetailedReadCampusOutput : BaseCampusContract { diff --git a/src/Domain/Contracts/Campus/ResumedReadCampusOutput.cs b/src/Application/Ports/Campus/ResumedReadCampusOutput.cs similarity index 75% rename from src/Domain/Contracts/Campus/ResumedReadCampusOutput.cs rename to src/Application/Ports/Campus/ResumedReadCampusOutput.cs index b150132f..d40f496a 100644 --- a/src/Domain/Contracts/Campus/ResumedReadCampusOutput.cs +++ b/src/Application/Ports/Campus/ResumedReadCampusOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Campus +namespace Application.Ports.Campus { public class ResumedReadCampusOutput : BaseCampusContract { diff --git a/src/Application/Ports/Campus/UpdateCampusInput.cs b/src/Application/Ports/Campus/UpdateCampusInput.cs new file mode 100644 index 00000000..e1c6c9f1 --- /dev/null +++ b/src/Application/Ports/Campus/UpdateCampusInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Campus +{ + public class UpdateCampusInput : BaseCampusContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Course/BaseCourseContract.cs b/src/Application/Ports/Course/BaseCourseContract.cs similarity index 82% rename from src/Domain/Contracts/Course/BaseCourseContract.cs rename to src/Application/Ports/Course/BaseCourseContract.cs index 9ff6ee36..6ad1e371 100644 --- a/src/Domain/Contracts/Course/BaseCourseContract.cs +++ b/src/Application/Ports/Course/BaseCourseContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Course +namespace Application.Ports.Course { public abstract class BaseCourseContract { diff --git a/src/Application/Ports/Course/CreateCourseInput.cs b/src/Application/Ports/Course/CreateCourseInput.cs new file mode 100644 index 00000000..8b8907a9 --- /dev/null +++ b/src/Application/Ports/Course/CreateCourseInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Course +{ + public class CreateCourseInput : BaseCourseContract + { + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Course/DetailedReadCourseOutput.cs b/src/Application/Ports/Course/DetailedReadCourseOutput.cs similarity index 81% rename from src/Domain/Contracts/Course/DetailedReadCourseOutput.cs rename to src/Application/Ports/Course/DetailedReadCourseOutput.cs index 0e988114..cbecbdb9 100644 --- a/src/Domain/Contracts/Course/DetailedReadCourseOutput.cs +++ b/src/Application/Ports/Course/DetailedReadCourseOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Course +namespace Application.Ports.Course { public class DetailedReadCourseOutput : BaseCourseContract { diff --git a/src/Domain/Contracts/Course/ResumedReadCourseOutput.cs b/src/Application/Ports/Course/ResumedReadCourseOutput.cs similarity index 75% rename from src/Domain/Contracts/Course/ResumedReadCourseOutput.cs rename to src/Application/Ports/Course/ResumedReadCourseOutput.cs index 8689d973..b3cd14b2 100644 --- a/src/Domain/Contracts/Course/ResumedReadCourseOutput.cs +++ b/src/Application/Ports/Course/ResumedReadCourseOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Course +namespace Application.Ports.Course { public class ResumedReadCourseOutput : BaseCourseContract { diff --git a/src/Application/Ports/Course/UpdateCourseInput.cs b/src/Application/Ports/Course/UpdateCourseInput.cs new file mode 100644 index 00000000..d5d96f0e --- /dev/null +++ b/src/Application/Ports/Course/UpdateCourseInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Course +{ + public class UpdateCourseInput : BaseCourseContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/MainArea/BaseMainAreaContract.cs b/src/Application/Ports/MainArea/BaseMainAreaContract.cs similarity index 86% rename from src/Domain/Contracts/MainArea/BaseMainAreaContract.cs rename to src/Application/Ports/MainArea/BaseMainAreaContract.cs index 5bd0c471..2cf2e4dc 100644 --- a/src/Domain/Contracts/MainArea/BaseMainAreaContract.cs +++ b/src/Application/Ports/MainArea/BaseMainAreaContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.MainArea +namespace Application.Ports.MainArea { public abstract class BaseMainAreaContract { diff --git a/src/Application/Ports/MainArea/CreateMainAreaInput.cs b/src/Application/Ports/MainArea/CreateMainAreaInput.cs new file mode 100644 index 00000000..77314192 --- /dev/null +++ b/src/Application/Ports/MainArea/CreateMainAreaInput.cs @@ -0,0 +1,4 @@ +namespace Application.Ports.MainArea +{ + public class CreateMainAreaInput : BaseMainAreaContract { } +} \ No newline at end of file diff --git a/src/Domain/Contracts/MainArea/DetailedMainAreaOutput.cs b/src/Application/Ports/MainArea/DetailedMainAreaOutput.cs similarity index 80% rename from src/Domain/Contracts/MainArea/DetailedMainAreaOutput.cs rename to src/Application/Ports/MainArea/DetailedMainAreaOutput.cs index ceec2c9d..720f7804 100644 --- a/src/Domain/Contracts/MainArea/DetailedMainAreaOutput.cs +++ b/src/Application/Ports/MainArea/DetailedMainAreaOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.MainArea +namespace Application.Ports.MainArea { public class DetailedMainAreaOutput : BaseMainAreaContract { diff --git a/src/Domain/Contracts/MainArea/ResumedReadMainAreaOutput.cs b/src/Application/Ports/MainArea/ResumedReadMainAreaOutput.cs similarity index 76% rename from src/Domain/Contracts/MainArea/ResumedReadMainAreaOutput.cs rename to src/Application/Ports/MainArea/ResumedReadMainAreaOutput.cs index 55003112..e726bac9 100644 --- a/src/Domain/Contracts/MainArea/ResumedReadMainAreaOutput.cs +++ b/src/Application/Ports/MainArea/ResumedReadMainAreaOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.MainArea +namespace Application.Ports.MainArea { public class ResumedReadMainAreaOutput : BaseMainAreaContract { diff --git a/src/Application/Ports/MainArea/UpdateMainAreaInput.cs b/src/Application/Ports/MainArea/UpdateMainAreaInput.cs new file mode 100644 index 00000000..da1a072c --- /dev/null +++ b/src/Application/Ports/MainArea/UpdateMainAreaInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.MainArea +{ + public class UpdateMainAreaInput : BaseMainAreaContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Notice/BaseNoticeContract.cs b/src/Application/Ports/Notice/BaseNoticeContract.cs new file mode 100644 index 00000000..123057bf --- /dev/null +++ b/src/Application/Ports/Notice/BaseNoticeContract.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.Notice +{ + public abstract class BaseNoticeContract + { + [Required] + public DateTime? RegistrationStartDate { get; set; } + [Required] + public DateTime? RegistrationEndDate { get; set; } + [Required] + public DateTime? EvaluationStartDate { get; set; } + [Required] + public DateTime? EvaluationEndDate { get; set; } + [Required] + public DateTime? AppealStartDate { get; set; } + [Required] + public DateTime? AppealEndDate { get; set; } + [Required] + public DateTime? SendingDocsStartDate { get; set; } + [Required] + public DateTime? SendingDocsEndDate { get; set; } + [Required] + public DateTime? PartialReportDeadline { get; set; } + [Required] + public DateTime? FinalReportDeadline { get; set; } + [Required] + public int? SuspensionYears { get; set; } + + public string? Description { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Notice/CreateNoticeInput.cs b/src/Application/Ports/Notice/CreateNoticeInput.cs new file mode 100644 index 00000000..f2b166c7 --- /dev/null +++ b/src/Application/Ports/Notice/CreateNoticeInput.cs @@ -0,0 +1,11 @@ +using Application.Ports.Activity; +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.Notice +{ + public class CreateNoticeInput : BaseNoticeContract + { + public IFormFile? File { get; set; } + public IList? Activities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Notice/DetailedReadNoticeOutput.cs b/src/Application/Ports/Notice/DetailedReadNoticeOutput.cs new file mode 100644 index 00000000..572b3ad7 --- /dev/null +++ b/src/Application/Ports/Notice/DetailedReadNoticeOutput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.Notice +{ + public class DetailedReadNoticeOutput : BaseNoticeContract + { + public Guid? Id { get; set; } + public string? DocUrl { get; set; } + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Notice/ResumedReadNoticeOutput.cs b/src/Application/Ports/Notice/ResumedReadNoticeOutput.cs new file mode 100644 index 00000000..9b9c42f2 --- /dev/null +++ b/src/Application/Ports/Notice/ResumedReadNoticeOutput.cs @@ -0,0 +1,8 @@ +namespace Application.Ports.Notice +{ + public class ResumedReadNoticeOutput : BaseNoticeContract + { + public Guid? Id { get; set; } + public string? DocUrl { get; set; } + } +} diff --git a/src/Application/Ports/Notice/UpdateNoticeInput.cs b/src/Application/Ports/Notice/UpdateNoticeInput.cs new file mode 100644 index 00000000..32fc9b42 --- /dev/null +++ b/src/Application/Ports/Notice/UpdateNoticeInput.cs @@ -0,0 +1,12 @@ +using Application.Ports.Activity; +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.Notice +{ + public class UpdateNoticeInput : BaseNoticeContract + { + public Guid? Id { get; set; } + public IFormFile? File { get; set; } + public IList? Activities { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Professor/BaseProfessorContract.cs b/src/Application/Ports/Professor/BaseProfessorContract.cs similarity index 86% rename from src/Domain/Contracts/Professor/BaseProfessorContract.cs rename to src/Application/Ports/Professor/BaseProfessorContract.cs index c51be82c..0b75309f 100644 --- a/src/Domain/Contracts/Professor/BaseProfessorContract.cs +++ b/src/Application/Ports/Professor/BaseProfessorContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Professor +namespace Application.Ports.Professor { public abstract class BaseProfessorContract { diff --git a/src/Domain/Contracts/Professor/CreateProfessorInput.cs b/src/Application/Ports/Professor/CreateProfessorInput.cs similarity index 84% rename from src/Domain/Contracts/Professor/CreateProfessorInput.cs rename to src/Application/Ports/Professor/CreateProfessorInput.cs index 2b2b87bd..2ac386b5 100644 --- a/src/Domain/Contracts/Professor/CreateProfessorInput.cs +++ b/src/Application/Ports/Professor/CreateProfessorInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Professor +namespace Application.Ports.Professor { public class CreateProfessorInput : BaseProfessorContract { @@ -13,6 +13,6 @@ public class CreateProfessorInput : BaseProfessorContract public string? Email { get; set; } [Required] public string? Password { get; set; } - #endregion + #endregion User Properties } } \ No newline at end of file diff --git a/src/Application/Ports/Professor/DetailedReadStudentOutput.cs b/src/Application/Ports/Professor/DetailedReadStudentOutput.cs new file mode 100644 index 00000000..20937ae6 --- /dev/null +++ b/src/Application/Ports/Professor/DetailedReadStudentOutput.cs @@ -0,0 +1,11 @@ +using Application.Ports.User; + +namespace Application.Ports.Professor +{ + public class DetailedReadProfessorOutput : BaseProfessorContract + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + public UserReadOutput? User { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Professor/ResumedReadStudentOutput.cs b/src/Application/Ports/Professor/ResumedReadStudentOutput.cs new file mode 100644 index 00000000..0348ab43 --- /dev/null +++ b/src/Application/Ports/Professor/ResumedReadStudentOutput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.Professor +{ + public class ResumedReadProfessorOutput : BaseProfessorContract + { + public Guid Id { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Professor/UpdateStudentInput.cs b/src/Application/Ports/Professor/UpdateStudentInput.cs new file mode 100644 index 00000000..b09ec418 --- /dev/null +++ b/src/Application/Ports/Professor/UpdateStudentInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Professor +{ + public class UpdateProfessorInput : BaseProfessorContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/ProgramType/BaseProgramTypeContract.cs b/src/Application/Ports/ProgramType/BaseProgramTypeContract.cs similarity index 84% rename from src/Domain/Contracts/ProgramType/BaseProgramTypeContract.cs rename to src/Application/Ports/ProgramType/BaseProgramTypeContract.cs index a5687fa4..27c9aa28 100644 --- a/src/Domain/Contracts/ProgramType/BaseProgramTypeContract.cs +++ b/src/Application/Ports/ProgramType/BaseProgramTypeContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.ProgramType +namespace Application.Ports.ProgramType { public abstract class BaseProgramTypeContract { diff --git a/src/Application/Ports/ProgramType/CreateProgramTypeInput.cs b/src/Application/Ports/ProgramType/CreateProgramTypeInput.cs new file mode 100644 index 00000000..88dc092d --- /dev/null +++ b/src/Application/Ports/ProgramType/CreateProgramTypeInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.ProgramType +{ + public class CreateProgramTypeInput : BaseProgramTypeContract + { + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/ProgramType/DetailedReadProgramTypeOutput.cs b/src/Application/Ports/ProgramType/DetailedReadProgramTypeOutput.cs similarity index 80% rename from src/Domain/Contracts/ProgramType/DetailedReadProgramTypeOutput.cs rename to src/Application/Ports/ProgramType/DetailedReadProgramTypeOutput.cs index 0c504c24..ed73361a 100644 --- a/src/Domain/Contracts/ProgramType/DetailedReadProgramTypeOutput.cs +++ b/src/Application/Ports/ProgramType/DetailedReadProgramTypeOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.ProgramType +namespace Application.Ports.ProgramType { public class DetailedReadProgramTypeOutput : BaseProgramTypeContract { diff --git a/src/Application/Ports/ProgramType/ResumedReadProgramTypeOutput.cs b/src/Application/Ports/ProgramType/ResumedReadProgramTypeOutput.cs new file mode 100644 index 00000000..dc877ce6 --- /dev/null +++ b/src/Application/Ports/ProgramType/ResumedReadProgramTypeOutput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.ProgramType +{ + public class ResumedReadProgramTypeOutput : BaseProgramTypeContract + { + public Guid? Id { get; set; } + } +} diff --git a/src/Application/Ports/ProgramType/UpdateProgramTypeInput.cs b/src/Application/Ports/ProgramType/UpdateProgramTypeInput.cs new file mode 100644 index 00000000..5c7f7f64 --- /dev/null +++ b/src/Application/Ports/ProgramType/UpdateProgramTypeInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.ProgramType +{ + public class UpdateProgramTypeInput : BaseProgramTypeContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Project/BaseProjectContract.cs b/src/Application/Ports/Project/BaseProjectContract.cs new file mode 100644 index 00000000..e025f684 --- /dev/null +++ b/src/Application/Ports/Project/BaseProjectContract.cs @@ -0,0 +1,46 @@ +namespace Application.Ports.Project +{ + public abstract class BaseProjectContract + { + #region Informações do Projeto + public string? Title { get; set; } + public string? KeyWord1 { get; set; } + public string? KeyWord2 { get; set; } + public string? KeyWord3 { get; set; } + public bool IsScholarshipCandidate { get; set; } + public string? Objective { get; set; } + public string? Methodology { get; set; } + public string? ExpectedResults { get; set; } + public string? ActivitiesExecutionSchedule { get; set; } + #endregion Informações do Projeto + + #region Resultados da Avaliação + public int? Status { get; set; } + public string? StatusDescription { get; set; } + public string? EvaluatorObservation { get; set; } + public string? AppealDescription { get; set; } + public string? AppealEvaluatorObservation { get; set; } + #endregion Resultados da Avaliação + + #region Relacionamentos + public Guid? ProgramTypeId { get; set; } + public Guid? ProfessorId { get; set; } + public Guid? StudentId { get; set; } + public Guid? SubAreaId { get; set; } + public Guid? NoticeId { get; set; } + #endregion Relacionamentos + + #region Informações de Controle + public DateTime? SubmitionDate { get; set; } + public DateTime? RessubmitionDate { get; set; } + public DateTime? CancellationDate { get; set; } + public string? CancellationReason { get; set; } + public DateTime SendingDocumentationDeadline { get; set; } + #endregion Informações de Controle + + #region Informações dos Envolvidos + public string? ProfessorName { get; set; } + public string? StudentName { get; set; } + #endregion Informações dos Envolvidos + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/DetailedReadProjectOutput.cs b/src/Application/Ports/Project/DetailedReadProjectOutput.cs similarity index 82% rename from src/Domain/Contracts/Project/DetailedReadProjectOutput.cs rename to src/Application/Ports/Project/DetailedReadProjectOutput.cs index bb43e51f..c4e399bb 100644 --- a/src/Domain/Contracts/Project/DetailedReadProjectOutput.cs +++ b/src/Application/Ports/Project/DetailedReadProjectOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.Project +namespace Application.Ports.Project { public class DetailedReadProjectOutput : BaseProjectContract { diff --git a/src/Application/Ports/Project/OpenProjectInput.cs b/src/Application/Ports/Project/OpenProjectInput.cs new file mode 100644 index 00000000..2faa3f23 --- /dev/null +++ b/src/Application/Ports/Project/OpenProjectInput.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; +using Application.Ports.ProjectActivity; + +namespace Application.Ports.Project +{ + public class OpenProjectInput + { + #region Informações do Projeto + [Required] + public string? Title { get; set; } + [Required] + public string? KeyWord1 { get; set; } + [Required] + public string? KeyWord2 { get; set; } + [Required] + public string? KeyWord3 { get; set; } + [Required] + public bool IsScholarshipCandidate { get; set; } + [Required] + public string? Objective { get; set; } + [Required] + public string? Methodology { get; set; } + [Required] + public string? ExpectedResults { get; set; } + [Required] + public string? ActivitiesExecutionSchedule { get; set; } + [Required] + public virtual IList? Activities { get; set; } + #endregion Informações do Projeto + + #region Relacionamentos + [Required] + public Guid? ProgramTypeId { get; set; } + [Required] + public Guid? ProfessorId { get; set; } + [Required] + public Guid? SubAreaId { get; set; } + [Required] + public Guid? NoticeId { get; set; } + public Guid? StudentId { get; set; } + #endregion Relacionamentos + } +} \ No newline at end of file diff --git a/src/Application/Ports/Project/ProjectReportInput.cs b/src/Application/Ports/Project/ProjectReportInput.cs new file mode 100644 index 00000000..d6ae8d43 --- /dev/null +++ b/src/Application/Ports/Project/ProjectReportInput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Project +{ + public class ProjectFinalReportInput + { + } +} \ No newline at end of file diff --git a/src/Application/Ports/Project/ProjectReportOutput.cs b/src/Application/Ports/Project/ProjectReportOutput.cs new file mode 100644 index 00000000..b654147c --- /dev/null +++ b/src/Application/Ports/Project/ProjectReportOutput.cs @@ -0,0 +1,6 @@ +namespace Application.Ports.Project +{ + public class ProjectFinalReportOutput + { + } +} \ No newline at end of file diff --git a/src/Application/Ports/Project/ResumedReadProjectOutput.cs b/src/Application/Ports/Project/ResumedReadProjectOutput.cs new file mode 100644 index 00000000..23ae0782 --- /dev/null +++ b/src/Application/Ports/Project/ResumedReadProjectOutput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Project +{ + public class ResumedReadProjectOutput : BaseProjectContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Project/UpdateProjectInput.cs b/src/Application/Ports/Project/UpdateProjectInput.cs new file mode 100644 index 00000000..57237316 --- /dev/null +++ b/src/Application/Ports/Project/UpdateProjectInput.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; +using Application.Ports.ProjectActivity; + +namespace Application.Ports.Project +{ + public class UpdateProjectInput + { + #region Informações do Projeto + [Required] + public string? Title { get; set; } + [Required] + public string? KeyWord1 { get; set; } + [Required] + public string? KeyWord2 { get; set; } + [Required] + public string? KeyWord3 { get; set; } + [Required] + public bool IsScholarshipCandidate { get; set; } + [Required] + public string? Objective { get; set; } + [Required] + public string? Methodology { get; set; } + [Required] + public string? ExpectedResults { get; set; } + [Required] + public string? ActivitiesExecutionSchedule { get; set; } + [Required] + public IList? Activities { get; set; } + #endregion Informações do Projeto + + #region Relacionamentos + [Required] + public Guid? ProgramTypeId { get; set; } + [Required] + public Guid? SubAreaId { get; set; } + public Guid? StudentId { get; set; } + #endregion Relacionamentos + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/BaseProjectActivityContract.cs b/src/Application/Ports/ProjectActivity/BaseProjectActivityContract.cs new file mode 100644 index 00000000..8927e354 --- /dev/null +++ b/src/Application/Ports/ProjectActivity/BaseProjectActivityContract.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectActivity +{ + public abstract class BaseProjectActivityContract + { + [Required] + public Guid? ActivityId { get; set; } + [Required] + public int? InformedActivities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/CreateProjectActivityInput.cs b/src/Application/Ports/ProjectActivity/CreateProjectActivityInput.cs new file mode 100644 index 00000000..e264f907 --- /dev/null +++ b/src/Application/Ports/ProjectActivity/CreateProjectActivityInput.cs @@ -0,0 +1,4 @@ +namespace Application.Ports.ProjectActivity +{ + public class CreateProjectActivityInput : BaseProjectActivityContract { } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/DetailedReadProjectActivityOutput.cs b/src/Application/Ports/ProjectActivity/DetailedReadProjectActivityOutput.cs new file mode 100644 index 00000000..6778d926 --- /dev/null +++ b/src/Application/Ports/ProjectActivity/DetailedReadProjectActivityOutput.cs @@ -0,0 +1,10 @@ +namespace Application.Ports.ProjectActivity +{ + public class DetailedReadProjectActivityOutput : BaseProjectActivityContract + { + public Guid? Id { get; set; } + public Guid? ProjectId { get; set; } + public int? FoundActivities { get; set; } + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/EvaluateProjectActivityInput.cs b/src/Application/Ports/ProjectActivity/EvaluateProjectActivityInput.cs new file mode 100644 index 00000000..ec711b0d --- /dev/null +++ b/src/Application/Ports/ProjectActivity/EvaluateProjectActivityInput.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectActivity +{ + public class EvaluateProjectActivityInput : BaseProjectActivityContract + { + [Required] + public Guid? ProjectId { get; set; } + [Required] + public int? FoundActivities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/ResumedReadProjectActivityOutput.cs b/src/Application/Ports/ProjectActivity/ResumedReadProjectActivityOutput.cs new file mode 100644 index 00000000..6bf17d0d --- /dev/null +++ b/src/Application/Ports/ProjectActivity/ResumedReadProjectActivityOutput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.ProjectActivity +{ + public class ResumedReadProjectActivityOutput : BaseProjectActivityContract + { + public Guid? Id { get; set; } + public Guid? ProjectId { get; set; } + public int? FoundActivities { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectActivity/UpdateProjectActivityInput.cs b/src/Application/Ports/ProjectActivity/UpdateProjectActivityInput.cs new file mode 100644 index 00000000..850da312 --- /dev/null +++ b/src/Application/Ports/ProjectActivity/UpdateProjectActivityInput.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectActivity +{ + public class UpdateProjectActivityInput : BaseProjectActivityContract + { + [Required] + public Guid? ProjectId { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs b/src/Application/Ports/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs new file mode 100644 index 00000000..d0542e9c --- /dev/null +++ b/src/Application/Ports/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs @@ -0,0 +1,27 @@ +namespace Application.Ports.ProjectEvaluation +{ + public class DetailedReadProjectEvaluationOutput + { + #region Informações Gerais da Avaliação + public Guid? ProjectId { get; set; } + public bool IsProductivityFellow { get; set; } + public Guid? SubmissionEvaluatorId { get; set; } + public int? SubmissionEvaluationStatus { get; set; } + public DateTime? SubmissionEvaluationDate { get; set; } + public string? SubmissionEvaluationDescription { get; set; } + public Guid? AppealEvaluatorId { get; set; } + public int? AppealEvaluationStatus { get; set; } + public DateTime? AppealEvaluationDate { get; set; } + public string? AppealEvaluationDescription { get; set; } + #endregion Informações Gerais da Avaliação + + #region Critérios de Avaliação + public int? APIndex { get; set; } + public int? Qualification { get; set; } + public int? ProjectProposalObjectives { get; set; } + public int? AcademicScientificProductionCoherence { get; set; } + public int? ProposalMethodologyAdaptation { get; set; } + public int? EffectiveContributionToResearch { get; set; } + #endregion Critérios de Avaliação + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectEvaluation/EvaluateAppealProjectInput.cs b/src/Application/Ports/ProjectEvaluation/EvaluateAppealProjectInput.cs new file mode 100644 index 00000000..eecca21e --- /dev/null +++ b/src/Application/Ports/ProjectEvaluation/EvaluateAppealProjectInput.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectEvaluation +{ + public class EvaluateAppealProjectInput + { + [Required] + public Guid? ProjectId { get; set; } + [Required] + public int? AppealEvaluationStatus { get; set; } + [Required] + public string? AppealEvaluationDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectEvaluation/EvaluateStudentDocumentsInput.cs b/src/Application/Ports/ProjectEvaluation/EvaluateStudentDocumentsInput.cs new file mode 100644 index 00000000..87c579d3 --- /dev/null +++ b/src/Application/Ports/ProjectEvaluation/EvaluateStudentDocumentsInput.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectEvaluation +{ + public class EvaluateStudentDocumentsInput + { + [Required] + public Guid? ProjectId { get; set; } + [Required] + public bool? IsDocumentsApproved { get; set; } + [Required] + public string? DocumentsEvaluationDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectEvaluation/EvaluateSubmissionProjectInput.cs b/src/Application/Ports/ProjectEvaluation/EvaluateSubmissionProjectInput.cs new file mode 100644 index 00000000..601048d4 --- /dev/null +++ b/src/Application/Ports/ProjectEvaluation/EvaluateSubmissionProjectInput.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using Application.Ports.ProjectActivity; + +namespace Application.Ports.ProjectEvaluation +{ + public class EvaluateSubmissionProjectInput + { + #region Informações Gerais da Avaliação + [Required] + public Guid? ProjectId { get; set; } + [Required] + public bool IsProductivityFellow { get; set; } + [Required] + public int? SubmissionEvaluationStatus { get; set; } + [Required] + public string? SubmissionEvaluationDescription { get; set; } + [Required] + public IList? Activities { get; set; } + #endregion Informações Gerais da Avaliação + + #region Critérios de Avaliação + [Required] + public int? Qualification { get; set; } + [Required] + public int? ProjectProposalObjectives { get; set; } + [Required] + public int? AcademicScientificProductionCoherence { get; set; } + [Required] + public int? ProposalMethodologyAdaptation { get; set; } + [Required] + public int? EffectiveContributionToResearch { get; set; } + #endregion Critérios de Avaliação + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectFinalReport/BaseProjectFinalReportContract.cs b/src/Application/Ports/ProjectFinalReport/BaseProjectFinalReportContract.cs new file mode 100644 index 00000000..ec268a56 --- /dev/null +++ b/src/Application/Ports/ProjectFinalReport/BaseProjectFinalReportContract.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectFinalReport +{ + public abstract class BaseProjectFinalReportContract + { + [Required] + public int? ReportType { get; set; } + [Required] + public Guid? ProjectId { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectFinalReport/CreateProjectFinalReportInput.cs b/src/Application/Ports/ProjectFinalReport/CreateProjectFinalReportInput.cs new file mode 100644 index 00000000..2edd8449 --- /dev/null +++ b/src/Application/Ports/ProjectFinalReport/CreateProjectFinalReportInput.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.ProjectFinalReport +{ + public class CreateProjectFinalReportInput : BaseProjectFinalReportContract + { + [Required] + public IFormFile? ReportFile { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectFinalReport/DetailedReadProjectFinalReportOutput.cs b/src/Application/Ports/ProjectFinalReport/DetailedReadProjectFinalReportOutput.cs new file mode 100644 index 00000000..0520f905 --- /dev/null +++ b/src/Application/Ports/ProjectFinalReport/DetailedReadProjectFinalReportOutput.cs @@ -0,0 +1,11 @@ +namespace Application.Ports.ProjectFinalReport +{ + public class DetailedReadProjectFinalReportOutput : BaseProjectFinalReportContract + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + public DateTime? SendDate { get; set; } + public string? ReportUrl { get; set; } + public Guid? UserId { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectFinalReport/UpdateProjectFinalReportInput.cs b/src/Application/Ports/ProjectFinalReport/UpdateProjectFinalReportInput.cs new file mode 100644 index 00000000..6a6118e3 --- /dev/null +++ b/src/Application/Ports/ProjectFinalReport/UpdateProjectFinalReportInput.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.ProjectFinalReport +{ + public class UpdateProjectFinalReportInput + { + [Required] + public IFormFile? ReportFile { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectPartialReport/BaseProjectPartialReportContract.cs b/src/Application/Ports/ProjectPartialReport/BaseProjectPartialReportContract.cs new file mode 100644 index 00000000..e1cb4b3e --- /dev/null +++ b/src/Application/Ports/ProjectPartialReport/BaseProjectPartialReportContract.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectPartialReport +{ + public abstract class BaseProjectPartialReportContract + { + [Required] + public Guid? ProjectId { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectPartialReport/CreateProjectPartialReportInput.cs b/src/Application/Ports/ProjectPartialReport/CreateProjectPartialReportInput.cs new file mode 100644 index 00000000..6597fd93 --- /dev/null +++ b/src/Application/Ports/ProjectPartialReport/CreateProjectPartialReportInput.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.ProjectPartialReport +{ + public class CreateProjectPartialReportInput : BaseProjectPartialReportContract + { + [Required] + public int CurrentDevelopmentStage { get; set; } + + [Required] + public int ScholarPerformance { get; set; } + + public string? AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectPartialReport/DetailedReadProjectPartialReportOutput.cs b/src/Application/Ports/ProjectPartialReport/DetailedReadProjectPartialReportOutput.cs new file mode 100644 index 00000000..315ef772 --- /dev/null +++ b/src/Application/Ports/ProjectPartialReport/DetailedReadProjectPartialReportOutput.cs @@ -0,0 +1,8 @@ +namespace Application.Ports.ProjectPartialReport +{ + public class DetailedReadProjectPartialReportOutput : BaseProjectPartialReportContract + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/ProjectPartialReport/UpdateProjectPartialReportInput.cs b/src/Application/Ports/ProjectPartialReport/UpdateProjectPartialReportInput.cs new file mode 100644 index 00000000..616a78df --- /dev/null +++ b/src/Application/Ports/ProjectPartialReport/UpdateProjectPartialReportInput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.ProjectPartialReport +{ + public class UpdateProjectPartialReportInput + { + public int? CurrentDevelopmentStage { get; set; } + public int? ScholarPerformance { get; set; } + public string? AdditionalInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/Student/BaseStudentContract.cs b/src/Application/Ports/Student/BaseStudentContract.cs similarity index 83% rename from src/Domain/Contracts/Student/BaseStudentContract.cs rename to src/Application/Ports/Student/BaseStudentContract.cs index 0ff884bb..d0a21c3e 100644 --- a/src/Domain/Contracts/Student/BaseStudentContract.cs +++ b/src/Application/Ports/Student/BaseStudentContract.cs @@ -1,11 +1,13 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Student +namespace Application.Ports.Student { public abstract class BaseStudentContract { #region Required Properties [Required] + public string? RegistrationCode { get; set; } + [Required] public DateTime BirthDate { get; set; } [Required] public long RG { get; set; } @@ -32,14 +34,14 @@ public abstract class BaseStudentContract [Required] public string? StartYear { get; set; } [Required] - public Guid? TypeAssistanceId { get; set; } - #endregion + public Guid? AssistanceTypeId { get; set; } + #endregion Required Properties #region Optional Properties public int? PhoneDDD { get; set; } public long? Phone { get; set; } public int? CellPhoneDDD { get; set; } public long? CellPhone { get; set; } - #endregion + #endregion Optional Properties } } \ No newline at end of file diff --git a/src/Domain/Contracts/Student/CreateStudentInput.cs b/src/Application/Ports/Student/CreateStudentInput.cs similarity index 84% rename from src/Domain/Contracts/Student/CreateStudentInput.cs rename to src/Application/Ports/Student/CreateStudentInput.cs index 2db24f04..4dfd32f8 100644 --- a/src/Domain/Contracts/Student/CreateStudentInput.cs +++ b/src/Application/Ports/Student/CreateStudentInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.Student +namespace Application.Ports.Student { public class CreateStudentInput : BaseStudentContract { @@ -13,6 +13,6 @@ public class CreateStudentInput : BaseStudentContract public string? Email { get; set; } [Required] public string? Password { get; set; } - #endregion + #endregion User Properties } } \ No newline at end of file diff --git a/src/Application/Ports/Student/DetailedReadStudentOutput.cs b/src/Application/Ports/Student/DetailedReadStudentOutput.cs new file mode 100644 index 00000000..12f5913b --- /dev/null +++ b/src/Application/Ports/Student/DetailedReadStudentOutput.cs @@ -0,0 +1,15 @@ +using Application.Ports.Campus; +using Application.Ports.Course; +using Application.Ports.User; + +namespace Application.Ports.Student +{ + public class DetailedReadStudentOutput : BaseStudentContract + { + public Guid? Id { get; set; } + public DateTime? DeletedAt { get; set; } + public UserReadOutput? User { get; set; } + public DetailedReadCourseOutput? Course { get; set; } + public DetailedReadCampusOutput? Campus { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Student/ResumedReadStudentOutput.cs b/src/Application/Ports/Student/ResumedReadStudentOutput.cs new file mode 100644 index 00000000..05c408b8 --- /dev/null +++ b/src/Application/Ports/Student/ResumedReadStudentOutput.cs @@ -0,0 +1,9 @@ +namespace Application.Ports.Student +{ + public class ResumedReadStudentOutput : BaseStudentContract + { + public Guid Id { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/Student/UpdateStudentInput.cs b/src/Application/Ports/Student/UpdateStudentInput.cs new file mode 100644 index 00000000..6234b0ef --- /dev/null +++ b/src/Application/Ports/Student/UpdateStudentInput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.Student +{ + public class UpdateStudentInput : BaseStudentContract + { + public Guid? Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/StudentDocuments/BaseStudentDocumentsOutput.cs b/src/Application/Ports/StudentDocuments/BaseStudentDocumentsOutput.cs similarity index 94% rename from src/Domain/Contracts/StudentDocuments/BaseStudentDocumentsOutput.cs rename to src/Application/Ports/StudentDocuments/BaseStudentDocumentsOutput.cs index 4e23d7b6..eb7b65e9 100644 --- a/src/Domain/Contracts/StudentDocuments/BaseStudentDocumentsOutput.cs +++ b/src/Application/Ports/StudentDocuments/BaseStudentDocumentsOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.StudentDocuments +namespace Application.Ports.StudentDocuments { public abstract class BaseStudentDocumentsOutput { @@ -35,7 +35,7 @@ public abstract class BaseStudentDocumentsOutput /// Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET) /// public string? ParentalAuthorization { get; set; } - #endregion + #endregion Documents #region BankData /// @@ -51,6 +51,6 @@ public abstract class BaseStudentDocumentsOutput /// Comprovante de Abertura de Conta /// public string? AccountOpeningProof { get; set; } - #endregion + #endregion BankData } } \ No newline at end of file diff --git a/src/Application/Ports/StudentDocuments/CreateStudentDocumentsInput.cs b/src/Application/Ports/StudentDocuments/CreateStudentDocumentsInput.cs new file mode 100644 index 00000000..be63978b --- /dev/null +++ b/src/Application/Ports/StudentDocuments/CreateStudentDocumentsInput.cs @@ -0,0 +1,65 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.StudentDocuments +{ + public class CreateStudentDocumentsInput + { + [Required] + public Guid? ProjectId { get; set; } + + #region Documents + /// + /// Cópia do documento de identidade com foto. + /// + [Required] + public IFormFile? IdentityDocument { get; set; } + + /// + /// Cópia do CPF + /// + [Required] + public IFormFile? CPF { get; set; } + + /// + /// Foto 3x4 + /// + [Required] + public IFormFile? Photo3x4 { get; set; } + + /// + /// Cópia atualizada do Histórico Escolar + /// + [Required] + public IFormFile? SchoolHistory { get; set; } + + /// + /// Termo de Compromisso do Bolsista assinado (Anexo II ou disponível na página do PIBIC) no caso de bolsista do CEFET/RJ + /// + [Required] + public IFormFile? ScholarCommitmentAgreement { get; set; } + + /// + /// Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET) + /// + public IFormFile? ParentalAuthorization { get; set; } + #endregion Documents + + #region BankData + /// + /// Número da Agência + /// + public string? AgencyNumber { get; set; } + /// + /// Número da Conta Corrente + /// + public string? AccountNumber { get; set; } + + /// + /// Comprovante de Abertura de Conta + /// + [Required] + public IFormFile? AccountOpeningProof { get; set; } + #endregion BankData + } +} \ No newline at end of file diff --git a/src/Application/Ports/StudentDocuments/DetailedReadStudentDocumentsOutput.cs b/src/Application/Ports/StudentDocuments/DetailedReadStudentDocumentsOutput.cs new file mode 100644 index 00000000..bbbc630e --- /dev/null +++ b/src/Application/Ports/StudentDocuments/DetailedReadStudentDocumentsOutput.cs @@ -0,0 +1,7 @@ +namespace Application.Ports.StudentDocuments +{ + public class DetailedReadStudentDocumentsOutput : BaseStudentDocumentsOutput + { + public DateTime? DeletedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Application/Ports/StudentDocuments/ResumedReadStudentDocumentsOutput.cs b/src/Application/Ports/StudentDocuments/ResumedReadStudentDocumentsOutput.cs new file mode 100644 index 00000000..b36f2897 --- /dev/null +++ b/src/Application/Ports/StudentDocuments/ResumedReadStudentDocumentsOutput.cs @@ -0,0 +1,4 @@ +namespace Application.Ports.StudentDocuments +{ + public class ResumedReadStudentDocumentsOutput : BaseStudentDocumentsOutput { } +} \ No newline at end of file diff --git a/src/Application/Ports/StudentDocuments/UpdateStudentDocumentsInput.cs b/src/Application/Ports/StudentDocuments/UpdateStudentDocumentsInput.cs new file mode 100644 index 00000000..0978b9cf --- /dev/null +++ b/src/Application/Ports/StudentDocuments/UpdateStudentDocumentsInput.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; + +namespace Application.Ports.StudentDocuments +{ + public class UpdateStudentDocumentsInput + { + /// + /// Cópia do documento de identidade com foto. + /// + public IFormFile? IdentityDocument { get; set; } + + /// + /// Cópia do CPF + /// + public IFormFile? CPF { get; set; } + + /// + /// Foto 3x4 + /// + public IFormFile? Photo3x4 { get; set; } + + /// + /// Cópia atualizada do Histórico Escolar + /// + public IFormFile? SchoolHistory { get; set; } + + /// + /// Termo de Compromisso do Bolsista assinado (Anexo II ou disponível na página do PIBIC) no caso de bolsista do CEFET/RJ + /// + public IFormFile? ScholarCommitmentAgreement { get; set; } + + /// + /// Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET) + /// + public IFormFile? ParentalAuthorization { get; set; } + + /// + /// Número da Agência + /// + public string? AgencyNumber { get; set; } + /// + /// Número da Conta Corrente + /// + public string? AccountNumber { get; set; } + + /// + /// Comprovante de Abertura de Conta + /// + public IFormFile? AccountOpeningProof { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/SubArea/BaseSubAreaContract.cs b/src/Application/Ports/SubArea/BaseSubAreaContract.cs similarity index 86% rename from src/Domain/Contracts/SubArea/BaseSubAreaContract.cs rename to src/Application/Ports/SubArea/BaseSubAreaContract.cs index 31413c68..39aa9bba 100644 --- a/src/Domain/Contracts/SubArea/BaseSubAreaContract.cs +++ b/src/Application/Ports/SubArea/BaseSubAreaContract.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.SubArea +namespace Application.Ports.SubArea { public abstract class BaseSubAreaContract { diff --git a/src/Application/Ports/SubArea/CreateSubAreaInput.cs b/src/Application/Ports/SubArea/CreateSubAreaInput.cs new file mode 100644 index 00000000..f6ed8681 --- /dev/null +++ b/src/Application/Ports/SubArea/CreateSubAreaInput.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.Ports.SubArea +{ + public class CreateSubAreaInput : BaseSubAreaContract + { + [Required] + public Guid? AreaId { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Contracts/SubArea/DetailedReadSubAreaOutput.cs b/src/Application/Ports/SubArea/DetailedReadSubAreaOutput.cs similarity index 77% rename from src/Domain/Contracts/SubArea/DetailedReadSubAreaOutput.cs rename to src/Application/Ports/SubArea/DetailedReadSubAreaOutput.cs index 1c707be9..56fb3f22 100644 --- a/src/Domain/Contracts/SubArea/DetailedReadSubAreaOutput.cs +++ b/src/Application/Ports/SubArea/DetailedReadSubAreaOutput.cs @@ -1,6 +1,6 @@ -using Domain.Contracts.Area; +using Application.Ports.Area; -namespace Domain.Contracts.SubArea +namespace Application.Ports.SubArea { public class DetailedReadSubAreaOutput : BaseSubAreaContract { diff --git a/src/Domain/Contracts/SubArea/ResumedReadSubAreaOutput.cs b/src/Application/Ports/SubArea/ResumedReadSubAreaOutput.cs similarity index 75% rename from src/Domain/Contracts/SubArea/ResumedReadSubAreaOutput.cs rename to src/Application/Ports/SubArea/ResumedReadSubAreaOutput.cs index 91e45161..25151306 100644 --- a/src/Domain/Contracts/SubArea/ResumedReadSubAreaOutput.cs +++ b/src/Application/Ports/SubArea/ResumedReadSubAreaOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.SubArea +namespace Application.Ports.SubArea { public class ResumedReadSubAreaOutput : BaseSubAreaContract { diff --git a/src/Domain/Contracts/SubArea/UpdateSubAreaInput.cs b/src/Application/Ports/SubArea/UpdateSubAreaInput.cs similarity index 85% rename from src/Domain/Contracts/SubArea/UpdateSubAreaInput.cs rename to src/Application/Ports/SubArea/UpdateSubAreaInput.cs index c8b445fc..1fdadbac 100644 --- a/src/Domain/Contracts/SubArea/UpdateSubAreaInput.cs +++ b/src/Application/Ports/SubArea/UpdateSubAreaInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.SubArea +namespace Application.Ports.SubArea { public class UpdateSubAreaInput : BaseSubAreaContract { diff --git a/src/Domain/Contracts/User/UserReadOutput.cs b/src/Application/Ports/User/UserReadOutput.cs similarity index 89% rename from src/Domain/Contracts/User/UserReadOutput.cs rename to src/Application/Ports/User/UserReadOutput.cs index 00d8ca19..2c6099e5 100644 --- a/src/Domain/Contracts/User/UserReadOutput.cs +++ b/src/Application/Ports/User/UserReadOutput.cs @@ -1,4 +1,4 @@ -namespace Domain.Contracts.User +namespace Application.Ports.User { public class UserReadOutput { diff --git a/src/Domain/Contracts/User/UserUpdateInput.cs b/src/Application/Ports/User/UserUpdateInput.cs similarity index 86% rename from src/Domain/Contracts/User/UserUpdateInput.cs rename to src/Application/Ports/User/UserUpdateInput.cs index 01d00cdf..1b2d77d3 100644 --- a/src/Domain/Contracts/User/UserUpdateInput.cs +++ b/src/Application/Ports/User/UserUpdateInput.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Domain.Contracts.User +namespace Application.Ports.User { public class UserUpdateInput { diff --git a/src/Application/UseCases/ActivityType/GetActivitiesByNoticeId.cs b/src/Application/UseCases/ActivityType/GetActivitiesByNoticeId.cs new file mode 100644 index 00000000..44cab29a --- /dev/null +++ b/src/Application/UseCases/ActivityType/GetActivitiesByNoticeId.cs @@ -0,0 +1,64 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Activity; +using Application.Interfaces.UseCases.ActivityType; + +namespace Application.UseCases.ActivityType +{ + public class GetActivitiesByNoticeId : IGetActivitiesByNoticeId + { + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IMapper _mapper; + public GetActivitiesByNoticeId(IActivityTypeRepository activityTypeRepository, IMapper mapper) + { + _activityTypeRepository = activityTypeRepository; + _mapper = mapper; + } + + public async Task> ExecuteAsync(Guid? noticeId) + { + // Obtém os tipos de atividades do edital + var activityTypes = await _activityTypeRepository.GetByNoticeIdAsync(noticeId); + + // Lista de tipos de atividades para o output + List activityTypesOutput = new(); + + // Se não houver tipos de atividades, retorna a lista vazia + if (activityTypes == null) + return activityTypesOutput; + + // Mapeia os tipos de atividades para o output + _ = _mapper.Map>(activityTypes); + + // Mapeia os tipos de atividades para o output + foreach (var activityType in activityTypes) + { + // Mapeia as atividades para o output + List activitiesOutput = new(); + foreach (var activity in activityType.Activities!) + { + activitiesOutput.Add(new ActivityOutput + { + Id = activity.Id, + Name = activity.Name, + Points = activity.Points, + Limits = activity.Limits, + DeletedAt = activity.DeletedAt + }); + } + + // Adiciona o tipo de atividade ao output + activityTypesOutput.Add(new ActivityTypeOutput + { + Id = activityType.Id, + Name = activityType.Name, + Unity = activityType.Unity, + DeletedAt = activityType.DeletedAt, + Activities = activitiesOutput + }); + } + + return activityTypesOutput; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ActivityType/GetLastNoticeActivities.cs b/src/Application/UseCases/ActivityType/GetLastNoticeActivities.cs new file mode 100644 index 00000000..048943b9 --- /dev/null +++ b/src/Application/UseCases/ActivityType/GetLastNoticeActivities.cs @@ -0,0 +1,65 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Activity; +using Application.Interfaces.UseCases.ActivityType; + +namespace Application.UseCases.ActivityType +{ + public class GetLastNoticeActivities : IGetLastNoticeActivities + { + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IMapper _mapper; + public GetLastNoticeActivities(IActivityTypeRepository activityTypeRepository, IMapper mapper) + { + _activityTypeRepository = activityTypeRepository; + _mapper = mapper; + } + + public async Task> ExecuteAsync() + { + // Obtém os tipos de atividades do último edital + var activityTypes = await _activityTypeRepository.GetLastNoticeActivitiesAsync(); + + // Lista de tipos de atividades para o output + List activityTypesOutput = new(); + + // Se não houver tipos de atividades, retorna a lista vazia + if (activityTypes == null) + return activityTypesOutput; + + // Mapeia os tipos de atividades para o output + _ = _mapper.Map>(activityTypes); + + // Mapeia os tipos de atividades para o output + foreach (var type in activityTypes) + { + // Mapeia as atividades para o output + List activitiesOutput = new(); + foreach (var activity in type.Activities!) + { + activitiesOutput.Add(new ActivityOutput + { + Id = activity.Id, + Name = activity.Name, + Points = activity.Points, + Limits = activity.Limits, + DeletedAt = activity.DeletedAt + }); + } + + // Adiciona o tipo de atividade ao output + activityTypesOutput.Add(new ActivityTypeOutput + { + Id = type.Id, + Name = type.Name, + Unity = type.Unity, + DeletedAt = type.DeletedAt, + Activities = activitiesOutput + }); + } + + // Retorna os tipos de atividades do último edital + return activityTypesOutput; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Area/CreateArea.cs b/src/Application/UseCases/Area/CreateArea.cs new file mode 100644 index 00000000..d74e44f3 --- /dev/null +++ b/src/Application/UseCases/Area/CreateArea.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; +using Application.Validation; + +namespace Application.UseCases.Area +{ + public class CreateArea : ICreateArea + { + #region Global Scope + private readonly IAreaRepository _areaRepository; + private readonly IMainAreaRepository _mainAreaRepository; + private readonly IMapper _mapper; + public CreateArea(IAreaRepository areaRepository, IMainAreaRepository mainAreaRepository, IMapper mapper) + { + _areaRepository = areaRepository; + _mainAreaRepository = mainAreaRepository; + _mapper = mapper; + } + #endregion + + public async Task ExecuteAsync(CreateAreaInput input) + { + var entity = await _areaRepository.GetByCodeAsync(input.Code); + UseCaseException.BusinessRuleViolation(entity != null, $"Já existe uma área principal para o código {input.Code}."); + + // Verifica id da área princial + UseCaseException.NotInformedParam(input.MainAreaId == null, nameof(input.MainAreaId)); + + // Valida se existe área principal + var area = await _mainAreaRepository.GetByIdAsync(input.MainAreaId); + UseCaseException.BusinessRuleViolation(area?.DeletedAt != null, "A Área Principal informada está inativa."); + + // Mapeia input para entidade + var newEntity = new Domain.Entities.Area(input.MainAreaId, input.Code, input.Name); + + // Cria nova área + entity = await _areaRepository.CreateAsync(newEntity); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Area/DeleteArea.cs b/src/Application/UseCases/Area/DeleteArea.cs new file mode 100644 index 00000000..2baaf480 --- /dev/null +++ b/src/Application/UseCases/Area/DeleteArea.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; +using Application.Validation; + +namespace Application.UseCases.Area +{ + public class DeleteArea : IDeleteArea + { + #region Global Scope + private readonly IAreaRepository _repository; + private readonly IMapper _mapper; + public DeleteArea(IAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id == null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna a área removida + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Area/GetAreaById.cs b/src/Application/UseCases/Area/GetAreaById.cs new file mode 100644 index 00000000..908b070f --- /dev/null +++ b/src/Application/UseCases/Area/GetAreaById.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; +using Application.Validation; + +namespace Application.UseCases.Area +{ + public class GetAreaById : IGetAreaById + { + #region Global Scope + private readonly IAreaRepository _repository; + private readonly IMapper _mapper; + public GetAreaById(IAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se Id foi informado. + UseCaseException.NotInformedParam(id is null, nameof(id)); + + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Area/GetAreasByMainArea.cs b/src/Application/UseCases/Area/GetAreasByMainArea.cs new file mode 100644 index 00000000..c9d5c69d --- /dev/null +++ b/src/Application/UseCases/Area/GetAreasByMainArea.cs @@ -0,0 +1,32 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; +using Application.Validation; + +namespace Application.UseCases.Area +{ + public class GetAreasByMainArea : IGetAreasByMainArea + { + #region Global Scope + private readonly IAreaRepository _repository; + private readonly IMapper _mapper; + public GetAreasByMainArea(IAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(Guid? mainAreaId, int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + UseCaseException.NotInformedParam(mainAreaId is null, nameof(mainAreaId)); + var entities = await _repository.GetAreasByMainAreaAsync(mainAreaId, skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Area/UpdateArea.cs b/src/Application/UseCases/Area/UpdateArea.cs new file mode 100644 index 00000000..943da93c --- /dev/null +++ b/src/Application/UseCases/Area/UpdateArea.cs @@ -0,0 +1,40 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; +using Application.Validation; + +namespace Application.UseCases.Area +{ + public class UpdateArea : IUpdateArea + { + #region Global Scope + private readonly IAreaRepository _repository; + private readonly IMapper _mapper; + public UpdateArea(IAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateAreaInput input) + { + // Verifica se Id foi informado. + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.MainArea)); + + // Atualiza atributos permitidos + entity.Name = input.Name; + entity.Code = input.Code; + entity.MainAreaId = input.MainAreaId; + + // Salva entidade atualizada no banco + var model = await _repository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/AssistanceType/CreateAssistanceType.cs b/src/Application/UseCases/AssistanceType/CreateAssistanceType.cs new file mode 100644 index 00000000..2264a879 --- /dev/null +++ b/src/Application/UseCases/AssistanceType/CreateAssistanceType.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.Validation; + +namespace Application.UseCases.AssistanceType +{ + public class CreateAssistanceType : ICreateAssistanceType + { + #region Global Scope + private readonly IAssistanceTypeRepository _repository; + private readonly IMapper _mapper; + public CreateAssistanceType(IAssistanceTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateAssistanceTypeInput model) + { + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(model.Name), nameof(model.Name)); + + // Verifica se já existe um tipo de programa com o nome indicado + var entity = await _repository.GetAssistanceTypeByNameAsync(model.Name!); + UseCaseException.BusinessRuleViolation(entity != null, + "Já existe um Tipo de Programa para o nome informado."); + + // Cria entidade + entity = await _repository.CreateAsync(_mapper.Map(model)); + + // Salva entidade no banco + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/AssistanceType/DeleteAssistanceType.cs b/src/Application/UseCases/AssistanceType/DeleteAssistanceType.cs new file mode 100644 index 00000000..c1587f24 --- /dev/null +++ b/src/Application/UseCases/AssistanceType/DeleteAssistanceType.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.Validation; + +namespace Application.UseCases.AssistanceType +{ + public class DeleteAssistanceType : IDeleteAssistanceType + { + #region Global Scope + private readonly IAssistanceTypeRepository _repository; + private readonly IMapper _mapper; + public DeleteAssistanceType(IAssistanceTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna o tipo de programa removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/AssistanceType/GetAssistanceTypeById.cs b/src/Application/UseCases/AssistanceType/GetAssistanceTypeById.cs new file mode 100644 index 00000000..640947fe --- /dev/null +++ b/src/Application/UseCases/AssistanceType/GetAssistanceTypeById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.Validation; + +namespace Application.UseCases.AssistanceType +{ + public class GetAssistanceTypeById : IGetAssistanceTypeById + { + #region Global Scope + private readonly IAssistanceTypeRepository _repository; + private readonly IMapper _mapper; + public GetAssistanceTypeById(IAssistanceTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/AssistanceType/GetAssistanceTypes.cs b/src/Application/UseCases/AssistanceType/GetAssistanceTypes.cs new file mode 100644 index 00000000..d0d1b565 --- /dev/null +++ b/src/Application/UseCases/AssistanceType/GetAssistanceTypes.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; + +namespace Application.UseCases.AssistanceType +{ + public class GetAssistanceTypes : IGetAssistanceTypes + { + #region Global Scope + private readonly IAssistanceTypeRepository _repository; + private readonly IMapper _mapper; + public GetAssistanceTypes(IAssistanceTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/AssistanceType/UpdateAssistanceType.cs b/src/Application/UseCases/AssistanceType/UpdateAssistanceType.cs new file mode 100644 index 00000000..d54020fa --- /dev/null +++ b/src/Application/UseCases/AssistanceType/UpdateAssistanceType.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Ports.AssistanceType; +using Application.Validation; + +namespace Application.UseCases.AssistanceType +{ + public class UpdateAssistanceType : IUpdateAssistanceType + { + #region Global Scope + private readonly IAssistanceTypeRepository _repository; + private readonly IMapper _mapper; + public UpdateAssistanceType(IAssistanceTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateAssistanceTypeInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); + + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.AssistanceType)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(entity.DeletedAt != null, + "O Bolsa de Assistência informado já foi excluído."); + + // Verifica se o nome já está sendo usado + UseCaseException.BusinessRuleViolation( + !string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) + && await _repository.GetAssistanceTypeByNameAsync(input.Name!) != null, + "Já existe um Bolsa de Assistência para o nome informado."); + + // Atualiza atributos permitidos + entity.Name = input.Name; + entity.Description = input.Description; + + // Salva entidade atualizada no banco + var model = await _repository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Auth/ConfirmEmail.cs b/src/Application/UseCases/Auth/ConfirmEmail.cs new file mode 100644 index 00000000..581cef1d --- /dev/null +++ b/src/Application/UseCases/Auth/ConfirmEmail.cs @@ -0,0 +1,39 @@ +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Auth; +using Application.Validation; + +namespace Application.UseCases.Auth +{ + public class ConfirmEmail : IConfirmEmail + { + #region Global Scope + private readonly IUserRepository _userRepository; + public ConfirmEmail(IUserRepository userRepository) + { + _userRepository = userRepository; + } + #endregion Global Scope + + public async Task ExecuteAsync(string? email, string? token) + { + // Verifica se o email é nulo + UseCaseException.NotInformedParam(string.IsNullOrEmpty(email), nameof(email)); + + // Verifica se o token é nulo + UseCaseException.NotInformedParam(string.IsNullOrEmpty(token), nameof(token)); + + // Busca usuário pelo email informado + var user = await _userRepository.GetUserByEmailAsync(email) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.User)); + + // Confirma usuário + user.ConfirmUserEmail(token!); + + // Atualiza usuário + await _userRepository.UpdateAsync(user); + + // Retorna mensagem de sucesso + return "Usuário confirmado com sucesso."; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Auth/ForgotPassword.cs b/src/Application/UseCases/Auth/ForgotPassword.cs new file mode 100644 index 00000000..486c36c8 --- /dev/null +++ b/src/Application/UseCases/Auth/ForgotPassword.cs @@ -0,0 +1,42 @@ +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Auth; +using Application.Validation; + +namespace Application.UseCases.Auth +{ + public class ForgotPassword : IForgotPassword + { + #region Global Scope + private readonly IUserRepository _userRepository; + private readonly IEmailService _emailService; + public ForgotPassword(IUserRepository userRepository, IEmailService emailService) + { + _emailService = emailService; + _userRepository = userRepository; + } + #endregion Global Scope + + public async Task ExecuteAsync(string? email) + { + // Verifica se o email é nulo + UseCaseException.NotInformedParam(string.IsNullOrEmpty(email), nameof(email)); + + // Busca usuário pelo email + var user = await _userRepository.GetUserByEmailAsync(email) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.User)); + + // Gera token de recuperação de senha + user.GenerateResetPasswordToken(); + + // Salva alterações + await _userRepository.UpdateAsync(user); + + // Envia email de recuperação de senha + await _emailService.SendResetPasswordEmailAsync(user.Email, user.Name, user.ResetPasswordToken); + + // Retorna token + return "Token de recuperação gerado e enviado por e-mail com sucesso."; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Auth/Login.cs b/src/Application/UseCases/Auth/Login.cs new file mode 100644 index 00000000..e38bca71 --- /dev/null +++ b/src/Application/UseCases/Auth/Login.cs @@ -0,0 +1,73 @@ +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Auth; +using Application.Ports.Auth; +using Application.Validation; + +namespace Application.UseCases.Auth +{ + public class Login : ILogin + { + #region Global Scope + private readonly ITokenAuthenticationService _tokenService; + private readonly IUserRepository _userRepository; + private readonly IProfessorRepository _professorRepository; + private readonly IStudentRepository _studentRepository; + private readonly IHashService _hashService; + public Login( + ITokenAuthenticationService tokenService, + IUserRepository userRepository, + IProfessorRepository professorRepository, + IStudentRepository studentRepository, + IHashService hashService) + { + _tokenService = tokenService; + _userRepository = userRepository; + _professorRepository = professorRepository; + _studentRepository = studentRepository; + _hashService = hashService; + } + #endregion Global Scope + + public async Task ExecuteAsync(UserLoginInput input) + { + // Verifica se o email é nulo + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Email), nameof(input.Email)); + + // Verifica se a senha é nula + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Password), nameof(input.Password)); + + // Busca usuário pelo email + var user = await _userRepository.GetUserByEmailAsync(input.Email) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.User)); + + // Verifica se o usuário está confirmado + UseCaseException.BusinessRuleViolation(!user.IsConfirmed, "O e-mail do usuário ainda não foi confirmado."); + + // Verifica se a senha é válida + UseCaseException.BusinessRuleViolation(!_hashService.VerifyPassword(input.Password!, user.Password), "Credenciais inválidas."); + + // Obtém id do professor ou do aluno + Guid? actorId; + if (user.Role == Domain.Entities.Enums.ERole.STUDENT) + { + var student = await _studentRepository.GetByUserIdAsync(user.Id) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.Student)); + actorId = student.Id; + } + // Professor ou Administrador + else + { + var professor = await _professorRepository.GetByUserIdAsync(user.Id) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.Professor)); + actorId = professor.Id; + } + + // Gera o token de autenticação e retorna o resultado + return new UserLoginOutput + { + Token = _tokenService.GenerateToken(user.Id, actorId, user.Name, user.Role.ToString()) + }; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Auth/ResetPassword.cs b/src/Application/UseCases/Auth/ResetPassword.cs new file mode 100644 index 00000000..af109169 --- /dev/null +++ b/src/Application/UseCases/Auth/ResetPassword.cs @@ -0,0 +1,58 @@ +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Auth; +using Application.Ports.Auth; +using Application.Validation; + +namespace Application.UseCases.Auth +{ + public class ResetPassword : IResetPassword + { + #region Global Scope + private readonly IUserRepository _userRepository; + private readonly IHashService _hashService; + public ResetPassword(IUserRepository userRepository, IHashService hashService) + { + _userRepository = userRepository; + _hashService = hashService; + } + #endregion Global Scope + + public async Task ExecuteAsync(UserResetPasswordInput input) + { + // Verifica se o id do usuário é nulo + UseCaseException.NotInformedParam(input.Id == null, nameof(input.Id)); + + // Verifica se a senha é nula + UseCaseException.NotInformedParam(input.Password == null, nameof(input.Password)); + + // Verifica se o token é nulo + UseCaseException.NotInformedParam(input.Token == null, nameof(input.Token)); + + // Busca o usuário pelo id + var entity = await _userRepository.GetByIdAsync(input.Id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + + // Verifica se o token de validação é nulo + if (string.IsNullOrEmpty(entity.ResetPasswordToken)) + { + throw UseCaseException.BusinessRuleViolation("Solicitação de atualização de senha não permitido."); + } + + // Verifica se o token de validação é igual ao token informado + input.Password = _hashService.HashPassword(input.Password!); + + // Atualiza a senha do usuário + if (!entity.UpdatePassword(input.Password, input.Token!)) + { + throw UseCaseException.BusinessRuleViolation("Token de validação inválido."); + } + + // Salva as alterações + _ = await _userRepository.UpdateAsync(entity); + + // Retorna o resultado + return "Senha atualizada com sucesso."; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Campus/CreateCampus.cs b/src/Application/UseCases/Campus/CreateCampus.cs new file mode 100644 index 00000000..e2413015 --- /dev/null +++ b/src/Application/UseCases/Campus/CreateCampus.cs @@ -0,0 +1,39 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; +using Application.Validation; + +namespace Application.UseCases.Campus +{ + public class CreateCampus : ICreateCampus + { + #region Global Scope + private readonly ICampusRepository _repository; + private readonly IMapper _mapper; + public CreateCampus(ICampusRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion + + public async Task ExecuteAsync(CreateCampusInput input) + { + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); + + // Verifica se já existe um edital para o período indicado + var entity = await _repository.GetCampusByNameAsync(input.Name!); + if (entity != null) + throw UseCaseException.BusinessRuleViolation("Já existe um Campus para o nome informado."); + + // Cria entidade + var newEntity = new Domain.Entities.Campus(input.Name); + entity = await _repository.CreateAsync(newEntity); + + // Salva entidade no banco + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Campus/DeleteCampus.cs b/src/Application/UseCases/Campus/DeleteCampus.cs new file mode 100644 index 00000000..67f85dc4 --- /dev/null +++ b/src/Application/UseCases/Campus/DeleteCampus.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; +using Application.Validation; + +namespace Application.UseCases.Campus +{ + public class DeleteCampus : IDeleteCampus + { + #region Global Scope + private readonly ICampusRepository _repository; + private readonly IMapper _mapper; + public DeleteCampus(ICampusRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna o curso removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Campus/GetCampusById.cs b/src/Application/UseCases/Campus/GetCampusById.cs new file mode 100644 index 00000000..5ba466c4 --- /dev/null +++ b/src/Application/UseCases/Campus/GetCampusById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; +using Application.Validation; + +namespace Application.UseCases.Campus +{ + public class GetCampusById : IGetCampusById + { + #region Global Scope + private readonly ICampusRepository _repository; + private readonly IMapper _mapper; + public GetCampusById(ICampusRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Campus/GetCampuses.cs b/src/Application/UseCases/Campus/GetCampuses.cs new file mode 100644 index 00000000..932a1017 --- /dev/null +++ b/src/Application/UseCases/Campus/GetCampuses.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; + +namespace Application.UseCases.Campus +{ + public class GetCampuses : IGetCampuses + { + #region Global Scope + private readonly ICampusRepository _repository; + private readonly IMapper _mapper; + public GetCampuses(ICampusRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Campus/UpdateCampus.cs b/src/Application/UseCases/Campus/UpdateCampus.cs new file mode 100644 index 00000000..d5611eda --- /dev/null +++ b/src/Application/UseCases/Campus/UpdateCampus.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Campus; +using Application.Ports.Campus; +using Application.Validation; + +namespace Application.UseCases.Campus +{ + public class UpdateCampus : IUpdateCampus + { + #region Global Scope + private readonly ICampusRepository _repository; + private readonly IMapper _mapper; + public UpdateCampus(ICampusRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateCampusInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); + + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) ?? throw new Exception("Campus não encontrado."); + + // Verifica se a entidade foi excluída + if (entity.DeletedAt != null) + { + throw UseCaseException.BusinessRuleViolation("O Campus informado já foi excluído."); + } + + // Verifica se o nome já está sendo usado + if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) && await _repository.GetCampusByNameAsync(input.Name!) != null) + { + throw UseCaseException.BusinessRuleViolation("Já existe um Campus para o nome informado."); + } + + // Atualiza atributos permitidos + entity.Name = input.Name; + + // Salva entidade atualizada no banco + var model = await _repository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Course/CreateCourse.cs b/src/Application/UseCases/Course/CreateCourse.cs new file mode 100644 index 00000000..4c14f57d --- /dev/null +++ b/src/Application/UseCases/Course/CreateCourse.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.Course; +using Application.Interfaces.UseCases.Course; +using Application.Validation; + +namespace Application.UseCases.Course +{ + public class CreateCourse : ICreateCourse + { + #region Global Scope + private readonly ICourseRepository _repository; + private readonly IMapper _mapper; + public CreateCourse(ICourseRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateCourseInput model) + { + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(model.Name), nameof(model.Name)); + + // Verifica se já existe um edital para o período indicado + var entity = await _repository.GetCourseByNameAsync(model.Name!); + UseCaseException.BusinessRuleViolation(entity != null, "Já existe um Curso para o nome informado."); + + // Cria entidade + Domain.Entities.Course newEntity = new(model.Name); + entity = await _repository.CreateAsync(newEntity); + + // Salva entidade no banco + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Course/DeleteCourse.cs b/src/Application/UseCases/Course/DeleteCourse.cs new file mode 100644 index 00000000..c00989eb --- /dev/null +++ b/src/Application/UseCases/Course/DeleteCourse.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.Validation; + +namespace Application.UseCases.Course +{ + public class DeleteCourse : IDeleteCourse + { + #region Global Scope + private readonly ICourseRepository _repository; + private readonly IMapper _mapper; + public DeleteCourse(ICourseRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna o curso removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Course/GetCourseById.cs b/src/Application/UseCases/Course/GetCourseById.cs new file mode 100644 index 00000000..dd3b1f9a --- /dev/null +++ b/src/Application/UseCases/Course/GetCourseById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.Validation; + +namespace Application.UseCases.Course +{ + public class GetCourseById : IGetCourseById + { + #region Global Scope + private readonly ICourseRepository _repository; + private readonly IMapper _mapper; + public GetCourseById(ICourseRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Course/GetCourses.cs b/src/Application/UseCases/Course/GetCourses.cs new file mode 100644 index 00000000..e00fd449 --- /dev/null +++ b/src/Application/UseCases/Course/GetCourses.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; + +namespace Application.UseCases.Course +{ + public class GetCourses : IGetCourses + { + #region Global Scope + private readonly ICourseRepository _repository; + private readonly IMapper _mapper; + public GetCourses(ICourseRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Course/UpdateCourse.cs b/src/Application/UseCases/Course/UpdateCourse.cs new file mode 100644 index 00000000..21ed5698 --- /dev/null +++ b/src/Application/UseCases/Course/UpdateCourse.cs @@ -0,0 +1,53 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Course; +using Application.Ports.Course; +using Application.Validation; + +namespace Application.UseCases.Course +{ + public class UpdateCourse : IUpdateCourse + { + #region Global Scope + private readonly ICourseRepository _repository; + private readonly IMapper _mapper; + public UpdateCourse(ICourseRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateCourseInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); + + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) ?? + throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Course)); + + // Verifica se a entidade foi excluída + if (entity.DeletedAt != null) + { + throw UseCaseException.BusinessRuleViolation("O Curso informado já foi excluído."); + } + + // Verifica se o nome já está sendo usado + if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) && await _repository.GetCourseByNameAsync(input.Name!) != null) + { + throw UseCaseException.BusinessRuleViolation("Já existe um Curso para o nome informado."); + } + + // Atualiza atributos permitidos + entity.Name = input.Name; + + // Salva entidade atualizada no banco + var model = await _repository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/MainArea/CreateMainArea.cs b/src/Application/UseCases/MainArea/CreateMainArea.cs new file mode 100644 index 00000000..adefbb65 --- /dev/null +++ b/src/Application/UseCases/MainArea/CreateMainArea.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.Validation; + +namespace Application.UseCases.MainArea +{ + public class CreateMainArea : ICreateMainArea + { + #region Global Scope + private readonly IMainAreaRepository _repository; + private readonly IMapper _mapper; + public CreateMainArea(IMainAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateMainAreaInput model) + { + // Validação de código da Área + var entity = await _repository.GetByCodeAsync(model.Code); + UseCaseException.BusinessRuleViolation(entity != null, $"Já existe uma Área Principal para o código {model.Code}"); + + entity = await _repository.CreateAsync(_mapper.Map(model)); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/MainArea/DeleteMainArea.cs b/src/Application/UseCases/MainArea/DeleteMainArea.cs new file mode 100644 index 00000000..0b69fe87 --- /dev/null +++ b/src/Application/UseCases/MainArea/DeleteMainArea.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.Validation; + +namespace Application.UseCases.MainArea +{ + public class DeleteMainArea : IDeleteMainArea + { + #region Global Scope + private readonly IMainAreaRepository _repository; + private readonly IMapper _mapper; + public DeleteMainArea(IMainAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna o edital removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/MainArea/GetMainAreaById.cs b/src/Application/UseCases/MainArea/GetMainAreaById.cs new file mode 100644 index 00000000..3b585573 --- /dev/null +++ b/src/Application/UseCases/MainArea/GetMainAreaById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.Validation; + +namespace Application.UseCases.MainArea +{ + public class GetMainAreaById : IGetMainAreaById + { + #region Global Scope + private readonly IMainAreaRepository _repository; + private readonly IMapper _mapper; + public GetMainAreaById(IMainAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/MainArea/GetMainAreas.cs b/src/Application/UseCases/MainArea/GetMainAreas.cs new file mode 100644 index 00000000..1a8b5be0 --- /dev/null +++ b/src/Application/UseCases/MainArea/GetMainAreas.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; + +namespace Application.UseCases.MainArea +{ + public class GetMainAreas : IGetMainAreas + { + #region Global Scope + private readonly IMainAreaRepository _repository; + private readonly IMapper _mapper; + public GetMainAreas(IMainAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/MainArea/UpdateMainArea.cs b/src/Application/UseCases/MainArea/UpdateMainArea.cs new file mode 100644 index 00000000..ebe5c1ae --- /dev/null +++ b/src/Application/UseCases/MainArea/UpdateMainArea.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.MainArea; +using Application.Ports.MainArea; +using Application.Validation; + +namespace Application.UseCases.MainArea +{ + public class UpdateMainArea : IUpdateMainArea + { + #region Global Scope + private readonly IMainAreaRepository _repository; + private readonly IMapper _mapper; + public UpdateMainArea(IMainAreaRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateMainAreaInput input) + { + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.BusinessRuleViolation("Área Principal não encontrada."); + + // Atualiza atributos permitidos + entity.Name = input.Name; + entity.Code = input.Code; + + // Salva entidade atualizada no banco + await _repository.UpdateAsync(entity); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/CreateNotice.cs b/src/Application/UseCases/Notice/CreateNotice.cs new file mode 100644 index 00000000..cca48f1a --- /dev/null +++ b/src/Application/UseCases/Notice/CreateNotice.cs @@ -0,0 +1,129 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; +using Microsoft.Extensions.Logging; + +namespace Application.UseCases.Notice +{ + public class CreateNotice : ICreateNotice + { + #region Global Scope + private readonly INoticeRepository _repository; + private readonly IStorageFileService _storageFileService; + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IActivityRepository _activityRepository; + private readonly IProfessorRepository _professorRepository; + private readonly IEmailService _emailService; + private readonly IMapper _mapper; + private readonly ILogger _logger; + public CreateNotice( + INoticeRepository repository, + IStorageFileService storageFileService, + IActivityTypeRepository activityTypeRepository, + IActivityRepository activityRepository, + IProfessorRepository professorRepository, + IEmailService emailService, + IMapper mapper, + ILogger logger) + { + _repository = repository; + _storageFileService = storageFileService; + _activityTypeRepository = activityTypeRepository; + _activityRepository = activityRepository; + _professorRepository = professorRepository; + _emailService = emailService; + _mapper = mapper; + _logger = logger; + } + #endregion + + public async Task ExecuteAsync(CreateNoticeInput input) + { + // Verifica se atividades foram informadas + UseCaseException.BusinessRuleViolation(input.Activities == null || input.Activities.Count == 0, + "As atividades devem ser informadas."); + + // Mapeia input para entidade + var notice = new Domain.Entities.Notice( + input.RegistrationStartDate, + input.RegistrationEndDate, + input.EvaluationStartDate, + input.EvaluationEndDate, + input.AppealStartDate, + input.AppealEndDate, + input.SendingDocsStartDate, + input.SendingDocsEndDate, + input.PartialReportDeadline, + input.FinalReportDeadline, + input.Description, + input.SuspensionYears + ); + + // Verifica se já existe um edital para o período indicado + var noticeFound = await _repository.GetNoticeByPeriodAsync((DateTime)input.RegistrationStartDate!, (DateTime)input.RegistrationEndDate!); + UseCaseException.BusinessRuleViolation(noticeFound != null, "Já existe um Edital para o período indicado."); + + // Salva arquivo no repositório e atualiza atributo DocUrl + if (input.File != null) + notice.DocUrl = await _storageFileService.UploadFileAsync(input.File); + + // Converte as atividades para entidades antes de prosseguir + // com o cadastro no banco, apenas para fins de validação. + foreach (var activityType in input.Activities!) + { + // Converte atividades para entidades + foreach (var activity in activityType.Activities!) + _ = new Domain.Entities.Activity(activity.Name, activity.Points, activity.Limits, Guid.Empty); + + // Converte tipo de atividade para entidade + _ = new Domain.Entities.ActivityType(activityType.Name, activityType.Unity, Guid.Empty); + } + + // Cria edital no banco + notice = await _repository.CreateAsync(notice); + + // Salva atividades no banco + foreach (var activityType in input.Activities!) + { + // Salva tipo de atividade no banco + var activityTypeEntity = new Domain.Entities.ActivityType(activityType.Name, activityType.Unity, notice.Id); + activityTypeEntity = await _activityTypeRepository.CreateAsync(activityTypeEntity); + + // Salva atividades no banco + foreach (var activity in activityType.Activities!) + { + var activityEntity = new Domain.Entities.Activity(activity.Name, activity.Points, activity.Limits, activityTypeEntity.Id); + await _activityRepository.CreateAsync(activityEntity); + } + } + + // Obtém professores ativos + var professors = await _professorRepository.GetAllActiveProfessorsAsync(); + + // Envia email de notificação para todos os professores ativos + foreach (var professor in professors) + { + try + { + // Envia email de notificação + await _emailService.SendNoticeEmailAsync( + professor.User?.Email, + professor.User?.Name, + notice.RegistrationStartDate, + notice.RegistrationEndDate, + notice.DocUrl); + } + catch (Exception ex) + { + _logger.LogError("Erro ao enviar email de notificação para o professor {0}. Detalhes: {Details}", professor?.User!.Name, ex); + } + } + + // Salva entidade no banco + return _mapper.Map(notice); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/DeleteNotice.cs b/src/Application/UseCases/Notice/DeleteNotice.cs new file mode 100644 index 00000000..6a0ceff0 --- /dev/null +++ b/src/Application/UseCases/Notice/DeleteNotice.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; + +namespace Application.UseCases.Notice +{ + public class DeleteNotice : IDeleteNotice + { + #region Global Scope + private readonly INoticeRepository _repository; + private readonly IStorageFileService _storageFileService; + private readonly IMapper _mapper; + public DeleteNotice(INoticeRepository repository, IStorageFileService storageFileService, IMapper mapper) + { + _repository = repository; + _storageFileService = storageFileService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id == null, nameof(id)); + + // Remove a entidade + var entity = await _repository.DeleteAsync(id); + + // Deleta o arquivo do edital + if (!string.IsNullOrEmpty(entity.DocUrl)) + { + await _storageFileService.DeleteFileAsync(entity.DocUrl); + } + + // Retorna o edital removido + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/GetNoticeById.cs b/src/Application/UseCases/Notice/GetNoticeById.cs new file mode 100644 index 00000000..84dc74fe --- /dev/null +++ b/src/Application/UseCases/Notice/GetNoticeById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; + +namespace Application.UseCases.Notice +{ + public class GetNoticeById : IGetNoticeById + { + #region Global Scope + private readonly INoticeRepository _repository; + private readonly IMapper _mapper; + public GetNoticeById(INoticeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/GetNotices.cs b/src/Application/UseCases/Notice/GetNotices.cs new file mode 100644 index 00000000..204d5215 --- /dev/null +++ b/src/Application/UseCases/Notice/GetNotices.cs @@ -0,0 +1,32 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; + +namespace Application.UseCases.Notice +{ + public class GetNotices : IGetNotices + { + #region Global Scope + private readonly INoticeRepository _repository; + private readonly IMapper _mapper; + public GetNotices(INoticeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + { + throw new ArgumentException("Parâmetros inválidos."); + } + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/ReportDeadlineNotification.cs b/src/Application/UseCases/Notice/ReportDeadlineNotification.cs new file mode 100644 index 00000000..0e1a3a85 --- /dev/null +++ b/src/Application/UseCases/Notice/ReportDeadlineNotification.cs @@ -0,0 +1,61 @@ +using Application.Interfaces.UseCases.Notice; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.Notice +{ + public class ReportDeadlineNotification : IReportDeadlineNotification + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IEmailService _emailService; + public ReportDeadlineNotification( + IProjectRepository projectRepository, + IEmailService emailService) + { + _projectRepository = projectRepository; + _emailService = emailService; + } + #endregion + + public async Task ExecuteAsync() + { + // Verifica se o há projetos que estejam no status Iniciado + var projects = await _projectRepository.GetProjectsWithCloseReportDueDateAsync(); + if (!projects.Any()) + return "Nenhum projeto com prazo de entrega de relatório próxima."; + + // Define datas de comparação + DateTime nextMonth = DateTime.UtcNow.AddMonths(1).Date; + DateTime nextWeek = DateTime.UtcNow.AddDays(7).Date; + + // Envia notificação para cada projeto + foreach (var project in projects) + { + // Verifica qual o relatório com data de entrega mais próxima + DateTime reportDeadline = project.Notice!.PartialReportDeadline!.Value.Date; + string reportType; + if (reportDeadline == nextWeek || reportDeadline == nextMonth) + { + reportType = "Relatório Parcial"; + } + else + { + reportType = "Relatório Final"; + reportDeadline = project.Notice!.FinalReportDeadline!.Value.Date; + } + + // Envia notificação para o professor + await _emailService.SendNotificationOfReportDeadlineEmailAsync( + project.Professor!.User!.Email, + project.Professor.User.Name, + project.Title, + reportType, + reportDeadline + ); + } + + return "Notificação de prazo de entrega de relatório enviada com sucesso."; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Notice/UpdateNotice.cs b/src/Application/UseCases/Notice/UpdateNotice.cs new file mode 100644 index 00000000..fa34b442 --- /dev/null +++ b/src/Application/UseCases/Notice/UpdateNotice.cs @@ -0,0 +1,199 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Ports.Activity; +using Application.Interfaces.UseCases.Notice; +using Application.Ports.Notice; +using Application.Validation; + +namespace Application.UseCases.Notice +{ + public class UpdateNotice : IUpdateNotice + { + #region Global Scope + private readonly INoticeRepository _repository; + private readonly IStorageFileService _storageFileService; + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IActivityRepository _activityRepository; + private readonly IMapper _mapper; + public UpdateNotice( + INoticeRepository repository, + IStorageFileService storageFileService, + IActivityTypeRepository activityTypeRepository, + IActivityRepository activityRepository, + IMapper mapper) + { + _repository = repository; + _storageFileService = storageFileService; + _activityTypeRepository = activityTypeRepository; + _activityRepository = activityRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateNoticeInput model) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id == null, nameof(id)); + + // Verifica se atividades foram informadas + UseCaseException.BusinessRuleViolation(model.Activities == null || model.Activities.Count == 0, + "As atividades devem ser informadas."); + + // Recupera entidade que será atualizada + var notice = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Notice)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(notice.DeletedAt != null, "O Edital inserido já foi excluído."); + + // Salva arquivo no repositório e atualiza atributo DocUrl + if (model.File != null) + { + notice.DocUrl = await _storageFileService.UploadFileAsync(model.File, notice.DocUrl); + } + + // Atualiza atributos permitidos + notice.RegistrationStartDate = model.RegistrationStartDate ?? notice.RegistrationStartDate; + notice.RegistrationEndDate = model.RegistrationEndDate ?? notice.RegistrationEndDate; + notice.EvaluationStartDate = model.EvaluationStartDate ?? notice.EvaluationStartDate; + notice.EvaluationEndDate = model.EvaluationEndDate ?? notice.EvaluationEndDate; + notice.AppealStartDate = model.AppealStartDate ?? notice.AppealStartDate; + notice.AppealEndDate = model.AppealEndDate ?? notice.AppealEndDate; + notice.SendingDocsStartDate = model.SendingDocsStartDate ?? notice.SendingDocsStartDate; + notice.SendingDocsEndDate = model.SendingDocsEndDate ?? notice.SendingDocsEndDate; + notice.PartialReportDeadline = model.PartialReportDeadline ?? notice.PartialReportDeadline; + notice.FinalReportDeadline = model.FinalReportDeadline ?? notice.FinalReportDeadline; + notice.Description = model.Description ?? notice.Description; + notice.SuspensionYears = model.SuspensionYears ?? notice.SuspensionYears; + + // Converte as atividades para entidades antes de prosseguir + // com a atualização no banco, apenas para fins de validação. + foreach (UpdateActivityTypeInput activityType in model.Activities!) + { + // Converte atividades para entidades + foreach (UpdateActivityInput activity in activityType.Activities!) + { + _ = new Domain.Entities.Activity(activity.Name, activity.Points, activity.Limits, Guid.Empty); + } + + // Converte tipo de atividade para entidade + _ = new Domain.Entities.ActivityType(activityType.Name, activityType.Unity, Guid.Empty); + } + + // Salva entidade atualizada no banco + _ = await _repository.UpdateAsync(notice); + + // Recupera atividades do edital + var noticeActivities = await _activityTypeRepository.GetByNoticeIdAsync(notice.Id); + + // Atualiza atividades + await HandleActivityType(model.Activities!, noticeActivities, notice.Id); + + // Retorna entidade atualizada + return _mapper.Map(notice); + } + + /// + /// Atualiza tipos de atividades e atividades. + /// + /// Lista de tipos de atividades que serão atualizados. + /// Lista de tipos de atividades que serão excluídos. + /// Id do edital. + private async Task HandleActivityType(IList newActivityTypes, IList oldActivityTypes, Guid? noticeId) + { + foreach (UpdateActivityTypeInput newActivityType in newActivityTypes) + { + // Verifica se o tipo de atividade já existe + var activityType = oldActivityTypes.FirstOrDefault(x => x.Id == newActivityType.Id); + + // Se o tipo de atividade não existir, cria um novo + if (activityType is null) + { + // Cria tipo de atividade + activityType = new Domain.Entities.ActivityType(newActivityType.Name, newActivityType.Unity, noticeId); + + // Salva tipo de atividade no banco + _ = await _activityTypeRepository.CreateAsync(activityType); + + // Cria atividades + await HandleActivity(newActivityType.Activities!, new List(), activityType.Id); + } + + // Se o tipo de atividade existir, atualiza + else + { + // Atualiza tipo de atividade + activityType.Name = newActivityType.Name; + activityType.Unity = newActivityType.Unity; + + // Salva tipo de atividade atualizado no banco + _ = await _activityTypeRepository.UpdateAsync(activityType); + + // Atualiza atividades + await HandleActivity(newActivityType.Activities!, activityType.Activities!, activityType.Id); + + // Remove tipo de atividade da lista de tipos de atividades do edital + _ = oldActivityTypes.Remove(activityType); + } + } + + // TODO: Validar remoção de tipos de atividade. + // Verifica se existem tipos de atividades que foram excluídos + foreach (Domain.Entities.ActivityType activityTypeToRemove in oldActivityTypes) + { + // Remove tipo de atividade do banco + _ = await _activityTypeRepository.DeleteAsync(activityTypeToRemove.Id); + } + } + + /// + /// Atualiza atividades. + /// + /// Lista de atividades que serão atualizadas. + /// Lista de atividades que serão excluídas. + /// Id do tipo de atividade. + private async Task HandleActivity(IList newActivities, IList oldActivities, Guid? activityTypeId) + { + // Verifica se existem atividades que foram criadas ou atualizadas + foreach (UpdateActivityInput newActivity in newActivities) + { + // Verifica se o tipo de atividade já existe + var activity = oldActivities.FirstOrDefault(x => x.Id == newActivity.Id); + + // Se o tipo de atividade não existir, cria um novo + if (activity is null) + { + // Cria atividade + activity = new Domain.Entities.Activity(newActivity.Name, newActivity.Points, newActivity.Limits, activityTypeId); + + // Salva atividade no banco + _ = await _activityRepository.CreateAsync(activity); + } + + // Se o tipo de atividade existir, atualiza + else + { + // Atualiza atividade + activity.Name = newActivity.Name; + activity.Points = newActivity.Points; + activity.Limits = newActivity.Limits; + + // Salva atividade atualizada no banco + _ = await _activityRepository.UpdateAsync(activity); + + // Remove atividade da lista de atividades do tipo de atividade + _ = oldActivities.Remove(activity); + } + } + + // TODO: Validar remoção de atividades. + // Verifica se existem atividades que foram excluídas + foreach (Domain.Entities.Activity activityToRemove in oldActivities) + { + // Remove atividade do banco + _ = await _activityRepository.DeleteAsync(activityToRemove.Id); + } + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Professor/CreateProfessor.cs b/src/Application/UseCases/Professor/CreateProfessor.cs new file mode 100644 index 00000000..c57b0cbb --- /dev/null +++ b/src/Application/UseCases/Professor/CreateProfessor.cs @@ -0,0 +1,74 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; + +namespace Application.UseCases.Professor +{ + public class CreateProfessor : ICreateProfessor + { + #region Global Scope + private readonly IProfessorRepository _professorRepository; + private readonly IUserRepository _userRepository; + private readonly IEmailService _emailService; + private readonly IHashService _hashService; + private readonly IMapper _mapper; + public CreateProfessor(IProfessorRepository professorRepository, + IUserRepository userRepository, + IEmailService emailService, + IHashService hashService, + IMapper mapper) + { + _professorRepository = professorRepository; + _userRepository = userRepository; + _emailService = emailService; + _hashService = hashService; + _mapper = mapper; + } + #endregion + + public async Task ExecuteAsync(CreateProfessorInput input) + { + // Realiza o map da entidade e a validação dos campos informados + var entity = new Domain.Entities.Professor(input.SIAPEEnrollment, input.IdentifyLattes); + + // Verifica se a senha é nula + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Password), nameof(input.Password)); + + // Verifica se já existe um usuário com o e-mail informado + var user = await _userRepository.GetUserByEmailAsync(input.Email); + UseCaseException.BusinessRuleViolation(user != null, "Já existe um usuário com o e-mail informado."); + + // Verifica se já existe um usuário com o CPF informado + user = await _userRepository.GetUserByCPFAsync(input.CPF); + UseCaseException.BusinessRuleViolation(user != null, "Já existe um usuário com o CPF informado."); + + // Gera hash da senha + input.Password = _hashService.HashPassword(input.Password!); + + // Cria usuário + user = new Domain.Entities.User(input.Name, + input.Email, + input.Password, + input.CPF, + Domain.Entities.Enums.ERole.PROFESSOR); + + // Adiciona usuário no banco + user = await _userRepository.CreateAsync(user); + UseCaseException.BusinessRuleViolation(user == null, "Não foi possível criar o usuário."); + + // Adiciona professor no banco + entity.UserId = user?.Id; + entity = await _professorRepository.CreateAsync(entity); + UseCaseException.BusinessRuleViolation(entity == null, "Não foi possível criar o professor."); + + // Envia e-mail de confirmação + await _emailService.SendConfirmationEmailAsync(user?.Email, user?.Name, user?.ValidationCode); + + // Salva entidade no banco + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Professor/DeleteProfessor.cs b/src/Application/UseCases/Professor/DeleteProfessor.cs new file mode 100644 index 00000000..b47bf9a8 --- /dev/null +++ b/src/Application/UseCases/Professor/DeleteProfessor.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; + +namespace Application.UseCases.Professor +{ + public class DeleteProfessor : IDeleteProfessor + { + #region Global Scope + private readonly IProfessorRepository _professorRepository; + private readonly IUserRepository _userRepository; + private readonly IMapper _mapper; + public DeleteProfessor(IProfessorRepository professorRepository, IUserRepository userRepository, IMapper mapper) + { + _professorRepository = professorRepository; + _userRepository = userRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se o professor existe + var professor = await _professorRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Professor)); + + // Verifica se o usuário existe + _ = await _userRepository.GetByIdAsync(professor.UserId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + + // Remove o professor + professor = await _professorRepository.DeleteAsync(id); + UseCaseException.BusinessRuleViolation(professor == null, "O professor não pôde ser removido."); + + // Remove o usuário + _ = await _userRepository.DeleteAsync(professor?.UserId) + ?? throw UseCaseException.BusinessRuleViolation("O usuário não pôde ser removido."); + + // Retorna o professor removido + return _mapper.Map(professor); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Professor/GetProfessorById.cs b/src/Application/UseCases/Professor/GetProfessorById.cs new file mode 100644 index 00000000..619c7e0b --- /dev/null +++ b/src/Application/UseCases/Professor/GetProfessorById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; + +namespace Application.UseCases.Professor +{ + public class GetProfessorById : IGetProfessorById + { + #region Global Scope + private readonly IProfessorRepository _repository; + private readonly IMapper _mapper; + public GetProfessorById(IProfessorRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Professor/GetProfessors.cs b/src/Application/UseCases/Professor/GetProfessors.cs new file mode 100644 index 00000000..5bea7372 --- /dev/null +++ b/src/Application/UseCases/Professor/GetProfessors.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; + +namespace Application.UseCases.Professor +{ + public class GetProfessors : IGetProfessors + { + #region Global Scope + private readonly IProfessorRepository _repository; + private readonly IMapper _mapper; + public GetProfessors(IProfessorRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Professor/UpdateProfessor.cs b/src/Application/UseCases/Professor/UpdateProfessor.cs new file mode 100644 index 00000000..f3b5697c --- /dev/null +++ b/src/Application/UseCases/Professor/UpdateProfessor.cs @@ -0,0 +1,47 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Professor; +using Application.Ports.Professor; +using Application.Validation; + +namespace Application.UseCases.Professor +{ + public class UpdateProfessor : IUpdateProfessor + { + #region Global Scope + private readonly IProfessorRepository _professorRepository; + private readonly IMapper _mapper; + public UpdateProfessor(IProfessorRepository professorRepository, IMapper mapper) + { + _professorRepository = professorRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateProfessorInput model) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Recupera entidade que será atualizada + var professor = await _professorRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Professor)); + + // Verifica se a entidade foi excluída + if (professor.DeletedAt != null) + { + throw UseCaseException.BusinessRuleViolation("O professor informado já foi excluído."); + } + + // Atualiza atributos permitidos + professor.IdentifyLattes = model.IdentifyLattes; + professor.SIAPEEnrollment = model.SIAPEEnrollment; + + // Atualiza professor com as informações fornecidas + professor = await _professorRepository.UpdateAsync(professor); + + // Salva entidade atualizada no banco + return _mapper.Map(professor); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProgramType/CreateProgramType.cs b/src/Application/UseCases/ProgramType/CreateProgramType.cs new file mode 100644 index 00000000..73ae0669 --- /dev/null +++ b/src/Application/UseCases/ProgramType/CreateProgramType.cs @@ -0,0 +1,37 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; + +namespace Application.UseCases.ProgramType +{ + public class CreateProgramType : ICreateProgramType + { + #region Global Scope + private readonly IProgramTypeRepository _repository; + private readonly IMapper _mapper; + public CreateProgramType(IProgramTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateProgramTypeInput model) + { + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(model.Name), nameof(model.Name)); + + // Verifica se já existe um tipo de programa com o nome indicado + var entity = await _repository.GetProgramTypeByNameAsync(model.Name!); + UseCaseException.BusinessRuleViolation(entity != null, "Já existe um Tipo de Programa para o nome informado."); + + // Cria entidade + entity = await _repository.CreateAsync(_mapper.Map(model)); + + // Salva entidade no banco + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProgramType/DeleteProgramType.cs b/src/Application/UseCases/ProgramType/DeleteProgramType.cs new file mode 100644 index 00000000..0cef52c0 --- /dev/null +++ b/src/Application/UseCases/ProgramType/DeleteProgramType.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; + +namespace Application.UseCases.ProgramType +{ + public class DeleteProgramType : IDeleteProgramType + { + #region Global Scope + private readonly IProgramTypeRepository _repository; + private readonly IMapper _mapper; + public DeleteProgramType(IProgramTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + var model = await _repository.DeleteAsync(id); + + // Retorna o tipo de programa removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProgramType/GetProgramTypeById.cs b/src/Application/UseCases/ProgramType/GetProgramTypeById.cs new file mode 100644 index 00000000..1dc9091a --- /dev/null +++ b/src/Application/UseCases/ProgramType/GetProgramTypeById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; + +namespace Application.UseCases.ProgramType +{ + public class GetProgramTypeById : IGetProgramTypeById + { + #region Global Scope + private readonly IProgramTypeRepository _repository; + private readonly IMapper _mapper; + public GetProgramTypeById(IProgramTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProgramType/GetProgramTypes.cs b/src/Application/UseCases/ProgramType/GetProgramTypes.cs new file mode 100644 index 00000000..2c08e34c --- /dev/null +++ b/src/Application/UseCases/ProgramType/GetProgramTypes.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProgramType; +using Application.Ports.ProgramType; + +namespace Application.UseCases.ProgramType +{ + public class GetProgramTypes : IGetProgramTypes + { + #region Global Scope + private readonly IProgramTypeRepository _repository; + private readonly IMapper _mapper; + public GetProgramTypes(IProgramTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + var entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProgramType/UpdateProgramType.cs b/src/Application/UseCases/ProgramType/UpdateProgramType.cs new file mode 100644 index 00000000..b7debd04 --- /dev/null +++ b/src/Application/UseCases/ProgramType/UpdateProgramType.cs @@ -0,0 +1,51 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProgramType; +using Application.Ports.ProgramType; +using Application.Validation; + +namespace Application.UseCases.ProgramType +{ + public class UpdateProgramType : IUpdateProgramType + { + #region Global Scope + private readonly IProgramTypeRepository _repository; + private readonly IMapper _mapper; + public UpdateProgramType(IProgramTypeRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateProgramTypeInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se nome foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); + + // Recupera entidade que será atualizada + var entity = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProgramType)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(entity.DeletedAt != null, "O Tipo de Programa informado já foi excluído."); + + // Verifica se o nome já está sendo usado + UseCaseException.BusinessRuleViolation( + !string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) + && await _repository.GetProgramTypeByNameAsync(input.Name!) != null, + "Já existe um Tipo de Programa para o nome informado."); + + // Atualiza atributos permitidos + entity.Name = input.Name; + entity.Description = input.Description; + + // Salva entidade atualizada no banco + var model = await _repository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/AppealProject.cs b/src/Application/UseCases/Project/AppealProject.cs new file mode 100644 index 00000000..2136d614 --- /dev/null +++ b/src/Application/UseCases/Project/AppealProject.cs @@ -0,0 +1,60 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class AppealProject : IAppealProject + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IMapper _mapper; + public AppealProject(IProjectRepository projectRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId, string? appealDescription) + { + // Verifica se Id foi informado. + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + + // Verifica se a descrição do recurso foi informada. + UseCaseException.NotInformedParam(string.IsNullOrWhiteSpace(appealDescription), + nameof(appealDescription)); + + // Verifica se o projeto existe + var project = await _projectRepository.GetByIdAsync(projectId!.Value) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto está em recurso + if (project.Status == EProjectStatus.Rejected) + { + // Verifica se o edital está na fase de recurso. + UseCaseException.BusinessRuleViolation(project.Notice?.AppealStartDate > DateTime.UtcNow + || project.Notice?.AppealEndDate < DateTime.UtcNow, + "O Edital não está na fase de Recurso."); + + // Altera o status do projeto para submetido + project.Status = EProjectStatus.Evaluation; + project.StatusDescription = EProjectStatus.Evaluation.GetDescription(); + project.AppealObservation = appealDescription; + project.AppealDate = DateTime.UtcNow; + + // Salva alterações no banco de dados + _ = await _projectRepository.UpdateAsync(project); + + // Retorna o projeto + return _mapper.Map(project); + } + + throw UseCaseException.BusinessRuleViolation("O projeto não está em uma fase que permita recurso."); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/CancelProject.cs b/src/Application/UseCases/Project/CancelProject.cs new file mode 100644 index 00000000..5f1c2c5e --- /dev/null +++ b/src/Application/UseCases/Project/CancelProject.cs @@ -0,0 +1,47 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class CancelProject : ICancelProject + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IMapper _mapper; + public CancelProject(IProjectRepository projectRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, string? observation) + { + // Verifica se o projeto existe + var project = await _projectRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto já não foi cancelado ou está encerrado + UseCaseException.BusinessRuleViolation( + project.Status is EProjectStatus.Canceled or EProjectStatus.Closed, + "Projeto já cancelado ou encerrado."); + + // Atualiza informações de cancelamento do projeto + project.Status = EProjectStatus.Canceled; + project.StatusDescription = EProjectStatus.Canceled.GetDescription(); + project.CancellationReason = observation; + project.CancellationDate = DateTime.UtcNow; + + // Atualiza projeto + project = await _projectRepository.UpdateAsync(project); + + // Mapeia entidade para output e retorna + return _mapper.Map(project); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/ClosePendingProjects.cs b/src/Application/UseCases/Project/ClosePendingProjects.cs new file mode 100644 index 00000000..28c57cae --- /dev/null +++ b/src/Application/UseCases/Project/ClosePendingProjects.cs @@ -0,0 +1,48 @@ +using Application.Interfaces.UseCases.Project; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; + +namespace Application.UseCases.Project +{ + public class ClosePendingProjects : IClosePendingProjects + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + public ClosePendingProjects( + IProjectRepository projectRepository) + { + _projectRepository = projectRepository; + } + #endregion + + public async Task ExecuteAsync() + { + /// Realiza o cancelamento de todos os projetos pendentes e com o prazo de resolução vendido. + /// Casos: + /// - Projetos abertos e com o prazo de submissão vencido; + // - Projetos rejeitados e com o prazo de recurso vencido; + // - Projetos aprovados e com entrega de documentação do aluno vencida; + // - Projetos pendentes de documentação e com o prazo de entrega vencido. + var projects = await _projectRepository.GetPendingAndOverdueProjectsAsync(); + + // Verifica se existem projetos pendentes e com prazo vencido + if (projects.Count == 0) + return "Nenhum projeto pendente e com prazo vencido foi encontrado."; + + // Atualiza status dos projetos + foreach (var project in projects) + { + project.Status = EProjectStatus.Canceled; + project.StatusDescription = "Projeto cancelado automaticamente por falta de ação dentro do prazo estipulado."; + } + + // Atualiza projetos no banco de dados + var cancelledProjects = await _projectRepository.UpdateManyAsync(projects); + if (cancelledProjects != projects.Count) + return $"Ocorreu um erro ao cancelar os projetos pendentes e com prazo vencido. Foram cancelados {cancelledProjects} de {projects.Count} projetos."; + + // Retorna mensagem de sucesso + return $"{projects.Count} projetos pendentes e com prazo de resolução vencido foram cancelados com sucesso."; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GenerateCertificate.cs b/src/Application/UseCases/Project/GenerateCertificate.cs new file mode 100644 index 00000000..0c514484 --- /dev/null +++ b/src/Application/UseCases/Project/GenerateCertificate.cs @@ -0,0 +1,119 @@ +using Application.Interfaces.UseCases.Project; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.Project +{ + public class GenerateCertificate : IGenerateCertificate + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly INoticeRepository _noticeRepository; + private readonly IProjectFinalReportRepository _projectReportRepository; + private readonly IProfessorRepository _professorRepository; + private readonly IUserRepository _userRepository; + private readonly IReportService _reportService; + private readonly IStorageFileService _storageFileService; + public GenerateCertificate( + IProjectRepository projectRepository, + INoticeRepository noticeRepository, + IProjectFinalReportRepository projectReportRepository, + IProfessorRepository professorRepository, + IUserRepository userRepository, + IReportService reportService, + IStorageFileService storageFileService) + { + _projectRepository = projectRepository; + _noticeRepository = noticeRepository; + _projectReportRepository = projectReportRepository; + _professorRepository = professorRepository; + _userRepository = userRepository; + _reportService = reportService; + _storageFileService = storageFileService; + } + #endregion + + public async Task ExecuteAsync() + { + // Busca edital que possui data final de entrega de relatório para o dia anterior + var notice = await _noticeRepository.GetNoticeEndingAsync(); + if (notice is null) + return "Nenhum edital em estágio de encerramento encontrado."; + + // Verifica se o há projetos que estejam no status Started ou Pending + var projects = await _projectRepository.GetProjectByNoticeAsync(notice!.Id); + if (!projects.Any()) + return "Nenhum projeto em estágio de encerramento encontrado."; + + // Obtém informações do coordenador + var coordinator = await _userRepository.GetCoordinatorAsync(); + if (coordinator is null) + return "Nenhum coordenador encontrado."; + + // Encerra cada projeto verificando se o mesmo possui relatório final + foreach (var project in projects) + { + // Verifica se o projeto possui relatório final + var finalReport = await _projectReportRepository.GetByProjectIdAsync(project.Id); + + // Se não possuir relatório final, não gera certificado e suspende o professor + if (finalReport is null) + { + // Suspende professor + var professor = await _professorRepository.GetByIdAsync(project.ProfessorId); + if (professor is not null) + { + professor.SuspensionEndDate = DateTime.Now.AddYears(notice.SuspensionYears ?? 0); + await _professorRepository.UpdateAsync(professor); + } + + // Encerra projeto + project.Status = EProjectStatus.Closed; + await _projectRepository.UpdateAsync(project); + } + else + { + // Gera nome único para o arquivo + var uniqueName = Guid.NewGuid().ToString() + ".pdf"; + + // Gera certificado + var path = await _reportService.GenerateCertificateAsync(project, coordinator.Name!, uniqueName); + + // Converte certificado para IFormFile a fim de enviá-lo para nuvem + var bytes = File.ReadAllBytes(path); + + // Envia certificado para nuvem e salva url no projeto + project.CertificateUrl = await _storageFileService.UploadFileAsync(bytes, path); + + // Encerra projeto + project.Status = EProjectStatus.Closed; + await _projectRepository.UpdateAsync(project); + + // Deleta certificado da pasta temporária + File.Delete(path); + } + } + + return "Certificados gerados com sucesso."; + } + + private async Task TestMethod() + { + var project = new Domain.Entities.Project("Project Title", "Keyword 1", "Keyword 2", "Keyword 3", true, "Objective", "Methodology", "Expected Results", "Schedule", Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), EProjectStatus.Opened, "Status Description", "Appeal Observation", DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow, "Cancellation Reason"); + var uniqueName = Guid.NewGuid().ToString() + ".pdf"; + var path = await _reportService.GenerateCertificateAsync(project, "Eduardo Paes", uniqueName); + + // Converte certificado para IFormFile a fim de enviá-lo para nuvem + var bytes = File.ReadAllBytes(path); + + // Envia certificado para nuvem e salva url no projeto + var url = await _storageFileService.UploadFileAsync(bytes, path); + + // Mostra URL do certificado + Console.WriteLine(url); + + return "Certificado gerado com sucesso. Url: " + url; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GetActivitiesByProjectId.cs b/src/Application/UseCases/Project/GetActivitiesByProjectId.cs new file mode 100644 index 00000000..a2cebc8f --- /dev/null +++ b/src/Application/UseCases/Project/GetActivitiesByProjectId.cs @@ -0,0 +1,46 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.ProjectActivity; +using Application.Interfaces.UseCases.Project; + +namespace Application.UseCases.Project +{ + public class GetActivitiesByProjectId : IGetActivitiesByProjectId + { + private readonly IProjectActivityRepository _projectActivityRepository; + public GetActivitiesByProjectId(IProjectActivityRepository projectActivityRepository) + { + _projectActivityRepository = projectActivityRepository; + } + + public async Task> ExecuteAsync(Guid? projectId) + { + // Obtém os tipos de atividades do edital + var projectActivities = await _projectActivityRepository.GetByProjectIdAsync(projectId); + + // Lista de tipos de atividades para o output + List projectActivitiesOutput = new(); + + // Se não houver tipos de atividades, retorna a lista vazia + if (projectActivities == null || projectActivities.Count == 0) + return projectActivitiesOutput; + + // Mapeia os tipos de atividades para o output + foreach (var projectActivity in projectActivities) + { + // Adiciona o tipo de atividade ao output + projectActivitiesOutput.Add(new DetailedReadProjectActivityOutput + { + Id = projectActivity.Id, + ActivityId = projectActivity.ActivityId, + ProjectId = projectActivity.ProjectId, + InformedActivities = projectActivity.InformedActivities, + DeletedAt = projectActivity.DeletedAt, + FoundActivities = projectActivity.FoundActivities + }); + } + + return projectActivitiesOutput; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GetClosedProjects.cs b/src/Application/UseCases/Project/GetClosedProjects.cs new file mode 100644 index 00000000..7bc10e2e --- /dev/null +++ b/src/Application/UseCases/Project/GetClosedProjects.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class GetClosedProjects : IGetClosedProjects + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public GetClosedProjects(IProjectRepository projectRepository, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectRepository = projectRepository; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take, bool onlyMyProjects = true) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + // Obtém as claims do usuário autenticado. + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims?.Values.FirstOrDefault(); + var actorId = userClaims?.Keys.FirstOrDefault(); + + // Se o usuário não estiver autenticado, lança uma exceção. + UseCaseException.BusinessRuleViolation(userClaim == null || userClaim.Role == null, + "Usuário não autorizado."); + + // Obtém a lista de projetos de acordo com o tipo de usuário. + IEnumerable projects; + + // Se o usuário for um professor, retorna apenas os seus projetos. + if (userClaim?.Role == ERole.PROFESSOR) + { + projects = await _projectRepository.GetProfessorProjectsAsync(skip, take, actorId, true); + } + + // Se o usuário for um aluno, retorna apenas os seus projetos. + else if (userClaim?.Role == ERole.STUDENT) + { + projects = await _projectRepository.GetStudentProjectsAsync(skip, take, actorId, true); + } + + // Se o usuário for um administrador, permite a busca apenas pelo seu ID. + else + { + projects = userClaim?.Role == ERole.ADMIN && onlyMyProjects + ? await _projectRepository.GetProfessorProjectsAsync(skip, take, actorId, true) + : userClaim?.Role == ERole.ADMIN && !onlyMyProjects + ? await _projectRepository.GetProjectsAsync(skip, take, true) + : throw UseCaseException.BusinessRuleViolation("Usuário não autorizado."); + } + + // Mapeia a lista de projetos para uma lista de projetos resumidos e retorna. + return _mapper.Map>(projects); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GetOpenProjects.cs b/src/Application/UseCases/Project/GetOpenProjects.cs new file mode 100644 index 00000000..21236a61 --- /dev/null +++ b/src/Application/UseCases/Project/GetOpenProjects.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class GetOpenProjects : IGetOpenProjects + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public GetOpenProjects(IProjectRepository projectRepository, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectRepository = projectRepository; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take, bool onlyMyProjects = true) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + // Obtém as claims do usuário autenticado. + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims?.Values.FirstOrDefault(); + var actorId = userClaims?.Keys.FirstOrDefault(); + + // Se o usuário não estiver autenticado, lança uma exceção. + UseCaseException.BusinessRuleViolation(userClaim == null || userClaim.Role == null, + "Usuário não autorizado."); + + // Obtém a lista de projetos de acordo com o tipo de usuário. + IEnumerable projects; + + // Se o usuário for um professor, retorna apenas os seus projetos. + if (userClaim?.Role == ERole.PROFESSOR) + { + projects = await _projectRepository.GetProfessorProjectsAsync(skip, take, actorId); + } + + // Se o usuário for um aluno, retorna apenas os seus projetos. + else if (userClaim?.Role == ERole.STUDENT) + { + projects = await _projectRepository.GetStudentProjectsAsync(skip, take, actorId); + } + + // Se o usuário for um administrador, permite a busca apenas pelo seu ID. + else + { + projects = userClaim?.Role == ERole.ADMIN && onlyMyProjects + ? await _projectRepository.GetProfessorProjectsAsync(skip, take, actorId) + : userClaim?.Role == ERole.ADMIN && !onlyMyProjects + ? await _projectRepository.GetProjectsAsync(skip, take) + : throw UseCaseException.BusinessRuleViolation("Usuário não autorizado."); + } + + // Mapeia a lista de projetos para uma lista de projetos resumidos e retorna. + return _mapper.Map>(projects); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GetProjectById.cs b/src/Application/UseCases/Project/GetProjectById.cs new file mode 100644 index 00000000..da565974 --- /dev/null +++ b/src/Application/UseCases/Project/GetProjectById.cs @@ -0,0 +1,32 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class GetProjectById : IGetProjectById + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IMapper _mapper; + public GetProjectById(IProjectRepository projectRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Busca projeto pelo Id informado + var project = await _projectRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Mapeia entidade para output e retorna + return _mapper.Map(project); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/GetProjectsToEvaluate.cs b/src/Application/UseCases/Project/GetProjectsToEvaluate.cs new file mode 100644 index 00000000..f9a93371 --- /dev/null +++ b/src/Application/UseCases/Project/GetProjectsToEvaluate.cs @@ -0,0 +1,50 @@ +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.Project +{ + public class GetProjectsToEvaluate : IGetProjectsToEvaluate + { + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IProjectRepository _projectRepository; + private readonly IMapper _mapper; + public GetProjectsToEvaluate(ITokenAuthenticationService tokenAuthenticationService, + IProjectRepository projectRepository, + IMapper mapper) + { + _tokenAuthenticationService = tokenAuthenticationService; + _projectRepository = projectRepository; + _mapper = mapper; + } + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + // Obtém o usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Verifica se o usuário logado é um professor ou administrador + UseCaseException.BusinessRuleViolation(userClaim!.Role != ERole.ADMIN && userClaim.Role != ERole.PROFESSOR, + "Usuário sem permissão para avaliar projetos."); + + // Obtém todos os projetos que estão na fase de avaliação (Submitted, Evaluation, DocumentAnalysis) + // e que o usuário logado possa avaliar (somente projetos que o usuário não é o orientador) + var projects = await _projectRepository.GetProjectsToEvaluateAsync(skip, take, actorId); + + // Mapeia a lista de projetos para uma lista de projetos resumidos e retorna. + return _mapper.Map>(projects); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/OpenProject.cs b/src/Application/UseCases/Project/OpenProject.cs new file mode 100644 index 00000000..9a2f73f1 --- /dev/null +++ b/src/Application/UseCases/Project/OpenProject.cs @@ -0,0 +1,162 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class OpenProject : IOpenProject + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IStudentRepository _studentRepository; + private readonly IProfessorRepository _professorRepository; + private readonly INoticeRepository _noticeRepository; + private readonly ISubAreaRepository _subAreaRepository; + private readonly IProgramTypeRepository _programTypeRepository; + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IProjectActivityRepository _projectActivityRepository; + private readonly IMapper _mapper; + public OpenProject(IProjectRepository projectRepository, + IStudentRepository studentRepository, + IProfessorRepository professorRepository, + INoticeRepository noticeRepository, + ISubAreaRepository subAreaRepository, + IProgramTypeRepository programTypeRepository, + IActivityTypeRepository activityTypeRepository, + IProjectActivityRepository projectActivityRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _studentRepository = studentRepository; + _professorRepository = professorRepository; + _noticeRepository = noticeRepository; + _subAreaRepository = subAreaRepository; + _programTypeRepository = programTypeRepository; + _activityTypeRepository = activityTypeRepository; + _projectActivityRepository = projectActivityRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(OpenProjectInput input) + { + // Mapeia input para entidade e realiza validação dos campos informados + Domain.Entities.Project project = new( + input.Title, + input.KeyWord1, + input.KeyWord2, + input.KeyWord3, + input.IsScholarshipCandidate, + input.Objective, + input.Methodology, + input.ExpectedResults, + input.ActivitiesExecutionSchedule, + input.StudentId, + input.ProgramTypeId, + input.ProfessorId, + input.SubAreaId, + input.NoticeId, + EProjectStatus.Opened, + EProjectStatus.Opened.GetDescription(), + null, + null, + null, + null, + null); + + // Verifica se Edital existe + var notice = await _noticeRepository.GetByIdAsync(project.NoticeId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Notice)); + + // Verifica se o período do edital é válido + if (notice.RegistrationStartDate > DateTime.UtcNow || notice.RegistrationEndDate < DateTime.UtcNow) + { + throw UseCaseException.BusinessRuleViolation("Fora do período de inscrição no edital."); + } + + // Verifica se a Subárea existe + _ = await _subAreaRepository.GetByIdAsync(project.SubAreaId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.SubArea)); + + // Verifica se o Tipo de Programa existe + _ = await _programTypeRepository.GetByIdAsync(project.ProgramTypeId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProgramType)); + + // Verifica se o Professor existe + var professor = await _professorRepository.GetByIdAsync(project.ProfessorId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Professor)); + + // Verifica se o professor está suspenso + if (professor.SuspensionEndDate.HasValue) + { + UseCaseException.BusinessRuleViolation(professor.SuspensionEndDate! > DateTime.UtcNow, + $"O acesso ao professor está suspenso até {professor.SuspensionEndDate!.Value:dd/MM/yyyy}, devido à pendência na entrega do relatório final em outro projeto."); + } + + // Caso tenha sido informado algum aluno no processo de abertura do projeto + if (project.StudentId.HasValue) + { + // Verifica se o aluno existe + var student = await _studentRepository.GetByIdAsync(project.StudentId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Student)); + + // Verifica se o aluno já está em um projeto + var studentProjects = await _projectRepository.GetStudentProjectsAsync(0, 1, student.Id); + if (studentProjects.Any()) + { + throw UseCaseException.BusinessRuleViolation("Aluno já está em um projeto."); + } + } + + // Verifica se foram informadas atividades + if (input.Activities?.Any() != true) + { + throw UseCaseException.BusinessRuleViolation("Atividades não informadas."); + } + + // Obtém atividades do Edital + var noticeActivities = await _activityTypeRepository.GetByNoticeIdAsync(notice.Id); + if (noticeActivities == null || !noticeActivities.Any()) + { + throw UseCaseException.BusinessRuleViolation("Edital não possui atividades cadastradas."); + } + + // Valida se todas as atividades do projeto foram informadas corretamente + List newProjectActivities = new(); + foreach (var activityType in noticeActivities) + { + // Verifica se as atividades que o professor informou existem no edital + // e se todas as atividades do edital foram informadas. + foreach (var activity in activityType.Activities!) + { + // Verifica se professor informou valor para essa atividade do edital + var inputActivity = input.Activities!.FirstOrDefault(x => x.ActivityId == activity.Id) + ?? throw UseCaseException.BusinessRuleViolation($"Não foi informado valor para a atividade {activity.Name}."); + + // Adiciona atividade do projeto na lista para ser criada posteriormente + newProjectActivities.Add(new Domain.Entities.ProjectActivity( + Guid.Empty, // Id do projeto será gerado na etapa seguinte + inputActivity.ActivityId, + inputActivity.InformedActivities, + 0)); + } + } + + // Cria o projeto + project = await _projectRepository.CreateAsync(project); + + // Cria as atividades do projeto + foreach (var projectActivity in newProjectActivities) + { + projectActivity.ProjectId = project.Id; + _ = await _projectActivityRepository.CreateAsync(projectActivity); + } + + // Mapeia o projeto para o retorno e retorna + return _mapper.Map(project); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/SubmitProject.cs b/src/Application/UseCases/Project/SubmitProject.cs new file mode 100644 index 00000000..8e96f992 --- /dev/null +++ b/src/Application/UseCases/Project/SubmitProject.cs @@ -0,0 +1,58 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class SubmitProject : ISubmitProject + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IMapper _mapper; + public SubmitProject(IProjectRepository projectRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId) + { + // Verifica se Id foi informado. + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + + // Verifica se o projeto existe + var project = await _projectRepository.GetByIdAsync(projectId!.Value) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se edital está em fase de inscrição + UseCaseException.BusinessRuleViolation(project.Notice?.RegistrationStartDate > DateTime.UtcNow || project.Notice?.RegistrationEndDate < DateTime.UtcNow, + "O edital não está na fase de inscrição."); + + // Verifica se aluno está preenchido + UseCaseException.BusinessRuleViolation(project.StudentId is null, + "O projeto não possui aluno vinculado."); + + // Verifica se o projeto está aberto + if (project.Status == EProjectStatus.Opened) + { + // Altera o status do projeto para submetido + project.Status = EProjectStatus.Submitted; + project.StatusDescription = EProjectStatus.Submitted.GetDescription(); + project.SubmissionDate = DateTime.UtcNow; + + // Salva alterações no banco de dados + _ = await _projectRepository.UpdateAsync(project); + + // Mapeia entidade para output e retorna + return _mapper.Map(project); + } + + throw UseCaseException.BusinessRuleViolation("Usuário não autorizado."); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Project/UpdateProject.cs b/src/Application/UseCases/Project/UpdateProject.cs new file mode 100644 index 00000000..e68570c9 --- /dev/null +++ b/src/Application/UseCases/Project/UpdateProject.cs @@ -0,0 +1,153 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Project; +using Application.Ports.Project; +using Application.Validation; + +namespace Application.UseCases.Project +{ + public class UpdateProject : IUpdateProject + { + #region Global Scope + private readonly IProjectRepository _projectRepository; + private readonly IStudentRepository _studentRepository; + private readonly IProfessorRepository _professorRepository; + private readonly INoticeRepository _noticeRepository; + private readonly ISubAreaRepository _subAreaRepository; + private readonly IProgramTypeRepository _programTypeRepository; + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IProjectActivityRepository _projectActivityRepository; + private readonly IMapper _mapper; + public UpdateProject(IProjectRepository projectRepository, + IStudentRepository studentRepository, + IProfessorRepository professorRepository, + INoticeRepository noticeRepository, + ISubAreaRepository subAreaRepository, + IProgramTypeRepository programTypeRepository, + IActivityTypeRepository activityTypeRepository, + IProjectActivityRepository projectActivityRepository, + IMapper mapper) + { + _projectRepository = projectRepository; + _studentRepository = studentRepository; + _professorRepository = professorRepository; + _noticeRepository = noticeRepository; + _subAreaRepository = subAreaRepository; + _programTypeRepository = programTypeRepository; + _activityTypeRepository = activityTypeRepository; + _projectActivityRepository = projectActivityRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateProjectInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se o projeto existe + var project = await _projectRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o edital está no período de inscrições + if (project.Notice!.RegistrationStartDate > DateTime.UtcNow || project.Notice?.RegistrationEndDate < DateTime.UtcNow) + { + throw UseCaseException.BusinessRuleViolation("Fora do período de inscrição no edital."); + } + + // Verifica se o projeto está aberto + if (project!.Status == EProjectStatus.Opened) + { + // Verifica se a nova Subárea existe + if (input.SubAreaId != project.SubAreaId) + { + _ = await _subAreaRepository.GetByIdAsync(input.SubAreaId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.SubArea)); + } + + // Verifica se o novo Tipo de Programa existe + if (input.ProgramTypeId != project.ProgramTypeId) + { + _ = await _programTypeRepository.GetByIdAsync(input.ProgramTypeId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProgramType)); + } + + // Caso tenha sido informado algum aluno no processo de abertura do projeto + if (input.StudentId.HasValue && input.StudentId != project.StudentId) + { + // Verifica se o aluno existe + var student = await _studentRepository.GetByIdAsync(input.StudentId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Student)); + + // Verifica se o aluno já está em um projeto + var studentProjects = await _projectRepository.GetStudentProjectsAsync(0, 1, student.Id); + UseCaseException.BusinessRuleViolation(studentProjects.Any(), "Student is already on a project."); + } + + // Atualiza campos permitidos + project.Title = input.Title; + project.KeyWord1 = input.KeyWord1; + project.KeyWord2 = input.KeyWord2; + project.KeyWord3 = input.KeyWord3; + project.IsScholarshipCandidate = input.IsScholarshipCandidate; + project.Objective = input.Objective; + project.Methodology = input.Methodology; + project.ExpectedResults = input.ExpectedResults; + project.ActivitiesExecutionSchedule = input.ActivitiesExecutionSchedule; + project.ProgramTypeId = input.ProgramTypeId; + project.StudentId = input.StudentId; + project.SubAreaId = input.SubAreaId; + + // Verifica se foram informadas atividades + if (input.Activities?.Any() != true) + { + throw UseCaseException.BusinessRuleViolation("Atividades não informadas."); + } + + // Obtém atividades do Edital + var noticeActivities = await _activityTypeRepository.GetByNoticeIdAsync(project.Notice!.Id); + + // Obtém atividades do projeto + var projectActivities = await _projectActivityRepository.GetByProjectIdAsync(project.Id); + + // Valida se todas as atividades do projeto foram informadas corretamente + List updateProjectActivities = new(); + foreach (var activityType in noticeActivities) + { + // Verifica se as atividades que o professor informou existem no edital + // e se todas as atividades do edital foram informadas. + foreach (var activity in activityType.Activities!) + { + // Verifica se professor informou valor para essa atividade do edital + var inputActivity = input.Activities!.FirstOrDefault(x => x.ActivityId == activity.Id) + ?? throw UseCaseException.BusinessRuleViolation($"Não foi informado valor para a atividade {activity.Name}."); + + // Obtém atividade do projeto + var updateProjectActivity = projectActivities.FirstOrDefault(x => x.ActivityId == activity.Id); + + // Atualiza valores da entidade + updateProjectActivity!.InformedActivities = inputActivity.InformedActivities; + + // Atualiza atividade do projeto no banco de dados + _ = await _projectActivityRepository.UpdateAsync(updateProjectActivity); + } + } + + // Atualiza o projeto + _ = await _projectRepository.UpdateAsync(project); + + // Atualiza atividades do projeto no banco + foreach (var projectActivity in updateProjectActivities) + { + _ = await _projectActivityRepository.UpdateAsync(projectActivity); + } + + // Mapeia o projeto para o retorno e retorna + return _mapper.Map(project); + } + + throw UseCaseException.BusinessRuleViolation("O projeto não está em um estágio que permita mudanças."); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectEvaluation/EvaluateAppealProject.cs b/src/Application/UseCases/ProjectEvaluation/EvaluateAppealProject.cs new file mode 100644 index 00000000..b310b8c6 --- /dev/null +++ b/src/Application/UseCases/ProjectEvaluation/EvaluateAppealProject.cs @@ -0,0 +1,112 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Validation; + +namespace Application.UseCases.ProjectEvaluation +{ + public class EvaluateAppealProject : IEvaluateAppealProject + { + #region Global Scope + private readonly IMapper _mapper; + private readonly IProjectRepository _projectRepository; + private readonly IEmailService _emailService; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IProjectEvaluationRepository _projectEvaluationRepository; + public EvaluateAppealProject(IMapper mapper, + IProjectRepository projectRepository, + IEmailService emailService, + ITokenAuthenticationService tokenAuthenticationService, + IProjectEvaluationRepository projectEvaluationRepository) + { + _mapper = mapper; + _projectRepository = projectRepository; + _emailService = emailService; + _tokenAuthenticationService = tokenAuthenticationService; + _projectEvaluationRepository = projectEvaluationRepository; + } + #endregion Global Scope + + public async Task ExecuteAsync(EvaluateAppealProjectInput input) + { + // Obtém informações do usuário logado. + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + + // Verifica se o usuário logado é um avaliador. + UseCaseException.BusinessRuleViolation(userClaim!.Role != ERole.ADMIN && userClaim.Role != ERole.PROFESSOR, + "O usuário não é um avaliador."); + + // Verifica se o status da avaliação foi informado. + UseCaseException.NotInformedParam(input.AppealEvaluationStatus is null, + nameof(input.AppealEvaluationStatus)); + + // Verifica se descrição da avaliação foi informada. + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.AppealEvaluationDescription), + nameof(input.AppealEvaluationDescription)); + + // Busca avaliação do projeto pelo Id. + var projectEvaluation = await _projectEvaluationRepository.GetByProjectIdAsync(input.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProjectEvaluation)); + + // Verifica se o avaliador é o professor orientador do projeto. + UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.ProfessorId == userClaim.Id, + "Avaliador é o orientador do projeto."); + + // Verifica se o projeto está na fase de recurso. + UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.Status != EProjectStatus.Evaluation, + "Projeto não está em fase de avaliação."); + + // Verifica se o edital está na fase de recurso. + UseCaseException.BusinessRuleViolation(projectEvaluation?.Project?.Notice?.AppealStartDate > DateTime.UtcNow || projectEvaluation?.Project?.Notice?.AppealEndDate < DateTime.UtcNow, + "O edital não está na fase de recurso."); + + // Recupera projeto pelo Id. + var project = await _projectRepository.GetByIdAsync(input.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Atualiza a avaliação do recurso. + projectEvaluation!.AppealEvaluatorId = userClaim.Id; + projectEvaluation.AppealEvaluationDate = DateTime.UtcNow; + + // Atualiza a descrição e o status da avaliação do recurso. + projectEvaluation.AppealEvaluationDescription = input.AppealEvaluationDescription; + projectEvaluation.AppealEvaluationStatus = (EProjectStatus)input.AppealEvaluationStatus!; + + // Atualiza avaliação do projeto. + _ = await _projectEvaluationRepository.UpdateAsync(projectEvaluation); + + // Se projeto foi aceito, adiciona prazo para envio da documentação. + if ((EProjectStatus)input.AppealEvaluationStatus == EProjectStatus.Accepted) + { + project.Status = EProjectStatus.Accepted; + project.StatusDescription = EProjectStatus.Accepted.GetDescription(); + } + else + { + project.Status = EProjectStatus.Canceled; + project.StatusDescription = EProjectStatus.Canceled.GetDescription(); + } + + // Informa ao professor o resultado da avaliação. + await _emailService.SendProjectNotificationEmailAsync( + project.Professor!.User!.Email, + project.Professor!.User!.Name, + project.Title, + project.StatusDescription, + projectEvaluation.AppealEvaluationDescription); + + // Atualiza projeto. + var output = await _projectRepository.UpdateAsync(project); + + // Mapeia dados de saída e retorna. + return _mapper.Map(output); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectEvaluation/EvaluateStudentDocuments.cs b/src/Application/UseCases/ProjectEvaluation/EvaluateStudentDocuments.cs new file mode 100644 index 00000000..41f6addb --- /dev/null +++ b/src/Application/UseCases/ProjectEvaluation/EvaluateStudentDocuments.cs @@ -0,0 +1,113 @@ +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Validation; +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.ProjectEvaluation +{ + public class EvaluateStudentDocuments : IEvaluateStudentDocuments + { + #region Global Scope + private readonly IMapper _mapper; + private readonly IProjectRepository _projectRepository; + private readonly IEmailService _emailService; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IProjectEvaluationRepository _projectEvaluationRepository; + public EvaluateStudentDocuments(IMapper mapper, + IProjectRepository projectRepository, + IEmailService emailService, + ITokenAuthenticationService tokenAuthenticationService, + IProjectEvaluationRepository projectEvaluationRepository) + { + _mapper = mapper; + _projectRepository = projectRepository; + _emailService = emailService; + _tokenAuthenticationService = tokenAuthenticationService; + _projectEvaluationRepository = projectEvaluationRepository; + } + #endregion Global Scope + + public async Task ExecuteAsync(EvaluateStudentDocumentsInput input) + { + // Obtém informações do usuário logado. + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Verifica se o usuário logado é um avaliador. + UseCaseException.BusinessRuleViolation(userClaim!.Role != ERole.ADMIN && userClaim.Role != ERole.PROFESSOR, + "O usuário não é um avaliador."); + + // Verifica se o status da avaliação foi informado. + UseCaseException.NotInformedParam(input.IsDocumentsApproved is null, + nameof(input.IsDocumentsApproved)); + + // Verifica se descrição da avaliação foi informada. + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.DocumentsEvaluationDescription), + nameof(input.DocumentsEvaluationDescription)); + + // Busca avaliação do projeto pelo Id. + var projectEvaluation = await _projectEvaluationRepository.GetByProjectIdAsync(input.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProjectEvaluation)); + + // Verifica se o avaliador é o professor orientador do projeto. + UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.ProfessorId == userClaim.Id, + "Avaliador é o orientador do projeto."); + + // Verifica se o projeto está na fase de avaliação da documentação. + UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.Status != EProjectStatus.DocumentAnalysis, + "Projeto não está em fase de avaliação da documentação."); + + // Verifica se o edital está na fase de avaliação da documentação. + UseCaseException.BusinessRuleViolation(projectEvaluation?.Project?.Notice?.SendingDocsStartDate > DateTime.UtcNow + || projectEvaluation?.Project?.Notice?.SendingDocsEndDate < DateTime.UtcNow, + "O edital não está na fase de avaliação da documentação."); + + // Recupera projeto pelo Id. + var project = await _projectRepository.GetByIdAsync(input.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Atualiza a avaliação do recurso. + projectEvaluation!.DocumentsEvaluatorId = userClaim.Id; + projectEvaluation.DocumentsEvaluationDate = DateTime.UtcNow; + + // Atualiza a descrição e o status da avaliação da documentação. + projectEvaluation.DocumentsEvaluationDescription = input.DocumentsEvaluationDescription; + + // Atualiza avaliação do projeto. + _ = await _projectEvaluationRepository.UpdateAsync(projectEvaluation); + + // Atualiza status do projeto de acordo com a avaliação da documentação. + if ((bool)input.IsDocumentsApproved!) + { + project.Status = EProjectStatus.Started; + project.StatusDescription = EProjectStatus.Started.GetDescription(); + } + else + { + project.Status = EProjectStatus.Pending; + project.StatusDescription = EProjectStatus.Pending.GetDescription(); + } + + // Informa ao professor o resultado da avaliação. + await _emailService.SendProjectNotificationEmailAsync( + project.Professor!.User!.Email, + project.Professor!.User!.Name, + project.Title, + project.StatusDescription, + projectEvaluation.DocumentsEvaluationDescription); + + // Atualiza projeto. + var output = await _projectRepository.UpdateAsync(project); + + // Mapeia dados de saída e retorna. + return _mapper.Map(output); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs b/src/Application/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs new file mode 100644 index 00000000..91dfb207 --- /dev/null +++ b/src/Application/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs @@ -0,0 +1,159 @@ +using AutoMapper; +using Domain.Entities.Enums; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.Project; +using Application.Ports.ProjectEvaluation; +using Application.Validation; + +namespace Application.UseCases.ProjectEvaluation +{ + public class EvaluateSubmissionProject : IEvaluateSubmissionProject + { + #region Global Scope + private readonly IMapper _mapper; + private readonly IProjectRepository _projectRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IProjectActivityRepository _projectActivityRepository; + private readonly IActivityTypeRepository _activityTypeRepository; + private readonly IEmailService _emailService; + private readonly IProjectEvaluationRepository _projectEvaluationRepository; + public EvaluateSubmissionProject(IMapper mapper, + IProjectRepository projectRepository, + ITokenAuthenticationService tokenAuthenticationService, + IProjectActivityRepository projectActivityRepository, + IActivityTypeRepository activityTypeRepository, + IEmailService emailService, + IProjectEvaluationRepository projectEvaluationRepository) + { + _mapper = mapper; + _projectRepository = projectRepository; + _tokenAuthenticationService = tokenAuthenticationService; + _projectActivityRepository = projectActivityRepository; + _activityTypeRepository = activityTypeRepository; + _emailService = emailService; + _projectEvaluationRepository = projectEvaluationRepository; + } + #endregion Global Scope + + public async Task ExecuteAsync(EvaluateSubmissionProjectInput input) + { + // Obtém informações do usuário logado. + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Verifica se o usuário logado é um avaliador. + UseCaseException.BusinessRuleViolation(userClaim!.Role != ERole.ADMIN && userClaim.Role != ERole.PROFESSOR, + "O usuário não é um avaliador."); + + // Verifica se já existe alguma avaliação para o projeto. + var projectEvaluation = await _projectEvaluationRepository.GetByProjectIdAsync(input.ProjectId); + UseCaseException.BusinessRuleViolation(projectEvaluation != null, + "Projeto já avaliado."); + + // Busca projeto pelo Id. + var project = await _projectRepository.GetByIdAsync(input.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o avaliador é o professor orientador do projeto. + UseCaseException.BusinessRuleViolation(project.ProfessorId == actorId, + "Avaliador é o orientador do projeto."); + + // Verifica se o projeto está na fase de submissão. + UseCaseException.BusinessRuleViolation(project.Status != EProjectStatus.Submitted, + "O projeto não está em fase de submissão."); + + // Verifica se o edital está em fase de avaliação. + UseCaseException.BusinessRuleViolation(project.Notice?.EvaluationStartDate > DateTime.UtcNow || project.Notice?.EvaluationEndDate < DateTime.UtcNow, + "Fora do intervalo de avaliação do edital."); + + // Verifica se o status da avaliação foi informado. + UseCaseException.NotInformedParam(input.SubmissionEvaluationStatus is null, + nameof(input.SubmissionEvaluationStatus)); + + // Mapeia dados de entrada para entidade. + projectEvaluation = new Domain.Entities.ProjectEvaluation(input.ProjectId, + input.IsProductivityFellow, + userClaim.Id, // Id do avaliador logado. + EnumExtensions.TryCastEnum(input.SubmissionEvaluationStatus), + DateTime.UtcNow, + input.SubmissionEvaluationDescription, + EnumExtensions.TryCastEnum(input.Qualification), + EnumExtensions.TryCastEnum(input.ProjectProposalObjectives), + EnumExtensions.TryCastEnum(input.AcademicScientificProductionCoherence), + EnumExtensions.TryCastEnum(input.ProposalMethodologyAdaptation), + EnumExtensions.TryCastEnum(input.EffectiveContributionToResearch), + 0); + + // Obtém atividades do Edital + var noticeActivities = await _activityTypeRepository.GetByNoticeIdAsync(project.Notice!.Id); + if (noticeActivities is null || noticeActivities.Count == 0) + throw UseCaseException.BusinessRuleViolation("Não foram encontradas atividades para o edital."); + + // Obtém atividades do projeto + var projectActivities = await _projectActivityRepository.GetByProjectIdAsync(project.Id); + + // Valida se todas as atividades do projeto foram informadas corretamente + List updateProjectActivities = new(); + foreach (var activityType in noticeActivities) + { + // Verifica se as atividades que o professor informou existem no edital + // e se todas as atividades do edital foram informadas. + foreach (var activity in activityType.Activities!) + { + // Verifica se professor informou valor para essa atividade do edital + var inputActivity = input.Activities!.FirstOrDefault(x => x.ActivityId == activity.Id) + ?? throw UseCaseException.BusinessRuleViolation($"Não foi informado valor para a atividade {activity.Name}."); + + // Obtém atividade do projeto + var updateProjectActivity = projectActivities.FirstOrDefault(x => x.ActivityId == activity.Id); + + // Atualiza valores da entidade + updateProjectActivity!.FoundActivities = inputActivity.FoundActivities; + + // Calcula pontuação da atividade + projectEvaluation.APIndex += updateProjectActivity.CalculatePoints(); + + // Atualiza atividade do projeto no banco de dados + _ = await _projectActivityRepository.UpdateAsync(updateProjectActivity); + } + } + + // Calcula pontuação da avaliação + projectEvaluation.CalculateFinalScore(); + + // Adiciona avaliação do projeto. + _ = await _projectEvaluationRepository.CreateAsync(projectEvaluation); + + // Se projeto foi aceito, adiciona prazo para envio da documentação. + if (projectEvaluation.SubmissionEvaluationStatus == EProjectStatus.Accepted) + { + project.Status = EProjectStatus.Accepted; + project.StatusDescription = EProjectStatus.Accepted.GetDescription(); + } + else + { + project.Status = EProjectStatus.Rejected; + project.StatusDescription = EProjectStatus.Rejected.GetDescription(); + } + + // Informa ao professor o resultado da avaliação. + await _emailService.SendProjectNotificationEmailAsync( + project.Professor!.User!.Email, + project.Professor!.User!.Name, + project.Title, + project.StatusDescription, + projectEvaluation.SubmissionEvaluationDescription); + + // Atualiza projeto. + var output = await _projectRepository.UpdateAsync(project); + + // Mapeia dados de saída e retorna. + return _mapper.Map(output); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs b/src/Application/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs new file mode 100644 index 00000000..406096d7 --- /dev/null +++ b/src/Application/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs @@ -0,0 +1,29 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Ports.ProjectEvaluation; +using Application.Validation; + +namespace Application.UseCases.ProjectEvaluation +{ + public class GetEvaluationByProjectId : IGetEvaluationByProjectId + { + #region Global Scope + private readonly IMapper _mapper; + private readonly IProjectEvaluationRepository _repository; + public GetEvaluationByProjectId(IMapper mapper, + IProjectEvaluationRepository repository) + { + _mapper = mapper; + _repository = repository; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId) + { + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + var entity = await _repository.GetByProjectIdAsync(projectId); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectFinalReport/CreateProjectFinalReport.cs b/src/Application/UseCases/ProjectFinalReport/CreateProjectFinalReport.cs new file mode 100644 index 00000000..5ad7c91d --- /dev/null +++ b/src/Application/UseCases/ProjectFinalReport/CreateProjectFinalReport.cs @@ -0,0 +1,89 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Ports.ProjectFinalReport; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Validation; + +namespace Application.UseCases.ProjectFinalReport +{ + public class CreateProjectFinalReport : ICreateProjectFinalReport + { + #region Global Scope + private readonly IProjectFinalReportRepository _projectReportRepository; + private readonly IProjectRepository _projectRepository; + private readonly IStorageFileService _storageFileService; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public CreateProjectFinalReport(IProjectFinalReportRepository projectReportRepository, + IProjectRepository projectRepository, + IStorageFileService storageFileService, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectReportRepository = projectReportRepository; + _projectRepository = projectRepository; + _storageFileService = storageFileService; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateProjectFinalReportInput input) + { + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Cria entidade a partir do modelo + Domain.Entities.ProjectFinalReport report = new( + projectId: input.ProjectId, + // Salva o Id do usuário logado no relatório + userId: userClaim!.Id + ); + + // Verifica se o projeto existe + Domain.Entities.Project project = await _projectRepository.GetByIdAsync(report.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto foi excluído + UseCaseException.BusinessRuleViolation(project.DeletedAt != null, + "O projeto informado já foi removido."); + + // Verifica se o projeto está em andamento + UseCaseException.BusinessRuleViolation(project.Status != Domain.Entities.Enums.EProjectStatus.Started, + "O projeto informado não está em andamento."); + + // Somente aluno ou professor do projeto pode fazer inclusão de relatório + UseCaseException.BusinessRuleViolation(actorId != project.StudentId && actorId != project.ProfessorId, + "Somente o aluno ou o professor orientador do projeto pode fazer inclusão de relatório."); + + // Verifica se o relatório está sendo enviado dentro do prazo + // Relatórios podem ser entregues até 6 meses antes do prazo final + var deadline = project.Notice?.FinalReportDeadline ?? throw UseCaseException.BusinessRuleViolation("O prazo para envio de relatório parcial não foi definido."); + var isBeforeDeadline = deadline < DateTime.UtcNow || deadline.AddMonths(-6) > DateTime.UtcNow; + + // Lança exceção caso o relatório esteja sendo enviado fora do prazo + UseCaseException.BusinessRuleViolation(isBeforeDeadline, + "Relatório enviado fora do prazo estipulado no edital."); + + // Lança exceção caso o arquivo não tenha sido enviado + UseCaseException.BusinessRuleViolation(input.ReportFile is null, "O arquivo do relatório é obrigatório."); + + // Tenta salvar o relatório no repositório de arquivos na núvem + string? fileUrl = await _storageFileService.UploadFileAsync(input.ReportFile!); + + // Salva o link do arquivo no relatório + report.ReportUrl = fileUrl; + + // Cria entidade + report = await _projectReportRepository.CreateAsync(report); + + // Salva entidade no banco + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectFinalReport/DeleteProjectFinalReport.cs b/src/Application/UseCases/ProjectFinalReport/DeleteProjectFinalReport.cs new file mode 100644 index 00000000..97533dc6 --- /dev/null +++ b/src/Application/UseCases/ProjectFinalReport/DeleteProjectFinalReport.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Validation; + +namespace Application.UseCases.ProjectFinalReport +{ + public class DeleteProjectFinalReport : IDeleteProjectFinalReport + { + #region Global Scope + private readonly IProjectFinalReportRepository _repository; + private readonly IMapper _mapper; + public DeleteProjectFinalReport(IProjectFinalReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + Domain.Entities.ProjectFinalReport model = await _repository.DeleteAsync(id); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportById.cs b/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportById.cs new file mode 100644 index 00000000..6955298a --- /dev/null +++ b/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Validation; + +namespace Application.UseCases.ProjectFinalReport +{ + public class GetProjectFinalReportById : IGetProjectFinalReportById + { + #region Global Scope + private readonly IProjectFinalReportRepository _repository; + private readonly IMapper _mapper; + public GetProjectFinalReportById(IProjectFinalReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + Domain.Entities.ProjectFinalReport? entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectId.cs b/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectId.cs new file mode 100644 index 00000000..62564f07 --- /dev/null +++ b/src/Application/UseCases/ProjectFinalReport/GetProjectFinalReportByProjectId.cs @@ -0,0 +1,28 @@ +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; + +namespace Application.UseCases.ProjectFinalReport +{ + public class GetProjectFinalReportByProjectId : IGetProjectFinalReportByProjectId + { + #region Global Scope + private readonly IProjectFinalReportRepository _repository; + private readonly IMapper _mapper; + public GetProjectFinalReportByProjectId(IProjectFinalReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId) + { + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + var report = await _repository.GetByProjectIdAsync(projectId); + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectFinalReport/UpdateProjectFinalReport.cs b/src/Application/UseCases/ProjectFinalReport/UpdateProjectFinalReport.cs new file mode 100644 index 00000000..d76b6de7 --- /dev/null +++ b/src/Application/UseCases/ProjectFinalReport/UpdateProjectFinalReport.cs @@ -0,0 +1,94 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.Ports.ProjectFinalReport; +using Application.Validation; + +namespace Application.UseCases.ProjectFinalReport +{ + public class UpdateProjectFinalReport : IUpdateProjectFinalReport + { + #region Global Scope + private readonly IProjectFinalReportRepository _projectReportRepository; + private readonly IProjectRepository _projectRepository; + private readonly IStorageFileService _storageFileService; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public UpdateProjectFinalReport(IProjectFinalReportRepository projectReportRepository, + IProjectRepository projectRepository, + IStorageFileService storageFileService, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectReportRepository = projectReportRepository; + _projectRepository = projectRepository; + _storageFileService = storageFileService; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateProjectFinalReportInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se o arquivo foi informado + UseCaseException.NotInformedParam(input.ReportFile is null, nameof(input.ReportFile)); + + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Recupera entidade que será atualizada + Domain.Entities.ProjectFinalReport report = await _projectReportRepository.GetByIdAsync(id) ?? + throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProjectFinalReport)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(report.DeletedAt != null, + "O relatório informado já foi removido."); + + // Verifica se o projeto existe + Domain.Entities.Project project = await _projectRepository.GetByIdAsync(report.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto foi excluído + UseCaseException.BusinessRuleViolation(project.DeletedAt != null, + "O projeto informado já foi removido."); + + // Verifica se o projeto está em andamento + UseCaseException.BusinessRuleViolation(project.Status != Domain.Entities.Enums.EProjectStatus.Started, + "O projeto informado não está em andamento."); + + // Somente aluno ou professor do projeto pode fazer alteração no relatório + UseCaseException.BusinessRuleViolation(actorId != project.StudentId && actorId != project.ProfessorId, + "Somente o aluno ou o professor orientador do projeto pode fazer alterações no relatório."); + + // Verifica se o relatório está sendo enviado dentro do prazo + // Relatórios podem ser entregues até 6 meses antes do prazo final + var deadline = project.Notice?.FinalReportDeadline ?? throw UseCaseException.BusinessRuleViolation("O prazo para envio de relatório parcial não foi definido."); + var isBeforeDeadline = deadline < DateTime.UtcNow || deadline.AddMonths(-6) > DateTime.UtcNow; + + // Lança exceção caso o relatório esteja sendo enviado fora do prazo + UseCaseException.BusinessRuleViolation(isBeforeDeadline, + "Relatório enviado fora do prazo estipulado no edital."); + + // Atualiza o relatório na núvem + await _storageFileService.UploadFileAsync(input.ReportFile!, report.ReportUrl); + + // Atualiza atributos permitidos + report.UserId = userClaim!.Id; + report.SendDate = DateTime.UtcNow; + + // Salva entidade atualizada no banco + await _projectReportRepository.UpdateAsync(report); + + // Mapeia entidade para o modelo de saída e retorna + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectPartialReport/CreateProjectPartialReport.cs b/src/Application/UseCases/ProjectPartialReport/CreateProjectPartialReport.cs new file mode 100644 index 00000000..703f92fb --- /dev/null +++ b/src/Application/UseCases/ProjectPartialReport/CreateProjectPartialReport.cs @@ -0,0 +1,80 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Ports.ProjectPartialReport; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Services; + +namespace Application.UseCases.ProjectPartialReport +{ + public class CreateProjectPartialReport : ICreateProjectPartialReport + { + #region Global Scope + private readonly IProjectPartialReportRepository _projectReportRepository; + private readonly IProjectRepository _projectRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public CreateProjectPartialReport(IProjectPartialReportRepository projectReportRepository, + IProjectRepository projectRepository, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectReportRepository = projectReportRepository; + _projectRepository = projectRepository; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateProjectPartialReportInput input) + { + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Cria entidade a partir do modelo + Domain.Entities.ProjectPartialReport report = new( + input.ProjectId, + input.CurrentDevelopmentStage, + EnumExtensions.TryCastEnum(input.ScholarPerformance), + input.AdditionalInfo, + userClaim!.Id + ); + + // Verifica se o projeto existe + Domain.Entities.Project project = await _projectRepository.GetByIdAsync(report.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto foi excluído + UseCaseException.BusinessRuleViolation(project.DeletedAt != null, + "O projeto informado já foi removido."); + + // Verifica se o projeto está em andamento + UseCaseException.BusinessRuleViolation(project.Status != EProjectStatus.Started, + "O projeto informado não está em andamento."); + + // Somente aluno ou professor do projeto pode fazer inclusão de relatório + UseCaseException.BusinessRuleViolation(actorId != project.StudentId && actorId != project.ProfessorId, + "Somente o aluno ou o professor orientador do projeto pode fazer inclusão de relatório."); + + // Verifica se o relatório está sendo enviado dentro do prazo + // Relatórios podem ser entregues até 6 meses antes do prazo final + var deadline = project.Notice?.PartialReportDeadline ?? throw UseCaseException.BusinessRuleViolation("O prazo para envio de relatório parcial não foi definido."); + var isBeforeDeadline = deadline < DateTime.UtcNow || deadline.AddMonths(-6) > DateTime.UtcNow; + + // Lança exceção caso o relatório esteja sendo enviado fora do prazo + UseCaseException.BusinessRuleViolation(isBeforeDeadline, + "Relatório enviado fora do prazo estipulado no edital."); + + // Cria entidade + report = await _projectReportRepository.CreateAsync(report); + + // Salva entidade no banco + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectPartialReport/DeleteProjectPartialReport.cs b/src/Application/UseCases/ProjectPartialReport/DeleteProjectPartialReport.cs new file mode 100644 index 00000000..4899cba0 --- /dev/null +++ b/src/Application/UseCases/ProjectPartialReport/DeleteProjectPartialReport.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Validation; + +namespace Application.UseCases.ProjectPartialReport +{ + public class DeleteProjectPartialReport : IDeleteProjectPartialReport + { + #region Global Scope + private readonly IProjectPartialReportRepository _repository; + private readonly IMapper _mapper; + public DeleteProjectPartialReport(IProjectPartialReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + Domain.Entities.ProjectPartialReport model = await _repository.DeleteAsync(id); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportById.cs b/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportById.cs new file mode 100644 index 00000000..f6c38aa8 --- /dev/null +++ b/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Validation; + +namespace Application.UseCases.ProjectPartialReport +{ + public class GetProjectPartialReportById : IGetProjectPartialReportById + { + #region Global Scope + private readonly IProjectPartialReportRepository _repository; + private readonly IMapper _mapper; + public GetProjectPartialReportById(IProjectPartialReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + Domain.Entities.ProjectPartialReport? entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectId.cs b/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectId.cs new file mode 100644 index 00000000..b1c9e981 --- /dev/null +++ b/src/Application/UseCases/ProjectPartialReport/GetProjectPartialReportByProjectId.cs @@ -0,0 +1,28 @@ +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Validation; +using AutoMapper; +using Domain.Interfaces.Repositories; + +namespace Application.UseCases.ProjectPartialReport +{ + public class GetProjectPartialReportByProjectId : IGetProjectPartialReportByProjectId + { + #region Global Scope + private readonly IProjectPartialReportRepository _repository; + private readonly IMapper _mapper; + public GetProjectPartialReportByProjectId(IProjectPartialReportRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId) + { + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + var report = await _repository.GetByProjectIdAsync(projectId); + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/ProjectPartialReport/UpdateProjectPartialReport.cs b/src/Application/UseCases/ProjectPartialReport/UpdateProjectPartialReport.cs new file mode 100644 index 00000000..c8e38629 --- /dev/null +++ b/src/Application/UseCases/ProjectPartialReport/UpdateProjectPartialReport.cs @@ -0,0 +1,93 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.Ports.ProjectPartialReport; +using Application.Validation; +using Domain.Entities.Enums; +using Domain.Interfaces.Services; + +namespace Application.UseCases.ProjectPartialReport +{ + public class UpdateProjectPartialReport : IUpdateProjectPartialReport + { + #region Global Scope + private readonly IProjectPartialReportRepository _projectReportRepository; + private readonly IProjectRepository _projectRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public UpdateProjectPartialReport(IProjectPartialReportRepository projectReportRepository, + IProjectRepository projectRepository, + ITokenAuthenticationService tokenAuthenticationService, + IMapper mapper) + { + _projectReportRepository = projectReportRepository; + _projectRepository = projectRepository; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateProjectPartialReportInput input) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Recupera entidade que será atualizada + Domain.Entities.ProjectPartialReport report = await _projectReportRepository.GetByIdAsync(id) ?? + throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.ProjectPartialReport)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(report.DeletedAt != null, + "O relatório informado já foi removido."); + + // Verifica se o projeto existe + Domain.Entities.Project project = await _projectRepository.GetByIdAsync(report.ProjectId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Project)); + + // Verifica se o projeto foi excluído + UseCaseException.BusinessRuleViolation(project.DeletedAt != null, + "O projeto informado já foi removido."); + + // Verifica se o projeto está em andamento + UseCaseException.BusinessRuleViolation(project.Status != EProjectStatus.Started, + "O projeto informado não está em andamento."); + + // Somente aluno ou professor do projeto pode fazer alteração no relatório + UseCaseException.BusinessRuleViolation(actorId != project.StudentId && actorId != project.ProfessorId, + "Somente o aluno ou o professor orientador do projeto pode fazer alterações no relatório."); + + // Verifica se o relatório está sendo enviado dentro do prazo + // Relatórios podem ser entregues até 6 meses antes do prazo final + var deadline = project.Notice?.PartialReportDeadline ?? throw UseCaseException.BusinessRuleViolation("O prazo para envio de relatório parcial não foi definido."); + var isBeforeDeadline = deadline < DateTime.UtcNow || deadline.AddMonths(-6) > DateTime.UtcNow; + + // Lança exceção caso o relatório esteja sendo enviado fora do prazo + UseCaseException.BusinessRuleViolation(isBeforeDeadline, + "Relatório enviado fora do prazo estipulado no edital."); + + // Atualiza propriedades da entidade + if (input.CurrentDevelopmentStage is not null) + report.CurrentDevelopmentStage = (int)input.CurrentDevelopmentStage; + if (input.ScholarPerformance is not null) + report.ScholarPerformance = EnumExtensions.TryCastEnum(input.ScholarPerformance); + if (input.AdditionalInfo is not null) + report.AdditionalInfo = input.AdditionalInfo; + + // Atualiza propriedades de auditoria + report.UserId = userClaim!.Id; + + // Salva entidade atualizada no banco + await _projectReportRepository.UpdateAsync(report); + + // Mapeia entidade para o modelo de saída e retorna + return _mapper.Map(report); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/CreateStudent.cs b/src/Application/UseCases/Student/CreateStudent.cs new file mode 100644 index 00000000..3e5c0ad9 --- /dev/null +++ b/src/Application/UseCases/Student/CreateStudent.cs @@ -0,0 +1,109 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; +using Domain.Entities.Enums; + +namespace Application.UseCases.Student +{ + public class CreateStudent : ICreateStudent + { + #region Global Scope + private readonly IStudentRepository _studentRepository; + private readonly IUserRepository _userRepository; + private readonly ICourseRepository _courseRepository; + private readonly ICampusRepository _campusRepository; + private readonly IEmailService _emailService; + private readonly IHashService _hashService; + private readonly IMapper _mapper; + public CreateStudent(IStudentRepository studentRepository, + IUserRepository userRepository, + ICampusRepository campusRepository, + ICourseRepository courseRepository, + IEmailService emailService, + IHashService hashService, + IMapper mapper) + { + _studentRepository = studentRepository; + _userRepository = userRepository; + _campusRepository = campusRepository; + _courseRepository = courseRepository; + _emailService = emailService; + _hashService = hashService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(CreateStudentInput input) + { + // Realiza o map da entidade e a validação dos campos informados + Domain.Entities.Student? student = new(input.BirthDate, + input.RG, + input.IssuingAgency, + input.DispatchDate, + (EGender)input.Gender, + (ERace)input.Race, + input.HomeAddress, + input.City, + input.UF, + input.CEP, + input.PhoneDDD, + input.Phone, + input.CellPhoneDDD, + input.CellPhone, + input.CampusId, + input.CourseId, + input.StartYear, + input.AssistanceTypeId, + input.RegistrationCode); + + // Verifica se já existe um usuário com o e-mail informado + Domain.Entities.User? user = await _userRepository.GetUserByEmailAsync(input.Email); + UseCaseException.BusinessRuleViolation(user != null, + "Já existe um usuário com o e-mail informado."); + + // Verifica se já existe um usuário com o CPF informado + user = await _userRepository.GetUserByCPFAsync(input.CPF); + UseCaseException.BusinessRuleViolation(user != null, + "Já existe um usuário com o CPF informado."); + + // Verifica se curso informado existe + Domain.Entities.Course? course = await _courseRepository.GetByIdAsync(input.CourseId); + UseCaseException.BusinessRuleViolation(course == null || course.DeletedAt != null, + "Curso informado não existe."); + + // Verifica se campus informado existe + Domain.Entities.Campus? campus = await _campusRepository.GetByIdAsync(input.CampusId); + UseCaseException.BusinessRuleViolation(campus == null || campus.DeletedAt != null, + "Campus informado não existe."); + + // Verifica se a senha é nula + UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Password), nameof(input.Password)); + + // Gera hash da senha + input.Password = _hashService.HashPassword(input.Password!); + + // Cria usuário + user = new Domain.Entities.User(input.Name, input.Email, input.Password, input.CPF, ERole.STUDENT); + + // Adiciona usuário no banco + user = await _userRepository.CreateAsync(user); + UseCaseException.BusinessRuleViolation(user == null, + "Não foi possível criar o usuário."); + + // Adiciona estudante no banco + student.UserId = user?.Id; + student = await _studentRepository.CreateAsync(student); + UseCaseException.BusinessRuleViolation(student == null, + "Não foi possível criar o estudante."); + + // Envia e-mail de confirmação + await _emailService.SendConfirmationEmailAsync(user?.Email, user?.Name, user?.ValidationCode); + + // Salva entidade no banco + return _mapper.Map(student); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/DeleteStudent.cs b/src/Application/UseCases/Student/DeleteStudent.cs new file mode 100644 index 00000000..8246bf0d --- /dev/null +++ b/src/Application/UseCases/Student/DeleteStudent.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; + +namespace Application.UseCases.Student +{ + public class DeleteStudent : IDeleteStudent + { + #region Global Scope + private readonly IStudentRepository _studentRepository; + private readonly IUserRepository _userRepository; + private readonly IMapper _mapper; + public DeleteStudent(IStudentRepository studentRepository, IUserRepository userRepository, IMapper mapper) + { + _studentRepository = studentRepository; + _userRepository = userRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se o estudante existe + Domain.Entities.Student? student = await _studentRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Student)); + + // Verifica se o usuário existe + _ = await _userRepository.GetByIdAsync(student.UserId) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + + // Remove o estudante + student = await _studentRepository.DeleteAsync(id); + UseCaseException.BusinessRuleViolation(student == null, "O estudante não pôde ser removido."); + + // Remove o usuário + _ = await _userRepository.DeleteAsync(student?.UserId) + ?? throw UseCaseException.BusinessRuleViolation("O usuário não pôde ser removido."); + + // Retorna o estudante removido + return _mapper.Map(student); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/GetStudentById.cs b/src/Application/UseCases/Student/GetStudentById.cs new file mode 100644 index 00000000..5630af5a --- /dev/null +++ b/src/Application/UseCases/Student/GetStudentById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; + +namespace Application.UseCases.Student +{ + public class GetStudentById : IGetStudentById + { + #region Global Scope + private readonly IStudentRepository _repository; + private readonly IMapper _mapper; + public GetStudentById(IStudentRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + Domain.Entities.Student? entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/GetStudentByRegistrationCode.cs b/src/Application/UseCases/Student/GetStudentByRegistrationCode.cs new file mode 100644 index 00000000..431fb6fd --- /dev/null +++ b/src/Application/UseCases/Student/GetStudentByRegistrationCode.cs @@ -0,0 +1,34 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; + +namespace Application.UseCases.Student +{ + public class GetStudentByRegistrationCode : IGetStudentByRegistrationCode + { + private readonly IStudentRepository _studentRepository; + private readonly IMapper _mapper; + public GetStudentByRegistrationCode(IStudentRepository studentRepository, IMapper mapper) + { + _studentRepository = studentRepository; + _mapper = mapper; + } + + public async Task ExecuteAsync(string? registrationCode) + { + // Verifica de a matrícula do aluno foi informada + UseCaseException.NotInformedParam(string.IsNullOrEmpty(registrationCode), "Matrícula"); + + // Busca o aluno pelo código de matrícula + var student = await _studentRepository.GetByRegistrationCodeAsync(registrationCode!); + + // Verifica se o aluno foi encontrado + UseCaseException.NotFoundEntityByParams(student is null, nameof(Domain.Entities.Student)); + + // Mapeia o aluno para a saída detalhada + return _mapper.Map(student); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/GetStudents.cs b/src/Application/UseCases/Student/GetStudents.cs new file mode 100644 index 00000000..f14beb58 --- /dev/null +++ b/src/Application/UseCases/Student/GetStudents.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; + +namespace Application.UseCases.Student +{ + public class GetStudents : IGetStudents + { + #region Global Scope + private readonly IStudentRepository _repository; + private readonly IMapper _mapper; + public GetStudents(IStudentRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + IEnumerable entities = await _repository.GetAllAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/RequestStudentRegister.cs b/src/Application/UseCases/Student/RequestStudentRegister.cs new file mode 100644 index 00000000..add688d0 --- /dev/null +++ b/src/Application/UseCases/Student/RequestStudentRegister.cs @@ -0,0 +1,60 @@ +using System.Text.RegularExpressions; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.Student; +using Application.Validation; + +namespace Application.UseCases.Student +{ + public class RequestStudentRegister : IRequestStudentRegister + { + private readonly IEmailService _emailService; + private readonly IUserRepository _userRepository; + public RequestStudentRegister(IEmailService emailService, IUserRepository userRepository) + { + _emailService = emailService; + _userRepository = userRepository; + } + + public async Task ExecuteAsync(string? email) + { + // Verifica se o email foi informado + UseCaseException.NotInformedParam(string.IsNullOrEmpty(email), "Email"); + + // Verifica se o email é válido + UseCaseException.BusinessRuleViolation(!ValidateStudentEmail(email!), "Email inválido."); + + // Verifica se o email já está cadastrado + var user = await _userRepository.GetUserByEmailAsync(email!); + + // Se o usuário já existe, lança uma exceção + UseCaseException.BusinessRuleViolation(user is not null, "Email já cadastrado."); + + // Solicita o registro do usuário + await _emailService.SendRequestStudentRegisterEmailAsync(email!); + + // Retorna resultado + return "Solicitação de registro enviada com sucesso."; + } + + /// + /// Verifica se o e-mail informado é válido para o estudante. + /// Padrões aceitos: + /// - nome.sobrenome@aluno.cefet-rj.br + /// - 12345678912@cefet-rj.br + /// + /// E-mail a ser validado + /// True se o e-mail é válido, caso contrário, False + public bool ValidateStudentEmail(string email) + { + // Padrão de e-mail para estudantes + string studentEmailPattern = @"^(?[a-zA-Z0-9._-]+)@(?aluno\.cefet-rj\.br|cefet-rj\.br)$"; + + // Realiza o match do e-mail com o padrão + Match emailMatch = Regex.Match(email, studentEmailPattern); + + // Retorna se o e-mail é válido de acordo com o padrão + return emailMatch.Success; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/Student/UpdateStudent.cs b/src/Application/UseCases/Student/UpdateStudent.cs new file mode 100644 index 00000000..ba74f6e8 --- /dev/null +++ b/src/Application/UseCases/Student/UpdateStudent.cs @@ -0,0 +1,64 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.Student; +using Application.Ports.Student; +using Application.Validation; +using Domain.Entities.Enums; + +namespace Application.UseCases.Student +{ + public class UpdateStudent : IUpdateStudent + { + #region Global Scope + private readonly IStudentRepository _studentRepository; + private readonly IMapper _mapper; + public UpdateStudent(IStudentRepository studentRepository, IMapper mapper) + { + _studentRepository = studentRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateStudentInput model) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Recupera entidade que será atualizada + Domain.Entities.Student student = await _studentRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.Student)); + + // Verifica se a entidade foi excluída + UseCaseException.BusinessRuleViolation(student.DeletedAt != null, + "O estudante informado já foi excluído."); + + // Atualiza atributos permitidos + student.BirthDate = model.BirthDate; + student.CampusId = model.CampusId; + student.CellPhone = model.CellPhone; + student.CellPhoneDDD = model.CellPhoneDDD; + student.CEP = model.CEP; + student.City = model.City; + student.CourseId = model.CourseId; + student.DispatchDate = model.DispatchDate; + student.HomeAddress = model.HomeAddress; + student.IssuingAgency = model.IssuingAgency; + student.Phone = model.Phone; + student.PhoneDDD = model.PhoneDDD; + student.RG = model.RG; + student.StartYear = model.StartYear; + student.UF = model.UF; + student.AssistanceTypeId = model.AssistanceTypeId; + + // Enums + student.Race = (ERace)model.Race; + student.Gender = (EGender)model.Gender; + + // Atualiza estudante com as informações fornecidas + student = await _studentRepository.UpdateAsync(student); + + // Salva entidade atualizada no banco + return _mapper.Map(student); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/StudentDocuments/CreateStudentDocuments.cs b/src/Application/UseCases/StudentDocuments/CreateStudentDocuments.cs new file mode 100644 index 00000000..b1eda067 --- /dev/null +++ b/src/Application/UseCases/StudentDocuments/CreateStudentDocuments.cs @@ -0,0 +1,107 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; +using Microsoft.AspNetCore.Http; + +namespace Application.UseCases.StudentDocuments +{ + public class CreateStudentDocuments : ICreateStudentDocuments + { + #region Global Scope + private readonly IStudentDocumentsRepository _studentDocumentRepository; + private readonly IProjectRepository _projectRepository; + private readonly IStorageFileService _storageFileService; + private readonly IMapper _mapper; + + /// + /// Lista de arquivos que foram salvos na nuvem para remoção em caso de erro. + /// + private readonly List _urlFiles; + + public CreateStudentDocuments( + IStudentDocumentsRepository studentDocumentsRepository, + IProjectRepository projectRepository, + IStorageFileService storageFileService, + IMapper mapper) + { + _studentDocumentRepository = studentDocumentsRepository; + _projectRepository = projectRepository; + _storageFileService = storageFileService; + _mapper = mapper; + + _urlFiles = new List(); + } + #endregion + + public async Task ExecuteAsync(CreateStudentDocumentsInput input) + { + // Verifica se já há documentos para o projeto informado + var documents = await _studentDocumentRepository.GetByProjectIdAsync(input.ProjectId!); + UseCaseException.BusinessRuleViolation(documents is not null, + "Já existem documentos do aluno para o projeto indicado."); + + // Verifica se o projeto existe + var project = await _projectRepository.GetByIdAsync(input.ProjectId!); + UseCaseException.NotFoundEntityById(project is null, nameof(Domain.Entities.Project)); + + // Verifica se o projeto se encontra em situação de submissão de documentos (Aceito) + UseCaseException.BusinessRuleViolation( + project?.Status != Domain.Entities.Enums.EProjectStatus.Accepted, + "O projeto não está na fase de apresentação de documentos."); + + // Cria entidade a partir do input informado + var studentDocument = new Domain.Entities.StudentDocuments(input.ProjectId, input.AgencyNumber, input.AccountNumber); + + // Verifica se o aluno é menor de idade + if (project?.Student?.BirthDate > DateTime.UtcNow.AddYears(-18)) + { + // Verifica se foi informado a autorização dos pais + UseCaseException.BusinessRuleViolation(input.ParentalAuthorization is null, + "A autorização dos pais deve ser fornecida para alunos menores de idade."); + + // Salva autorização dos pais + studentDocument.ParentalAuthorization = await TryToSaveFileInCloud(input.ParentalAuthorization!); + } + + // Salva demais arquivos na nuvem + studentDocument.IdentityDocument = await TryToSaveFileInCloud(input.IdentityDocument!); + studentDocument.CPF = await TryToSaveFileInCloud(input.CPF!); + studentDocument.Photo3x4 = await TryToSaveFileInCloud(input.Photo3x4!); + studentDocument.SchoolHistory = await TryToSaveFileInCloud(input.SchoolHistory!); + studentDocument.ScholarCommitmentAgreement = await TryToSaveFileInCloud(input.ScholarCommitmentAgreement!); + studentDocument.AccountOpeningProof = await TryToSaveFileInCloud(input.AccountOpeningProof!); + + // Cria entidade + studentDocument = await _studentDocumentRepository.CreateAsync(studentDocument); + + // Atualiza status do projeto + project!.Status = Domain.Entities.Enums.EProjectStatus.DocumentAnalysis; + + // Salva alterações no banco de dados + _ = await _projectRepository.UpdateAsync(project); + + // Salva entidade no banco + return _mapper.Map(studentDocument); + } + + private async Task TryToSaveFileInCloud(IFormFile file) + { + try + { + string url = await _storageFileService.UploadFileAsync(file); + _urlFiles.Add(url); + return url; + } + catch (Exception ex) + { + // Caso dê erro, remove da nuvem os arquivos que foram salvos + foreach (var url in _urlFiles) + await _storageFileService.DeleteFileAsync(url); + throw UseCaseException.BusinessRuleViolation($"Erro ao salvar arquivos na nuvem.\n{ex}"); + } + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/StudentDocuments/DeleteStudentDocuments.cs b/src/Application/UseCases/StudentDocuments/DeleteStudentDocuments.cs new file mode 100644 index 00000000..7be2f110 --- /dev/null +++ b/src/Application/UseCases/StudentDocuments/DeleteStudentDocuments.cs @@ -0,0 +1,29 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; + +namespace Application.UseCases.StudentDocuments +{ + public class DeleteStudentDocuments : IDeleteStudentDocuments + { + #region Global Scope + private readonly IStudentDocumentsRepository _repository; + private readonly IMapper _mapper; + public DeleteStudentDocuments(IStudentDocumentsRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // TODO: Verificar se seria preciso remover os documentos do aluno caso fosse removido o registro de documentos do aluno + UseCaseException.NotInformedParam(id is null, nameof(id)); + var model = await _repository.DeleteAsync(id); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs b/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs new file mode 100644 index 00000000..67c368bb --- /dev/null +++ b/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; + +namespace Application.UseCases.StudentDocuments +{ + public class GetStudentDocumentsByProjectId : IGetStudentDocumentsByProjectId + { + #region Global Scope + private readonly IStudentDocumentsRepository _repository; + private readonly IMapper _mapper; + public GetStudentDocumentsByProjectId( + IStudentDocumentsRepository repository, + IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? projectId) + { + UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); + Domain.Entities.StudentDocuments? entity = await _repository.GetByProjectIdAsync(projectId); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs b/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs new file mode 100644 index 00000000..7a2bbf8d --- /dev/null +++ b/src/Application/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; + +namespace Application.UseCases.StudentDocuments +{ + public class GetStudentDocumentsByStudentId : IGetStudentDocumentsByStudentId + { + #region Global Scope + private readonly IStudentDocumentsRepository _repository; + private readonly IMapper _mapper; + public GetStudentDocumentsByStudentId( + IStudentDocumentsRepository repository, + IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? studentId) + { + UseCaseException.NotInformedParam(studentId is null, nameof(studentId)); + Domain.Entities.StudentDocuments? entity = await _repository.GetByStudentIdAsync(studentId); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/StudentDocuments/UpdateStudentDocuments.cs b/src/Application/UseCases/StudentDocuments/UpdateStudentDocuments.cs new file mode 100644 index 00000000..5120f7a6 --- /dev/null +++ b/src/Application/UseCases/StudentDocuments/UpdateStudentDocuments.cs @@ -0,0 +1,90 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Ports.StudentDocuments; +using Application.Validation; +using Microsoft.AspNetCore.Http; + +namespace Application.UseCases.StudentDocuments +{ + public class UpdateStudentDocuments : IUpdateStudentDocuments + { + #region Global Scope + private readonly IStudentDocumentsRepository _studentDocumentRepository; + private readonly IProjectRepository _projectRepository; + private readonly IStorageFileService _storageFileService; + private readonly IMapper _mapper; + + public UpdateStudentDocuments( + IStudentDocumentsRepository studentDocumentsRepository, + IProjectRepository projectRepository, + IStorageFileService storageFileService, + IMapper mapper) + { + _studentDocumentRepository = studentDocumentsRepository; + _projectRepository = projectRepository; + _storageFileService = storageFileService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateStudentDocumentsInput model) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Verifica se já foram enviados documentos para o projeto informado + Domain.Entities.StudentDocuments? studentDocuments = await _studentDocumentRepository.GetByIdAsync(id!); + UseCaseException.NotFoundEntityById(studentDocuments is null, nameof(studentDocuments)); + + // Verifica se o projeto se encontra em situação de submissão de documentos (Aceito ou Pendente do envio de documentação) + UseCaseException.BusinessRuleViolation( + studentDocuments!.Project?.Status is not Domain.Entities.Enums.EProjectStatus.Accepted + and not Domain.Entities.Enums.EProjectStatus.Pending, + "O projeto não está na fase de apresentação de documentos."); + + // Atualiza entidade a partir do input informado + studentDocuments!.AgencyNumber = model.AgencyNumber; + studentDocuments!.AccountNumber = model.AccountNumber; + + // Atualiza arquivos na nuvem + await TryToSaveFileInCloud(model.IdentityDocument!, studentDocuments.IdentityDocument); + await TryToSaveFileInCloud(model.CPF!, studentDocuments.CPF); + await TryToSaveFileInCloud(model.Photo3x4!, studentDocuments.Photo3x4); + await TryToSaveFileInCloud(model.SchoolHistory!, studentDocuments.SchoolHistory); + await TryToSaveFileInCloud(model.ScholarCommitmentAgreement!, studentDocuments.ScholarCommitmentAgreement); + await TryToSaveFileInCloud(model.ParentalAuthorization!, studentDocuments.ParentalAuthorization); + await TryToSaveFileInCloud(model.AccountOpeningProof!, studentDocuments.AccountOpeningProof); + + // Atualiza entidade + studentDocuments = await _studentDocumentRepository.UpdateAsync(studentDocuments); + + // Se o projeto está no status de pendente, atualiza para o status de análise de documentos + if (studentDocuments.Project?.Status == Domain.Entities.Enums.EProjectStatus.Pending) + { + Domain.Entities.Project? project = await _projectRepository.GetByIdAsync(studentDocuments.ProjectId); + project!.Status = Domain.Entities.Enums.EProjectStatus.DocumentAnalysis; + _ = await _projectRepository.UpdateAsync(project); + } + + // Retorna entidade atualizada + return _mapper.Map(studentDocuments); + } + + private async Task TryToSaveFileInCloud(IFormFile file, string? url) + { + try + { + if (file is not null) + { + _ = await _storageFileService.UploadFileAsync(file, url); + } + } + catch (Exception ex) + { + throw UseCaseException.BusinessRuleViolation($"Erro ao salvar arquivos na nuvem.\n{ex}"); + } + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/SubArea/CreateSubArea.cs b/src/Application/UseCases/SubArea/CreateSubArea.cs new file mode 100644 index 00000000..f93b9b00 --- /dev/null +++ b/src/Application/UseCases/SubArea/CreateSubArea.cs @@ -0,0 +1,45 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.Validation; + +namespace Application.UseCases.SubArea +{ + public class CreateSubArea : ICreateSubArea + { + #region Global Scope + private readonly ISubAreaRepository _subAreaRepository; + private readonly IAreaRepository _areaRepository; + private readonly IMapper _mapper; + public CreateSubArea(ISubAreaRepository subAreaRepository, IAreaRepository areaRepository, IMapper mapper) + { + _subAreaRepository = subAreaRepository; + _areaRepository = areaRepository; + _mapper = mapper; + } + #endregion + + public async Task ExecuteAsync(CreateSubAreaInput input) + { + // Verifica id da área + UseCaseException.NotInformedParam(input.AreaId == null, nameof(input.AreaId)); + + var entity = await _subAreaRepository.GetByCodeAsync(input.Code); + UseCaseException.BusinessRuleViolation(entity != null, + "Já existe uma Subárea para o código informado."); + + // Valida se existe área + var area = await _areaRepository.GetByIdAsync(input.AreaId) + ?? throw UseCaseException.NotFoundEntityByParams(nameof(Domain.Entities.Area)); + + // Verifica se área está ativa + UseCaseException.BusinessRuleViolation(area.DeletedAt != null, + "A Área informada está inativa."); + + // Cria nova área + entity = await _subAreaRepository.CreateAsync(_mapper.Map(input)); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/SubArea/DeleteSubArea.cs b/src/Application/UseCases/SubArea/DeleteSubArea.cs new file mode 100644 index 00000000..e50a9b9b --- /dev/null +++ b/src/Application/UseCases/SubArea/DeleteSubArea.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.Validation; + +namespace Application.UseCases.SubArea +{ + public class DeleteSubArea : IDeleteSubArea + { + #region Global Scope + private readonly ISubAreaRepository _repository; + private readonly IMapper _mapper; + public DeleteSubArea(ISubAreaRepository subAreaRepository, IMapper mapper) + { + _repository = subAreaRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Remove a entidade + Domain.Entities.SubArea model = await _repository.DeleteAsync(id); + + // Retorna o entidade removido + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/SubArea/GetSubAreaById.cs b/src/Application/UseCases/SubArea/GetSubAreaById.cs new file mode 100644 index 00000000..afea8b6f --- /dev/null +++ b/src/Application/UseCases/SubArea/GetSubAreaById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.Validation; + +namespace Application.UseCases.SubArea +{ + public class GetSubAreaById : IGetSubAreaById + { + #region Global Scope + private readonly ISubAreaRepository _subAreaRepository; + private readonly IMapper _mapper; + public GetSubAreaById(ISubAreaRepository subAreaRepository, IMapper mapper) + { + _subAreaRepository = subAreaRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _subAreaRepository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/SubArea/GetSubAreasByArea.cs b/src/Application/UseCases/SubArea/GetSubAreasByArea.cs new file mode 100644 index 00000000..fd5ba7c1 --- /dev/null +++ b/src/Application/UseCases/SubArea/GetSubAreasByArea.cs @@ -0,0 +1,32 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.Validation; + +namespace Application.UseCases.SubArea +{ + public class GetSubAreasByArea : IGetSubAreasByArea + { + #region Global Scope + private readonly ISubAreaRepository _subAreaRepository; + private readonly IMapper _mapper; + public GetSubAreasByArea(ISubAreaRepository subAreaRepository, IMapper mapper) + { + _subAreaRepository = subAreaRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(Guid? areaId, int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + UseCaseException.NotInformedParam(areaId is null, nameof(areaId)); + IEnumerable entities = await _subAreaRepository.GetSubAreasByAreaAsync(areaId, skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/SubArea/UpdateSubArea.cs b/src/Application/UseCases/SubArea/UpdateSubArea.cs new file mode 100644 index 00000000..0b907e62 --- /dev/null +++ b/src/Application/UseCases/SubArea/UpdateSubArea.cs @@ -0,0 +1,37 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.SubArea; +using Application.Ports.SubArea; +using Application.Validation; + +namespace Application.UseCases.SubArea +{ + public class UpdateSubArea : IUpdateSubArea + { + #region Global Scope + private readonly ISubAreaRepository _subAreaRepository; + private readonly IMapper _mapper; + public UpdateSubArea(ISubAreaRepository subAreaRepository, IMapper mapper) + { + _subAreaRepository = subAreaRepository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id, UpdateSubAreaInput input) + { + // Recupera entidade que será atualizada + Domain.Entities.SubArea entity = await _subAreaRepository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.SubArea)); + + // Atualiza atributos permitidos + entity.Name = input.Name; + entity.Code = input.Code; + entity.AreaId = input.AreaId; + + // Salva entidade atualizada no banco + Domain.Entities.SubArea model = await _subAreaRepository.UpdateAsync(entity); + return _mapper.Map(model); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/ActivateUser.cs b/src/Application/UseCases/User/ActivateUser.cs new file mode 100644 index 00000000..0abc6dd5 --- /dev/null +++ b/src/Application/UseCases/User/ActivateUser.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; +using Application.Validation; + +namespace Application.UseCases.User +{ + public class ActivateUser : IActivateUser + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly IMapper _mapper; + public ActivateUser(IUserRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se id é nulo + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Encontra usuário pelo Id e o ativa + Domain.Entities.User user = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + user.ActivateEntity(); + + // Atualiza usuário + Domain.Entities.User entity = await _repository.UpdateAsync(user); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/DeactivateUser.cs b/src/Application/UseCases/User/DeactivateUser.cs new file mode 100644 index 00000000..124589af --- /dev/null +++ b/src/Application/UseCases/User/DeactivateUser.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; +using Application.Validation; + +namespace Application.UseCases.User +{ + public class DeactivateUser : IDeactivateUser + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly IMapper _mapper; + public DeactivateUser(IUserRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + // Verifica se id é nulo + UseCaseException.NotInformedParam(id is null, nameof(id)); + + // Encontra usuário pelo Id e o desativa + Domain.Entities.User user = await _repository.GetByIdAsync(id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + user.DeactivateEntity(); + + // Atualiza usuário + Domain.Entities.User entity = await _repository.UpdateAsync(user); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/GetActiveUsers.cs b/src/Application/UseCases/User/GetActiveUsers.cs new file mode 100644 index 00000000..2d51ad4e --- /dev/null +++ b/src/Application/UseCases/User/GetActiveUsers.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; + +namespace Application.UseCases.User +{ + public class GetActiveUsers : IGetActiveUsers + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly IMapper _mapper; + public GetActiveUsers(IUserRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + IEnumerable entities = await _repository.GetActiveUsersAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/GetInactiveUsers.cs b/src/Application/UseCases/User/GetInactiveUsers.cs new file mode 100644 index 00000000..834cefb3 --- /dev/null +++ b/src/Application/UseCases/User/GetInactiveUsers.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; + +namespace Application.UseCases.User +{ + public class GetInactiveUsers : IGetInactiveUsers + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly IMapper _mapper; + public GetInactiveUsers(IUserRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task> ExecuteAsync(int skip, int take) + { + // Valida valores de skip e take + if (skip < 0 || take < 1) + throw new ArgumentException("Parâmetros inválidos."); + + IEnumerable entities = await _repository.GetInactiveUsersAsync(skip, take); + return _mapper.Map>(entities).AsQueryable(); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/GetUserById.cs b/src/Application/UseCases/User/GetUserById.cs new file mode 100644 index 00000000..0446f0db --- /dev/null +++ b/src/Application/UseCases/User/GetUserById.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; +using Application.Validation; + +namespace Application.UseCases.User +{ + public class GetUserById : IGetUserById + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly IMapper _mapper; + public GetUserById(IUserRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(Guid? id) + { + UseCaseException.NotInformedParam(id is null, nameof(id)); + var entity = await _repository.GetByIdAsync(id); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/MakeAdmin.cs b/src/Application/UseCases/User/MakeAdmin.cs new file mode 100644 index 00000000..7a4170be --- /dev/null +++ b/src/Application/UseCases/User/MakeAdmin.cs @@ -0,0 +1,67 @@ +using Application.Interfaces.UseCases.User; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.User +{ + public class MakeAdmin : IMakeAdmin + { + private readonly IUserRepository _userRepository; + private readonly IProfessorRepository _professorRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + public MakeAdmin( + IUserRepository userRepository, + IProfessorRepository professorRepository, + ITokenAuthenticationService tokenAuthenticationService) + { + _userRepository = userRepository; + _professorRepository = professorRepository; + _tokenAuthenticationService = tokenAuthenticationService; + } + + public async Task ExecuteAsync(Guid? userId) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(userId is null, + nameof(userId)); + + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + UseCaseException.BusinessRuleViolation(userClaims is null, + "Usuário não autenticado"); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + + // Verifica se usuário logado é administrador + UseCaseException.BusinessRuleViolation(userClaim!.Role != Domain.Entities.Enums.ERole.ADMIN, + "Usuário logado não é administrador"); + + // Verifica se usuário logado realmente existe + var user = await _userRepository.GetByIdAsync(userClaim.Id); + UseCaseException.NotFoundEntityById(user is null, nameof(user)); + + // Obtém usuário que será tornado administrador + var userToMakeAdmin = await _userRepository.GetByIdAsync(userId); + + // Verifica se usuário que será tornado administrador existe + UseCaseException.NotFoundEntityById(userToMakeAdmin is null, + nameof(userToMakeAdmin)); + + /// Verifica se usuário que será tornado administrador é professor, + /// pois apenas um professor pode se tornar administrador + _ = await _professorRepository.GetByUserIdAsync(userId) + ?? throw new UseCaseException("Apenas professores podem se tornar administradores"); + + // Atualiza usuário + userToMakeAdmin!.Role = Domain.Entities.Enums.ERole.ADMIN; + + // Salva alterações + await _userRepository.UpdateAsync(userToMakeAdmin); + + // Retorna mensagem de sucesso + return $"Usuário {user!.Id} tornou administrador o usuário {userToMakeAdmin.Id}"; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/MakeCoordinator.cs b/src/Application/UseCases/User/MakeCoordinator.cs new file mode 100644 index 00000000..e1e9b014 --- /dev/null +++ b/src/Application/UseCases/User/MakeCoordinator.cs @@ -0,0 +1,69 @@ +using Application.Interfaces.UseCases.User; +using Application.Validation; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; + +namespace Application.UseCases.User +{ + public class MakeCoordinator : IMakeCoordinator + { + private readonly IUserRepository _userRepository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + public MakeCoordinator( + IUserRepository userRepository, + ITokenAuthenticationService tokenAuthenticationService) + { + _userRepository = userRepository; + _tokenAuthenticationService = tokenAuthenticationService; + } + + public async Task ExecuteAsync(Guid? userId) + { + // Verifica se o id foi informado + UseCaseException.NotInformedParam(userId is null, + nameof(userId)); + + // Obtém usuário logado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + UseCaseException.BusinessRuleViolation(userClaims is null, + "Usuário não autenticado"); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + + // Verifica se usuário logado é administrador + UseCaseException.BusinessRuleViolation(userClaim!.Role != Domain.Entities.Enums.ERole.ADMIN, + "Usuário não é administrador"); + + // Verifica se usuário logado realmente existe + var user = await _userRepository.GetByIdAsync(userClaim.Id); + UseCaseException.NotFoundEntityById(user is null, nameof(user)); + + // Verifica se usuário logado é coordenador + UseCaseException.BusinessRuleViolation(!user!.IsCoordinator, + "Usuário não é coordenador"); + + // Obtém usuário que será tornado coordenador + var userToMakeCoordinator = await _userRepository.GetByIdAsync(userId); + + // Verifica se usuário que será tornado coordenador existe + UseCaseException.NotFoundEntityById(userToMakeCoordinator is null, + nameof(userToMakeCoordinator)); + + // Remove coordenação do usuário logado + user.IsCoordinator = false; + + // Salva alterações + await _userRepository.UpdateAsync(user); + + // Atualiza usuário + userToMakeCoordinator!.IsCoordinator = true; + + // Salva alterações + await _userRepository.UpdateAsync(userToMakeCoordinator); + + // Retorna mensagem de sucesso + return $"Usuário {user.Id} tornou coordenador o usuário {userToMakeCoordinator.Id}"; + } + } +} \ No newline at end of file diff --git a/src/Application/UseCases/User/UpdateUser.cs b/src/Application/UseCases/User/UpdateUser.cs new file mode 100644 index 00000000..80135d4c --- /dev/null +++ b/src/Application/UseCases/User/UpdateUser.cs @@ -0,0 +1,49 @@ +using AutoMapper; +using Domain.Interfaces.Repositories; +using Domain.Interfaces.Services; +using Application.Interfaces.UseCases.User; +using Application.Ports.User; +using Application.Validation; + +namespace Application.UseCases.User +{ + public class UpdateUser : IUpdateUser + { + #region Global Scope + private readonly IUserRepository _repository; + private readonly ITokenAuthenticationService _tokenAuthenticationService; + private readonly IMapper _mapper; + public UpdateUser(IUserRepository repository, ITokenAuthenticationService tokenAuthenticationService, IMapper mapper) + { + _repository = repository; + _tokenAuthenticationService = tokenAuthenticationService; + _mapper = mapper; + } + #endregion Global Scope + + public async Task ExecuteAsync(UserUpdateInput input) + { + // Busca as claims do usuário autenticado + var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); + + // Obtém id do usuário e id de acordo com perfil logado + var userClaim = userClaims!.Values.FirstOrDefault(); + var actorId = userClaims.Keys.FirstOrDefault(); + + // Verifica se o id informado é nulo + UseCaseException.NotInformedParam(userClaim!.Id is null, nameof(userClaim.Id)); + + // Busca usuário pelo id informado + Domain.Entities.User user = await _repository.GetByIdAsync(userClaim.Id) + ?? throw UseCaseException.NotFoundEntityById(nameof(Domain.Entities.User)); + + // Atualiza atributos permitidos + user.Name = input.Name; + user.CPF = input.CPF; + + // Salva usuário atualizado no banco + Domain.Entities.User entity = await _repository.UpdateAsync(user); + return _mapper.Map(entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Validation/UseCaseException.cs b/src/Application/Validation/UseCaseException.cs new file mode 100644 index 00000000..c838d5ec --- /dev/null +++ b/src/Application/Validation/UseCaseException.cs @@ -0,0 +1,37 @@ +namespace Application.Validation; +public class UseCaseException : Exception +{ + public UseCaseException(string error) : base(error) { } + public UseCaseException() : base() { } + public UseCaseException(string? message, Exception? innerException) : base(message, innerException) { } + + public static Exception BusinessRuleViolation(string message) => new UseCaseException(message); + public static Exception NotValidParam(string message) => new UseCaseException(message); + public static Exception NotFoundEntityById(string entityName) => new UseCaseException($"Entidade ({entityName}) não encontrada através do Id informado."); + public static Exception NotFoundEntityByParams(string entityName) => new UseCaseException($"Entidade ({entityName}) não encontrada através dos parâmetros informados."); + + public static void NotFoundEntityByParams(bool hasError, string entityName) + { + if (hasError) throw NotFoundEntityByParams(entityName); + } + + public static void NotFoundEntityById(bool hasError, string entityName) + { + if (hasError) throw NotFoundEntityById(entityName); + } + + public static void BusinessRuleViolation(bool hasError, string message) + { + if (hasError) throw BusinessRuleViolation(message); + } + + public static void NotInformedParam(bool hasError, string paramName) + { + if (hasError) throw new UseCaseException($"Parâmetro ({paramName}) é obrigatório."); + } + + public static void NotValidParam(bool hasError, string paramName) + { + if (hasError) throw new UseCaseException($"Parâmetro ({paramName}) é inválido."); + } +} diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md new file mode 100644 index 00000000..3e68fd4c --- /dev/null +++ b/src/CHANGELOG.md @@ -0,0 +1,279 @@ +# Change Log + +All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines. + + +## [0.1.0](https://www.github.com/eduardo-paes/DotnetGraphQl/releases/tag/v0.1.0) (2023-11-1) + +### Features + +* Add CORS configuration ([eb8cde4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/eb8cde421b565331008d8719e335ee5b4b00d391)) +* Add user names to project GET endpoints ([05e2610](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/05e2610f8239e1424b21adb8aec3cb7d7dba0719)) + +### Bug Fixes + +* Adjust GetUserById usecase ([e4774ed](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e4774ed601ec0c27847732506047d653f7628ac8)) +* Adjust subarea usecases ([75ae476](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/75ae476f8e013c1b5a2e0f36868e55131d77025a)) +* Correction on project opening. ([8d258a8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8d258a8339c0e248bfcb8122cecb175df5f192e3)) +* Disable CORS policy for tests ([9868851](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/98688510d106b00fd28f75f433854dace1bc6588)) +* Enable correct StorageFileService ([a5557d4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a5557d4a054820b3b39eb17993477e6f2f7e6268)) +* Enable IpRateLimiting in Startup ([2283568](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2283568bda84fed8416097fe9722e60491890473)) + + +## [0.0.1](https://www.github.com/eduardo-paes/DotnetGraphQl/releases/tag/v0.0.1) (2023-10-24) + +### Features + +* add .env support ([f9f0037](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f9f0037296b70e86d1dafee1346456d41bf4bad1)) +* add acitivity type entity ([80837c5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/80837c5e33d97824917b8ad55c6de5a4e177984b)) +* add activities implementation ([a0345e3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a0345e3e973a2efa5660b03f4c6d02b1408eb342)) +* add activities seeder ([8dc8dee](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8dc8dee3a4cf9acb8248e14f63321a8d3804b103)) +* add activities to project evaluation ([42609e4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/42609e47e85769224cf51cda6a5c47b9fa101d30)) +* add all methods of userController ([a3d5e92](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a3d5e92adb12026760f51a7cdb8a6233ae24167f)) +* add application project ([374d8c4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/374d8c4b23711b3d821b53d29fc0a9bbfe066f5a)) +* add area and mainArea dbsets ([6581e9b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6581e9b7f8669a5e896aaf3fab59eada2907c69d)) +* add area and mainArea mappings ([efc2b25](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/efc2b25b9de9cc7597ac3321cb5d2b9aca25056c)) +* add area dtos, repositories and services ([9c975dd](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9c975dd53dbdae78bd2e765ee436e5f7e3d894ab)) +* add area implementation ([d2a6499](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d2a6499b685d0315a87dbefeabbce22314e20a0e)) +* add areaRepository ([c02d9ce](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c02d9ce7f68ec1b027185750f3757cceac5da9f8)) +* add areas seed to migrations ([6fbc02a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6fbc02af55442d0b76b6e079657bb2cdb472bce5)) +* add assistance scolarship ([8778fd8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8778fd8142eef081e94b152cd38066cb38804805)) +* add auth basic methods to user repository and service ([1e2bafa](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1e2bafac28095ce65ff3e72cf3bd9de64d8d34bb)) +* add autogenerate id to user ([e8e7ffc](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e8e7ffce927b514836600f09e6d41e6d0d81b7cb)) +* add campus implementation ([6e903cd](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6e903cdbef19825a333e1064cf8de3535e5e3439)) +* add certificate url to project ([d694954](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d6949548f3dbdf038e039477e467fe5855896bbd)) +* add ClosePendingProjects web function ([7f8401f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7f8401fc3135032b174c5fb5586e7aa538108d6a)) +* add confirm email user usecase ([789028b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/789028bedefd4baff2cc921620095fe22cb8940c)) +* add controller documentation and simplify startup ([d264a61](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d264a612423b94bc8e20faa90fc7226f42274a4d)) +* add coordinator name to certificate ([fbca60f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/fbca60f95a2f36622e774e5030e30b96f72ae4e5)) +* add cors configuration ([4375b35](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4375b354316d8f53cdefd61f435eb4fc55e2ec13)) +* add course controller tests ([ddffbf9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ddffbf939062e1e02e93eba6bcbd4d4677012b8d)) +* add course implementation ([c70a93c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c70a93cbe53d34628fc1bbf263d7dc671020037f)) +* add data context initial configuration ([02c9de9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/02c9de92404e5e7cbeec4b0f709ee2ca42005f01)) +* add description field on notice. ([a3ab36e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a3ab36ea411ab09b1cf44537ce41e1cdb0ccb557)) +* add docker support ([5aca3ce](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5aca3ce583bc717b001dc59e7b68f4a61f2289d6)) +* add docs to project ([331d668](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/331d668d8cbaf5efc1575085750dbf7e8644fe42)) +* add domain project and some validations to entities ([e9787b5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e9787b52d442c502fde77fc32f6656e88e33aa15)) +* add domain project to manage entities ([7ca5e90](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7ca5e9077694ad99c601c7c7fba45e0aa6c422b2)) +* add evaluate appeal project usecase ([8341042](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8341042fca7e4d66320884247411f1b118a902c8)) +* add evaluate submission project usecase ([5f52bb2](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5f52bb2ced231398627d181ca8eb1e94ccb70f1b)) +* add files to seed data in migrations ([1ba39b8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1ba39b836a0835b0b80422cdeaf25c47731f276b)) +* add first version of auth endpoints and methods ([c919a83](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c919a8373ee93cce590de380e8cc38f8f55fed20)) +* add front-end url to emails ([7cedd17](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7cedd17541557ee920ba06c69cd58da8aacbe7a9)) +* add GenerateCertificate usecase ([6e6efce](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6e6efceed5ea671d87148c498ec837b02027be80)) +* add generic crud service ([2ab86e1](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2ab86e195c2e9ccdbd49cc35a21f8f28c8f4b36a)) +* add get user by cpf implementation ([3b16629](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3b166298bcc20c2deb1e0f02f46ab596661360a6)) +* add get user by cpf in repository interface ([c556b33](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c556b334f098e9dfae6942ea60649d19bd1e9371)) +* add get user claims method ([596619f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/596619f18fd5aca994437bca850348080646d780)) +* Add GetActivitiesByProjectId endpoint ([cc995ed](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cc995ed2733a9dd5e7757fa8d61c0f25dd17635d)) +* add GetStudentByRegistrationCode endpoint ([a89c491](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a89c49144abbf4e6f53df15f0fbad3851624b949)) +* add gitattributes ([bbc8619](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/bbc8619cbd35a3d32720647fba4d0d1ad301c894)) +* add guid to entity ([6814b86](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6814b86f4f9809c735bed14d0fa9f88320d6e53c)) +* add impediment to opening a project for a suspended teacher ([502fb33](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/502fb33950973187a16492c5a7f44ff176f8df6f)) +* add infra-ioc reference to solution ([40f6c56](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/40f6c564dfda87aff246deb212412a9e050d4697)) +* add initial dbcontext ([6657f5e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6657f5ea3c7f3b4cf86481b2140443f26a6bb480)) +* add instructions to readme file ([90b56b5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/90b56b5c36c88c76dde668b3b9ad3da140000724)) +* add main appsettings ([a19f4e1](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a19f4e1bd153699fbd8dd7b31e74dc4dbd520355)) +* add main area implementations ([96d45fa](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/96d45fa56f3962ae471bada259859d983484482e)) +* add main area repository ([c026ed1](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c026ed18b36fd4d03eb8e8a8b8642e99f9ea7e9e)) +* add main area service ([6085260](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6085260a2ba4d9e5fd3d5eaa162f24f542ae84f7)) +* add make admin and coordinator endpoints ([6cd2e52](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6cd2e528b676824869aec0223746c87104d7198e)) +* add MakeAdmin usecase ([8a71658](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8a71658b858dd0c8a0b9290f0a99435166324883)) +* add MakeCoordinator usecase ([6bf3a9a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6bf3a9a9c06aefcb43e49d2700ddff6221dc085a)) +* add manual rate limit ([4b9be7c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4b9be7c620fbcd59ddcb6c5906e1cde8c4e6c2b1)) +* add migrations to git ([73351b9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/73351b9be2287f57700449e2f8ad4ccb83a28ade)) +* add more info to ReportService ([e4be590](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e4be59028a8a4e6e595e061e7330ed62edb4d2dd)) +* add more project usecases ([c81284c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c81284c468f58e83ea2e39e5bc3b98520131a6bc)) +* add more validation to user model ([6c40f4f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6c40f4fc7b4b52ee1b0c59f45d0c4f880b8f083b)) +* add notice implementation ([6871781](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6871781a02b757b43e8b756b69dc563b1d061c54)) +* add order to controllers tests ([4350c76](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4350c76e5079a5a34ef5a3c7b15057f5508dc2be)) +* add pagination to routes ([632a63f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/632a63f42e3c91d9317dce74255ded9d6d33d8ae)) +* add pagination to user repository ([7a90597](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7a9059773802bb8b14608c09207c9b05bbf3240f)) +* add partial implementation of project class ([3c4fb2e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3c4fb2eff951eafb3c0f6981eb490cd2a6d4b879)) +* add partial implementation of ReportService ([f784c99](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f784c99641d4d3c3584ec19ee4eeff5aaf82ee6d)) +* add postman integration ([be11cc2](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/be11cc2d905ca4db25702dba4140da9e8f888771)) +* Add ProfessorId or StudentId to JWT token ([6ea9f1e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6ea9f1eda654838511d3598e130455fcc7e42190)) +* add program-type implementation ([cc1cd07](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cc1cd0722d8c0231cec719a4add8ef2b55f1936e)) +* add programtype controller tests ([fab01e0](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/fab01e0202365813ed0a891d3cd2ce71c97465e5)) +* add ProjectActivities to Project usecases ([b57d8e7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/b57d8e740e6f39795ea2e88efb60f05ef583c8b5)) +* add ProjectReport controller ([ad6b048](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ad6b04803f785e82437b63cecbcce25073e5a633)) +* add ProjectReport usecases and entity ([315c042](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/315c0429d951a4794b256284e78b95c102eea530)) +* add RegistrationCode to student ([3341b9d](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3341b9deb2217d840e58a32c94696e9bc8f93dce)) +* add ReportDeadlineNotificationFunction ([b9401ba](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/b9401ba4bb8985e800064517c0c2b2c9e41c199f)) +* add ReportService implementation ([9fbfad3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9fbfad33c8ad3929f943e751fcb6ee2ce74bb700)) +* add RequestStudentRegister endpoint ([8942901](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8942901a99e016d0b6c02d43e3102fb01a8c74a6)) +* add search by main-area in area service ([cbf95db](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cbf95db8cfbba61e8115edc4b5e215231ccf358f)) +* add separation between mappers on application layer ([8ec0cd3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8ec0cd39763746c5766273d5e2e495073fd7a170)) +* add serilog for logging service ([0fac1e3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/0fac1e3db621c1ec3f402be145ce6169d964cd8c)) +* add setup to startup and program ([40827c8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/40827c883bba2aa733501ee222a404af6584de9d)) +* add simple user model ([0e31a32](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/0e31a32c249a335874832201c996955273ebd541)) +* add skip/take validations for usecases ([0d47a93](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/0d47a93abaa316ce091d71147b6d4faf9594e330)) +* add some scoped classes ([169d686](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/169d686e1f52512cdcf9a8255f91ef16f9b96ba6)) +* add staging docker-compose ([af847b5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/af847b555de6382a63af5fad872f3d7f8d974042)) +* add storage service implementation ([9b6c016](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9b6c016db6401fa0aa2ed63eead3c9af895d1957)) +* add student class implementation ([992e681](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/992e681f3940b5fec97acb1080adeb5bf4fbe483)) +* add student implementation ([4e66556](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4e6655684703f45cd295e121dd76952f1bad1a9a)) +* add StudentDocuments endpoints ([6805f0a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6805f0a6068dd4596ffb9aa5d51b40378ab32a1b)) +* add subarea repositories and services ([2dc9d32](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2dc9d32e6f732cfd09d011bb9c8037f96ccee1ce)) +* add support for migrations in infra.data project ([a9fcb67](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a9fcb6748d2934a7f8e3aa74a13c03db100e8e17)) +* add support to debug in vscode ([a18cafe](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a18cafe35f80895da490481510cf650f2f39aa85)) +* add swagger libraries ([927bf41](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/927bf41879cc78753097ab9e20e50dea8f337a5d)) +* add temporary folder creation ([b55803d](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/b55803d5cf4648fbe0612d1d0c018dc4a0ffe02a)) +* add unhandler exception ([bdde659](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/bdde659e9089aef27477a852a8657eeb1f967868)) +* add unit tests and entities fixing ([c604b23](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c604b2326dee72b6467e210ea5e234e8f2d31571)) +* add unit tests for user model ([27d4405](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/27d4405878568e334ddd288462ca01e1d2202904)) +* add user validations on register method ([a485993](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a485993c45f52e1e3b0bc7029ab3b0c81a50ca87)) +* add validations to attributes of area and mainArea ([41b0906](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/41b0906e62b8c5e1043cc359bcc1e234e6e0f917)) +* add validations to services and controllers ([58f2f76](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/58f2f76c59e0d29e04a5678062ac1668967394e2)) +* add voyager support ([1468441](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1468441e46f54c47cb67bd215d7d9ccfee9cdfbe)) +* add web function to generate certificates ([c8540c1](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c8540c102685eadfd994d5e9ab938b1d6c3159d8)) +* adjust notice and activity relation ([6634ecd](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6634ecdafffe2501ce4cb82f24222fdaad1e337f)) +* conclude GenerateCertificate web function ([1e5d438](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1e5d4380ff556a47cfcdd1234ee7a5f419b0a71c)) +* create directory for local storage if doesn't exist ([883b58b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/883b58b3e0d47bbd95cbf80002f4dae9081d5f5d)) +* disable native rate limit ([bd1dbe5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/bd1dbe561b07fecd7dd34141cdaa62759ef44bdf)) +* enable CORS ([64ed9d8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/64ed9d8ec55ec39b45b7b282d75db80d565da0a5)) +* update TimerTrigger for tests ([e795b80](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e795b8011ba96e932911ddeb0af1fcc5050f3d7e)) +* update web function for deployment ([241897b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/241897be0ca9d581a052d9de7fe5a290761952c4)) +* **.env:** add .env file verification ([bff332a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/bff332aa989f85aed287fd919fc75ae6111a35de)) +* **adapters:** add professor services ([9707b33](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9707b338d7fca570af8d22f438677616f64686dc)) +* **adapters:** add user service implementation ([09851c5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/09851c5ebb1144d48526a7789a10b1b308a17f3d)) +* **api:** add professor controller ([f1f26c3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f1f26c38ef2dcf3be104d9169267cf39c858ff51)) +* **application:** add main structures to user service ([c07819f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c07819f996db9e02bbbb3875bf89674f1e497c5a)) +* **appsettings:** add azure secret support ([289b016](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/289b01662a8e9c6edee7a02c873f7951d3158456)) +* **auth:** add auth full implementations ([f2da049](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f2da049c62ce83556b1ee5daec0a96f65320f3b9)) +* **auth:** add password set and reset methods ([5a67447](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5a67447359bc52caf60e685a55a8362acbd7f3b7)) +* **azure:** add support to azure blob storage ([a09f431](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a09f431a6f5133bbce133a54d5b087b2c7ba8b79)) +* **CORS:** add CORS policy. ([ab75a24](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ab75a240e303ea4cf0b0273aa76f4bc02ebc3e99)) +* **domain:** add user implementation ([395d353](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/395d35369e71a838231a8a6f27e32089b3057719)) +* **domain:** add user repository ([c818268](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c8182689f39d8a817e27e2497d8a1a5af7ff3748)) +* **dotenv:** add dotenv class to manage secrets ([3c13351](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3c13351cbd089c78e905c1cdfff94df6338fd04f)) +* **entities:** add DeletedAt to ProjectEvaluation ([a494d2a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a494d2a8293e5e26eb04f5f7deb3a0c3ab045bcf)) +* **entities:** add notices ([eb0c0d0](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/eb0c0d0148a45f4f11a6dcf63bd8733bc1c191e4)) +* **entities:** add ProjectActivity ([d757dac](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d757dac09f9e8a8d58c4bb25f8a8c21173ec16ff)) +* **entities:** add SuspensionEndDate to professor ([12aa2c3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/12aa2c32ae9dddd2b0f7215c28323dc4d7253a38)) +* **entity:** add activate entity control to class ([62013b7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/62013b72a236b8fd79f166174e49c9699f1e5879)) +* **entity:** add StudentDocuments ([7cc6ee0](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7cc6ee079248c06e63431de6ae4dee4e0fb10353)) +* **infra:** add professor implementations and dependencies ([d68e99f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d68e99fc08c1f7c776241618d64d86de6ff7018b)) +* **infra-data:** add database logic to infra-data project ([9a4229c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9a4229c6af2486c5b98a1b43ebc35db2d9a92532)) +* **infra-data:** add integration from user repository to database ([074f38e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/074f38ec603b30f325a6ea3ae136a274f071c81d)) +* **infra-data:** add project reference to solution ([006f546](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/006f5464b08e1250f7c9af95f24c35128741d58c)) +* **infra-ioc:** add basic api dependency injection logics ([2ec556e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2ec556e05f8817f5f554b46266bd4f1fd6858b41)) +* **ioc:** add projects dependency injection ([7cfe692](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7cfe692d581e953af2f83e5f22083872c5ca0e45)) +* **ioc:** add support to azure key vault ([00624c9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/00624c9f65f5ca1a312ac34885e3f564f891c239)) +* **ioc:** update .env reading ([6ba86be](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6ba86be33dd5c9faf93012d25aaa5a9c525ab90a)) +* **jwt:** add authorization request in controllers ([1e2fc78](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1e2fc78bd3efd364fc295b94a6d069b7cfa0f1fb)) +* **jwt:** add jwt implementation ([13f8e0a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/13f8e0a3429220e93d71d186c3bd5c8fd0b23ce4)) +* **logging:** add seq support in azure ([40247c9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/40247c92858f11bf54cdc44a57d4f35861f81f9d)) +* **logs:** add seq support for managing logs ([f22fc9c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f22fc9ca14b07cc35b4559ce8401840ed533830e)) +* **notice:** add more properties to notice ([958f8d2](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/958f8d2be1fcdd74030ac73bdded9f24e5fd42eb)) +* **persistence:** add notice configuration ([87e834b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/87e834bda432d4ce7ff33859ac31fa101b2541e8)) +* **persistence:** add project configuration ([21deade](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/21deade02f0ecc90c62e5acf824e48baca787ec8)) +* **persistence:** add project implementation ([02778ca](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/02778ca6662bddf7972bb2e59f7ec62b6ff3dc50)) +* **persistence:** add soft-delete ([ca82e5b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ca82e5bb7c8b169788d05d302769a023b1d4e309)) +* **persistence:** add student-assistance repository ([ca9a086](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ca9a086858a77bcce600d3570d588b424cd3c9ba)) +* **persistence:** add user seed ([cdaf8e8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cdaf8e8cdecb8801186a3bcfdc3315bad3fcd459)) +* **presenter-controllers:** add evaluation-project ([7e7e78e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7e7e78ee80d6a6716126a5deef0a5a2c65f613b4)) +* **presenter-controllers:** add student-documents ([6aa00ea](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6aa00eabb1ea9e6cd16295445466d95c167c864e)) +* **professor:** add service implementation ([a9896a5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a9896a5df84d7a7bd74160a11fa5ba80109ed398)) +* **professor:** add usecase implementation ([6eb338b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6eb338bf1dcc4e06e20bd5e4ff5d2a1cf6a3adeb)) +* **proj-activity:** add contracts and repository interface ([e230ab7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e230ab7d499a86484c191c0ae1a7992122cfb593)) +* **project:** add evaluation results to entity ([5bfab7e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5bfab7ecf838023e50019f35787bce8397a12d06)) +* **project:** add more properties to entity ([5a679b6](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5a679b67bf8a48beea30855d47ed266f929bd219)) +* **project:** add presenters and controllers ([2146214](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2146214792335b10fbe8f408e1ec0eb5f4cda6a3)) +* **project:** add some usecases ([a6d6710](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a6d67105376dd6c6fdb9802ec3464afec3a0caf2)) +* **Project:** add endpoint to get project to evaluate ([7f9c309](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7f9c30975d3007473150097bfad0aec152604f1a)) +* **Project:** add ProjectPartialReport endpoints ([56c8c2b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/56c8c2b5d43a5b7b7a38b2fc68299222b9fa8631)) +* **project-evaluation:** add usecase structures ([5a424d9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5a424d9a8eea8c572692d1a89a86c5862085205d)) +* **ProjectEvaluation:** add student documents evaluation ([24be4b9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/24be4b946cb4b4844f22f7f7afec0b1ad7e57d61)) +* **ProjectReport:** add permission to deliver report 6 months ahead of schedule ([fa4f910](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/fa4f910a7c813629453016425c9b3823319fe97a)) +* **ProjectReport:** add persistence configuration ([8b52603](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8b5260375c163d1d49080ef4284d8e544fe1dc6f)) +* **readme:** update .net version ([cb0b4c2](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cb0b4c2d63c653d90837ce0b76346b6d900358b9)) +* **repositories:** add some implementations ([1280181](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1280181c934b8c5b6e4a6a7a749706aaae10ab69)) +* **repositoris:** add order by to list methods ([d90efa8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d90efa8fdb777a4dc1be2a0bad1013b52a14da49)) +* **services:** add certificate template ([6ebf8b2](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6ebf8b2da9854c3107f3f8c455befbde7647b332)) +* **settings:** add launch config to debugger ([95ab95d](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/95ab95dd9894988f124583d110869910ed07bbd6)) +* **StorageFileService:** add UploadFileAsync by bytes ([0ed1b15](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/0ed1b156c6edd68a3ae2dcbaaacb2ad004eb75f7)) +* **student:** add email-service and crud ([7964e3b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7964e3b1211fc97357467f4ff5d7628e2c48d36d)) +* **tests:** add area unit tests ([ecd170c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ecd170c280c5e3354c2e18e96cffc347a0cb147f)) +* **tests:** add area usecases tests ([b75842f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/b75842f2e3ea94e67148f6f8a003942cdd98e647)) +* **tests:** add assistance scholarship unit tests ([523be6c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/523be6c2bd2985710e4afa658d5796609a3b3052)) +* **tests:** add campus unit tests ([28d4bed](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/28d4bedd686c318329dcae5525c00dcba96a79a3)) +* **tests:** add course unit tests ([271dccb](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/271dccb267eb68efd049d3ddb142daf054394054)) +* **tests:** add first domain test ([af74391](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/af74391f22375ee099ed457a9054e0680f36aad0)) +* **tests:** add mainarea unit tests ([decaa01](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/decaa015b0493f9b1a7bff6d7d4bd1546993dc54)) +* **tests:** add notice unit tests ([9bb0efc](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9bb0efcfe9605e3ab0451ac6baa4a257cc54e285)) +* **tests:** add professor unit tests ([c6db754](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c6db754b041d0c47cdb5b60f4e04d92d4ef2d178)) +* **tests:** add program type unit tests ([057b5fa](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/057b5fa720b0950e60dbb05a33649188fb1e9e30)) +* **tests:** add project evaluation unit tests ([1588052](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1588052c31c44475282980e6d118dc44a892bf25)) +* **tests:** add project unit tests ([578ac26](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/578ac2665e6f71fbeccb4b1102d5eb28a95226a0)) +* **tests:** add student unit tests ([8eb5d8c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8eb5d8c468f7db9e5c5f90360367d35eebc5a81c)) +* **tests:** add subarea unit tests ([7f4af70](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7f4af706f7f2325a990a025382e9919fba9b7bd3)) +* **usecase:** add interfaces ([d35d891](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d35d891a151f575b4812df78ee3c38a06f14537e)) +* **usecase:** add project contracts ([ea9a3f9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ea9a3f9b921cf92f34bd100f833f47d25e7c9e59)) +* **usecases:** add email alerts to Notice and ProjectEvaluation ([4e53848](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4e53848cc77996cad46a231821e89c09888fa898)) +* **usecases:** add get evaluation by project id ([c8f8d3a](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c8f8d3ae039add9ba651c67436f2794b8b54b818)) +* **usecases:** add student documents ([1e6f6b3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1e6f6b3652f94325a2093d89c66bee8365d47dab)) +* **users:** add first query ([6f87bc6](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6f87bc6751abd08b46e627756c2f3f8beb578f35)) +* **web-api:** add project controller ([290da8b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/290da8b1b66b36b80d770a8721d1e993505d3729)) +* **web-api:** add user controller ([2345922](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/2345922f3ce91d752288d1e6158e19b5ccb7ebbd)) +* **webapi:** add project evaluation controller ([507313e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/507313e7d880fdc1ad6cbcf931687fbc715f7b92)) +* **webapi:** add rate limit support to endpoints ([68382a8](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/68382a84e2c42c69ffc805cf82527ee27b319363)) +* **webapi:** add role policies to endpoints ([d49032f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d49032f3dd8afa3f628ca2a1b18de101c618103c)) +* **webapi:** add version endpoint for tests ([c86cc60](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c86cc603e6f2408c2e6359738374d6d48ceec652)) + +### Bug Fixes + +* add area repository retrieves ([8215f9d](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8215f9de0a414074f98b9e715f6177aeaca10a82)) +* add empty and protected constructor to user class ([3d14888](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3d14888264e228acec1604f8ff4b75bba9808293)) +* add space before logo on email templates ([6ca37e9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6ca37e93a496ac2ad7f9bb6da5e3d6d9cd59193a)) +* adjust activities seeder. ([33b2333](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/33b23331d810cc09fdbf9a8e8cf3c1f3ec561595)) +* Adjust admin user login and default seeding ([486a46c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/486a46cd055f873eb40c7c94f09fc507ff1eec6d)) +* adjust all namespaces ([5aa712b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5aa712b1e5ee7d35b1b5c83571957ec2b8d7cc11)) +* adjust api endpoints ([993f624](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/993f624290c4621b3d351d976a06cf2937014498)) +* adjust Area namespaces ([fc00e73](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/fc00e73ac4a27a90a3b2b022f2389a9b62928000)) +* adjust CORS configuration ([d77df0f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/d77df0f7f21ce52a49e2cd01b336fa7c062fa220)) +* adjust email sender and disable ssl for tests ([602fbd3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/602fbd358d1a79df13b4eb186e99961c9628ade4)) +* adjust email service dependency injection ([fb947b0](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/fb947b0c36ecddb95a96bb3b136257c1f11737f5)) +* adjust GetProjectsToEvaluate endpoint returns ([4f45374](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4f453742827ea61ee8061c6358ed062207d497f4)) +* adjust logs recording on seq ([508ff97](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/508ff9729073e93b236c980c0e91da28895b4b73)) +* adjust Notice seeder ([f391532](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f391532cc089702996f41bb0cee1dcb6397e8e58)) +* adjust notice tests ([7262dcc](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7262dcc88eb8526f6992a64211ca128f0342daca)) +* adjust NoticeUnitTests ([5aaa5e9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5aaa5e9ce089303d19eb7b0ce76585eba7b81ad7)) +* adjust oderby to avoid get errors ([ac207c4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ac207c4b34b11ae53b5f3bc226bb3ca16a0858ee)) +* adjust project evaluation usecases ([9db0791](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9db07910f8ea3e8ca42543ee5137a85389a8aeba)) +* adjust project evalution usecases ([65e1d1c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/65e1d1c60ed7ee74c0a7dd27bd32a2db2041f69a)) +* adjust project usecases ([3aebb17](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3aebb17bc04c4a2d529d80af4cff3500e1f14059)) +* adjust ProjectEvaluationRepository ([e73ba88](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e73ba881d9d43bf4ea9446b5f1874683a5ff75b0)) +* adjust repositories for new project entity ([6915fb3](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/6915fb32e86d2c89c23b05c2b7b7f422475a73a0)) +* adjust UpdateAreaInput namespace ([9d1e819](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9d1e8190b336444dd1a14390be928a62e53837bd)) +* adjust user update ([23d01a4](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/23d01a4beb536cd5ddd519bc237506ace1189e48)) +* adjust user-seed password ([3919953](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/391995384ae343cf5df55cf4b809762a8ba9530c)) +* allow anonymous some endpoints ([0fc2623](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/0fc2623f61488c82c90acff847352a7141f4675f)) +* fix cpf and email validation ([8276427](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/8276427f5c867d0576e4eaff691b70db8fc080ab)) +* fix create user usecase ([7fe95c9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/7fe95c93506902062610bd493570b7e40acc016a)) +* fix return of getall methods in services ([37a4ff7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/37a4ff77cb5b643243953e5a5e94c228c4afc5cb)) +* merge with volumes ([ad6f276](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/ad6f2766f994690aa63802c985c4e3f63ac91851)) +* remove docker-compose duplication project ([4b6f6e7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/4b6f6e7865764372a5f03571fc5968df8837e147)) +* remove ProfessorProjectTitle from certificate ([a188314](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a1883148bb1162e8c88233e28e738ad150d851c6)) +* remove unnecessary files ([956e65b](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/956e65b168acc3a771a83dccd4030995118aa5e4)) +* remove warnings ([cb67fe9](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/cb67fe93b8358d2ff67b3d92b17fea4b908996d0)) +* **adapters:** adjust GetSubAreasByArea return ([82a5af5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/82a5af5593d48244b3b92d5f53dff98bb895fe61)) +* **area:** fix response mapping ([a558c9c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/a558c9c56439bf31e1658f2b81b24879c41b63fc)) +* **CreateStudentDocuments:** change project status ([87ffdeb](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/87ffdeb032a7cc452f895599da68b29f33d8b7d6)) +* **dbcontext:** fix dbcontext dispose bug ([3f839fc](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3f839fcb1a09c8fd25b0a749240ef0f675290664)) +* **email-service:** enable ssl ([1965ed0](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/1965ed065cfdd0df22857d79dbcd80fa6d1e339e)) +* **entities:** change EF constructors ([9a6b525](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/9a6b5257be790b8104b4bbb93dd0f5b5c5e12d2c)) +* **entities:** remove evaluation fields from project ([418862c](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/418862c312e1b1b0d2fc34eb13fc4d31ec50273c)) +* **entity:** update assistance type name ([f1b4110](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f1b4110da9a4f35e94f313b9314853ae503ef680)) +* **EvaluateAppealProject:** adjust inputs validation ([94fc0cc](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/94fc0cc98117484ff9917ed01a5b4d0fcb974414)) +* **GenerateCertificate:** adjust GetNoticeEndingAsync ([5b1348e](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/5b1348e0920d176277878618b6c1f1013f88de61)) +* **infra.ioc:** add authservice instance ([f875ad7](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/f875ad7aac833785ce05e3d2ade5af2bdceefd18)) +* **ioc:** adjust external services imports ([76dd9a5](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/76dd9a5fc07fe18384cf4ed55c7a2ca43c648e33)) +* **Notice:** adjust Description mapping ([e17da78](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/e17da78d80f55ac629632aeeabe4fa5fd5b13aeb)) +* **project:** adjust user-role validation ([510ba68](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/510ba68f1b59de7f07517a623a1809a8d07de9d4)) +* **project-evaluation:** fix usecase outputs ([c4704e6](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/c4704e6b4b40e3de26490190129ac66145ec22af)) +* **subarea:** fix mappings ([51f1dac](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/51f1daca5fdd985eb02766e7885622a18faaa092)) +* **tests:** adjust unit and integration tests ([adbfa51](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/adbfa515d0aaa53b49aee51a21c9b2cd16d59e93)) +* **usecases:** adjust notice creation ([3febd1f](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3febd1f85464445197b95e01ef38b16f3c2234fa)) +* **webapi:** adjust error type in activity controller ([3ff2d36](https://www.github.com/eduardo-paes/DotnetGraphQl/commit/3ff2d3654866da67cdd00b10d49eb483e3edf4b0)) + diff --git a/src/CopetSystem.API.sln b/src/CopetSystem.API.sln deleted file mode 100644 index 63cc7262..00000000 --- a/src/CopetSystem.API.sln +++ /dev/null @@ -1,97 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{60BB6D65-1899-43B7-BD10-986239A3257E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adapters", "Adapters\Adapters.csproj", "{9C140CD0-7DDC-4859-BA21-68B64AF11284}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{CCAE2172-E62F-4440-ABAD-D1A1DC5A234D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence", "Infrastructure\Persistence\Persistence.csproj", "{FCC65F72-7AF9-4940-B07A-E7DDDC7967B7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IoC", "Infrastructure\IoC\IoC.csproj", "{DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPI", "Infrastructure\WebAPI\WebAPI.csproj", "{163219BE-A0D2-41A1-A028-501AAB069BD9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain.Tests", "Domain.Tests\Domain.Tests.csproj", "{370C1BE1-63EE-4D2E-AD4A-6D8392EEFF40}" -EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "Docker\docker-compose.dcproj", "{58DF51E2-67F0-4740-B20B-8DE70C99FE2B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {862A9206-AC16-433A-ABF3-90A368B54F81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {862A9206-AC16-433A-ABF3-90A368B54F81}.Debug|Any CPU.Build.0 = Debug|Any CPU - {862A9206-AC16-433A-ABF3-90A368B54F81}.Release|Any CPU.ActiveCfg = Release|Any CPU - {862A9206-AC16-433A-ABF3-90A368B54F81}.Release|Any CPU.Build.0 = Release|Any CPU - {70685C44-2526-483D-92AF-F388BF7A82FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70685C44-2526-483D-92AF-F388BF7A82FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70685C44-2526-483D-92AF-F388BF7A82FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70685C44-2526-483D-92AF-F388BF7A82FF}.Release|Any CPU.Build.0 = Release|Any CPU - {3D46FA46-8426-483A-B1C2-2225359B99FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D46FA46-8426-483A-B1C2-2225359B99FC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D46FA46-8426-483A-B1C2-2225359B99FC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D46FA46-8426-483A-B1C2-2225359B99FC}.Release|Any CPU.Build.0 = Release|Any CPU - {2CEC4DEE-78D4-4130-AC1E-F732FF9F37DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CEC4DEE-78D4-4130-AC1E-F732FF9F37DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CEC4DEE-78D4-4130-AC1E-F732FF9F37DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CEC4DEE-78D4-4130-AC1E-F732FF9F37DD}.Release|Any CPU.Build.0 = Release|Any CPU - {2669F050-AFB0-4DB8-A81E-D0F4D8DDDD01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2669F050-AFB0-4DB8-A81E-D0F4D8DDDD01}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2669F050-AFB0-4DB8-A81E-D0F4D8DDDD01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2669F050-AFB0-4DB8-A81E-D0F4D8DDDD01}.Release|Any CPU.Build.0 = Release|Any CPU - {04B6663E-228C-4278-B913-614958C9DBFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04B6663E-228C-4278-B913-614958C9DBFB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04B6663E-228C-4278-B913-614958C9DBFB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04B6663E-228C-4278-B913-614958C9DBFB}.Release|Any CPU.Build.0 = Release|Any CPU - {656ABFBE-1791-4014-B59E-F9C83DBF8AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {656ABFBE-1791-4014-B59E-F9C83DBF8AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {656ABFBE-1791-4014-B59E-F9C83DBF8AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {656ABFBE-1791-4014-B59E-F9C83DBF8AC0}.Release|Any CPU.Build.0 = Release|Any CPU - {84B8DFE3-440C-4E7F-B35E-5A668D03D549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84B8DFE3-440C-4E7F-B35E-5A668D03D549}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84B8DFE3-440C-4E7F-B35E-5A668D03D549}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84B8DFE3-440C-4E7F-B35E-5A668D03D549}.Release|Any CPU.Build.0 = Release|Any CPU - {60BB6D65-1899-43B7-BD10-986239A3257E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60BB6D65-1899-43B7-BD10-986239A3257E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60BB6D65-1899-43B7-BD10-986239A3257E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60BB6D65-1899-43B7-BD10-986239A3257E}.Release|Any CPU.Build.0 = Release|Any CPU - {9C140CD0-7DDC-4859-BA21-68B64AF11284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C140CD0-7DDC-4859-BA21-68B64AF11284}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C140CD0-7DDC-4859-BA21-68B64AF11284}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9C140CD0-7DDC-4859-BA21-68B64AF11284}.Release|Any CPU.Build.0 = Release|Any CPU - {FCC65F72-7AF9-4940-B07A-E7DDDC7967B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCC65F72-7AF9-4940-B07A-E7DDDC7967B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCC65F72-7AF9-4940-B07A-E7DDDC7967B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCC65F72-7AF9-4940-B07A-E7DDDC7967B7}.Release|Any CPU.Build.0 = Release|Any CPU - {DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2}.Release|Any CPU.Build.0 = Release|Any CPU - {163219BE-A0D2-41A1-A028-501AAB069BD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {163219BE-A0D2-41A1-A028-501AAB069BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {163219BE-A0D2-41A1-A028-501AAB069BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {163219BE-A0D2-41A1-A028-501AAB069BD9}.Release|Any CPU.Build.0 = Release|Any CPU - {60C2C8B4-6EF9-40E6-A193-972EBD7EE1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60C2C8B4-6EF9-40E6-A193-972EBD7EE1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60C2C8B4-6EF9-40E6-A193-972EBD7EE1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60C2C8B4-6EF9-40E6-A193-972EBD7EE1F6}.Release|Any CPU.Build.0 = Release|Any CPU - {58DF51E2-67F0-4740-B20B-8DE70C99FE2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58DF51E2-67F0-4740-B20B-8DE70C99FE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58DF51E2-67F0-4740-B20B-8DE70C99FE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58DF51E2-67F0-4740-B20B-8DE70C99FE2B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FCC65F72-7AF9-4940-B07A-E7DDDC7967B7} = {CCAE2172-E62F-4440-ABAD-D1A1DC5A234D} - {DC12A23D-7BF2-4F0A-A317-B9FA8818C9C2} = {CCAE2172-E62F-4440-ABAD-D1A1DC5A234D} - {163219BE-A0D2-41A1-A028-501AAB069BD9} = {CCAE2172-E62F-4440-ABAD-D1A1DC5A234D} - EndGlobalSection -EndGlobal diff --git a/src/Docker/.dockerignore b/src/Docker/.dockerignore deleted file mode 100644 index 3729ff0c..00000000 --- a/src/Docker/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md \ No newline at end of file diff --git a/src/Docker/Postgres/docker-compose.yml b/src/Docker/Postgres/docker-compose.yml new file mode 100644 index 00000000..c324a677 --- /dev/null +++ b/src/Docker/Postgres/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.4' + +services: + # PostgreSQL Database + postgres: + image: postgres:latest + container_name: postgres + environment: + POSTGRES_PASSWORD: Copet@123 + POSTGRES_USER: copet-admin + POSTGRES_DB: COPET_DB + ports: + - 15432:5432 + volumes: + - ./volumes/postgresql:/var/lib/postgresql/data + networks: + - gpic-network + + # PostgreAdmin UI + pgadmin: + image: dpage/pgadmin4:latest + container_name: pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: eduardo-paes@outlook.com + PGADMIN_DEFAULT_PASSWORD: CopetSystem!2022 + ports: + - 16543:80 + depends_on: + - postgres + networks: + - gpic-network + +networks: + gpic-network: + driver: bridge diff --git a/src/Docker/Seq/docker-compose.yml b/src/Docker/Seq/docker-compose.yml new file mode 100644 index 00000000..c39c8008 --- /dev/null +++ b/src/Docker/Seq/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.4' + +services: + seq: + image: datalust/seq:latest + ports: + - "5341:80" + environment: + - ACCEPT_EULA=Y + - SEQ_LICENSE_KEY= + networks: + - gpic-network + +networks: + gpic-network: + driver: bridge \ No newline at end of file diff --git a/src/Docker/WebAPI/docker-compose.yml b/src/Docker/WebAPI/docker-compose.yml new file mode 100644 index 00000000..288d5fda --- /dev/null +++ b/src/Docker/WebAPI/docker-compose.yml @@ -0,0 +1,90 @@ +version: '3.4' + +services: + + # PostgreSQL Database + postgres: + container_name: postgres_gpic + image: postgres:latest + environment: + POSTGRES_PASSWORD: Copet@123 + POSTGRES_USER: copet-admin + POSTGRES_DB: COPET_DB + ports: + - 15432:5432 + volumes: + - ./volumes/postgresql:/var/lib/postgresql/data + networks: + - gpic-network + + # Seq - Servidor de Logs + seq: + container_name: seq_gpic + image: datalust/seq:latest + ports: + - "5341:80" + environment: + - ACCEPT_EULA=Y + - SEQ_API_KEY=vxM2YLukOTgnraaYczDh + networks: + - gpic-network + + # Web API + webapi: + container_name: webapi_gpic + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=https://+:443;http://+:80 + image: ${DOCKER_REGISTRY-}webapi + build: + context: ../../ + dockerfile: Infrastructure/WebAPI/Dockerfile + ports: + - 5051:443 + - 5050:80 + volumes: + - ~/.aspnet/https:/root/.aspnet/https:ro + - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro + depends_on: + - postgres + - seq + networks: + - gpic-network + + # Web Functions + webfunctions: + container_name: webfunctions_gpic + image: ${DOCKER_REGISTRY-}webfunctions + build: + context: ../../ + dockerfile: Infrastructure/WebFunctions/Dockerfile + ports: + - "7071:80" + volumes: + - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro + environment: + - AzureWebJobsStorage=UseDevelopmentStorage=true + - AzureFunctionsJobHost__Logging__Console__IsEnabled=true + depends_on: + - azurite + - postgres + - seq + networks: + - gpic-network + + # Azurite - Storage Emulator (Blob, Queue, Table) + azurite: + container_name: azurite_gpic + image: mcr.microsoft.com/azure-storage/azurite + ports: + - "10000:10000" + - "10001:10001" + - "10002:10002" + environment: + - AZURITE_ACCOUNTS=devstoreaccount1:devstoreaccount1Key + networks: + - gpic-network + +networks: + gpic-network: + driver: bridge diff --git a/src/Docker/WebAPI/sample.env b/src/Docker/WebAPI/sample.env new file mode 100644 index 00000000..2bb33cfa --- /dev/null +++ b/src/Docker/WebAPI/sample.env @@ -0,0 +1,20 @@ +# Email Service +SMTP_EMAIL_USERNAME=EMAIL +SMTP_EMAIL_PASSWORD=SENHA + +# JWT Secrets +JWT_SECRET_KEY=8y/B?E(G+KbPeShVmYq3t6w9z$C&F)J@McQfTjWnZr4u7x!A%D*G-KaPdRgUkXp2 +JWT_EXPIRE_IN=60 +JWT_ISSUER=gpic-webapi +JWT_AUDIENCE=webapi.gpic.server + +# SEQ Logger Service +SEQ_API_KEY=vxM2YLukOTgnraaYczDh +SEQ_URL=http://seq_gpic:5341 + +# Connection String do banco de dados +POSTGRES_CONNECTION_STRING="Server=postgres_gpic;Database=COPET_DB;Port=5432;User Id=copet-admin;Password=Copet@123;" + +# Executa a migração do banco de dados +# deixe como True apenas na inicilização +EXECUTE_MIGRATION=False \ No newline at end of file diff --git a/src/Docker/WebFunction/docker-compose.yml b/src/Docker/WebFunction/docker-compose.yml new file mode 100644 index 00000000..b9924fdb --- /dev/null +++ b/src/Docker/WebFunction/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.4' + +services: + + # Web Functions + webfunctions: + container_name: webfunctions_gpic + image: ${DOCKER_REGISTRY-}webfunctions + build: + context: ../../ + dockerfile: Infrastructure/WebFunctions/Dockerfile + ports: + - "7071:80" + volumes: + - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro + environment: + - AzureWebJobsStorage=UseDevelopmentStorage=true + - AzureFunctionsJobHost__Logging__Console__IsEnabled=true + depends_on: + - azurite + networks: + - gpic-network + + # Azurite - Storage Emulator (Blob, Queue, Table) + azurite: + container_name: azurite_gpic + image: mcr.microsoft.com/azure-storage/azurite + ports: + - "10000:10000" + - "10001:10001" + - "10002:10002" + environment: + - AZURITE_ACCOUNTS=devstoreaccount1:devstoreaccount1Key + networks: + - gpic-network diff --git a/src/Docker/docker-compose.dcproj b/src/Docker/docker-compose.dcproj deleted file mode 100644 index 61415981..00000000 --- a/src/Docker/docker-compose.dcproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - 2.1 - Linux - {58DF51E2-67F0-4740-B20B-8DE70C99FE2B} - True - {Scheme}://localhost:{ServicePort}/swagger - webapi - - - - docker-compose.yml - - - - - diff --git a/src/Docker/docker-compose.override.yml b/src/Docker/docker-compose.override.yml deleted file mode 100644 index 8d0d72f4..00000000 --- a/src/Docker/docker-compose.override.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: '3.4' - -services: - # PostgreSQL Database - postgres: - image: postgres:latest - container_name: postgres - environment: - POSTGRES_PASSWORD: Copet@123 - POSTGRES_USER: copet-admin - POSTGRES_DB: COPET_DB - ports: - - 15432:5432 - volumes: - - ./volumes/postgresql:/var/lib/postgresql/data - networks: - - postgres-network - - # PostgreAdmin UI - pgadmin: - image: dpage/pgadmin4:latest - container_name: pgadmin - environment: - PGADMIN_DEFAULT_EMAIL: eduardo-paes@outlook.com - PGADMIN_DEFAULT_PASSWORD: CopetSystem!2022 - ports: - - 16543:80 - depends_on: - - postgres - networks: - - postgres-network - - seq: - image: datalust/seq:latest - ports: - - "5341:80" - environment: - - ACCEPT_EULA=Y - - SEQ_LICENSE_KEY= - networks: - - postgres-network - - # Web API - webapi: - environment: - - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=https://+:443;http://+:80 - ports: - - 5555:443 - volumes: - - ~/.aspnet/https:/root/.aspnet/https:ro - - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro - depends_on: - - postgres - - seq - networks: - - postgres-network - -networks: - postgres-network: - driver: bridge diff --git a/src/Docker/docker-compose.yml b/src/Docker/docker-compose.yml deleted file mode 100644 index 62c656d2..00000000 --- a/src/Docker/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3.4' - -services: - webapi: - image: ${DOCKER_REGISTRY-}webapi - build: - context: ../ - dockerfile: Infrastructure/WebAPI/Dockerfile diff --git a/src/Docker/seq.docker-compose.yml b/src/Docker/seq.docker-compose.yml deleted file mode 100644 index fe1614e9..00000000 --- a/src/Docker/seq.docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: '3.4' - -services: - seq: - image: datalust/seq:latest - ports: - - "5341:80" - environment: - - ACCEPT_EULA=Y - - SEQ_LICENSE_KEY= diff --git a/src/Dockerfile b/src/Dockerfile deleted file mode 100644 index 38ddcc8d..00000000 --- a/src/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 - -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["Domain/Domain.csproj", "Domain/"] -COPY ["Adapters/Adapters.csproj", "Adapters/"] -COPY ["Infrastructure/Services/Services.csproj", "Infrastructure/Services/"] -COPY ["Infrastructure/Persistence/Persistence.csproj", "Infrastructure/Persistence/"] -COPY ["Infrastructure/IoC/IoC.csproj", "Infrastructure/IoC/"] -COPY ["Infrastructure/WebAPI/WebAPI.csproj", "Infrastructure/WebAPI/"] -RUN dotnet restore "Infrastructure/WebAPI/WebAPI.csproj" -COPY . . -WORKDIR "/src/Infrastructure/WebAPI" -RUN dotnet build "WebAPI.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "WebAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "WebAPI.dll"] diff --git a/src/Domain.Tests/Domain.Tests.csproj b/src/Domain.Tests/Domain.Tests.csproj index 7b77a051..f54c4a16 100644 --- a/src/Domain.Tests/Domain.Tests.csproj +++ b/src/Domain.Tests/Domain.Tests.csproj @@ -1,10 +1,13 @@ + net7.0 enable enable false + 0.1.0 + @@ -23,12 +26,15 @@ + - - - + + + + + \ No newline at end of file diff --git a/src/Domain.Tests/Entities/ActivityTypeUnitTests.cs b/src/Domain.Tests/Entities/ActivityTypeUnitTests.cs new file mode 100644 index 00000000..88edb097 --- /dev/null +++ b/src/Domain.Tests/Entities/ActivityTypeUnitTests.cs @@ -0,0 +1,128 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class ActivityTypeUnitTests + { + private static ActivityType MockValidActivityType() => + new("Valid Name", "Valid Unity", Guid.NewGuid()); + + [Fact] + public void SetName_ValidName_SetsName() + { + // Arrange + var activityType = MockValidActivityType(); + var name = "Valid New Name"; + + // Act + activityType.Name = name; + + // Assert + activityType.Name.Should().Be(name); + } + + [Theory] + [InlineData("")] + [InlineData("AB")] + [InlineData("A")] + [InlineData(null)] + public void SetName_InvalidName_ThrowsException(string invalidName) + { + // Arrange + var activityType = MockValidActivityType(); + + // Act & Assert + Assert.Throws(() => activityType.Name = invalidName); + } + + [Fact] + public void SetUnity_ValidUnity_SetsUnity() + { + // Arrange + var activityType = MockValidActivityType(); + var unity = "Valid New Unity"; + + // Act + activityType.Unity = unity; + + // Assert + activityType.Unity.Should().Be(unity); + } + + [Theory] + [InlineData("")] + [InlineData("AB")] + [InlineData("A")] + [InlineData(null)] + public void SetUnity_InvalidUnity_ThrowsException(string invalidUnity) + { + // Arrange + var activityType = MockValidActivityType(); + + // Act & Assert + Assert.Throws(() => activityType.Unity = invalidUnity); + } + + [Fact] + public void Constructor_WithValidArguments_CreatesActivityTypeWithCorrectValues() + { + // Arrange + var name = "Valid Name"; + var unity = "Valid Unity"; + var noticeId = Guid.NewGuid(); + + // Act + var activityType = new ActivityType(name, unity, noticeId); + + // Assert + activityType.Name.Should().Be(name); + activityType.Unity.Should().Be(unity); + activityType.NoticeId.Should().Be(noticeId); + } + + [Theory] + [InlineData("")] + [InlineData("AB")] + [InlineData("A")] + [InlineData(null)] + public void Constructor_WithInvalidName_ThrowsException(string invalidName) + { + // Arrange + var unity = "Valid Unity"; + var noticeId = Guid.NewGuid(); + + // Act & Assert + Assert.Throws(() => new ActivityType(invalidName, unity, noticeId)); + } + + [Theory] + [InlineData("")] + [InlineData("AB")] + [InlineData("A")] + [InlineData(null)] + public void Constructor_WithInvalidUnity_ThrowsException(string invalidUnity) + { + // Arrange + var name = "Valid Name"; + var noticeId = Guid.NewGuid(); + + // Act & Assert + Assert.Throws(() => new ActivityType(name, invalidUnity, noticeId)); + } + + [Fact] + public void Constructor_WithNullNoticeId_ThrowsException() + { + // Arrange + var name = "Valid Name"; + var unity = "Valid Unity"; + var noticeId = (Guid?)null; + + // Act & Assert + Assert.Throws(() => new ActivityType(name, unity, noticeId)); + } + } +} diff --git a/src/Domain.Tests/Entities/ActivityUnitTests.cs b/src/Domain.Tests/Entities/ActivityUnitTests.cs new file mode 100644 index 00000000..900939cf --- /dev/null +++ b/src/Domain.Tests/Entities/ActivityUnitTests.cs @@ -0,0 +1,150 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class ActivityUnitTests + { + private static Activity MockValidActivity() => + new("Valid Name", 10.0, 20.0, Guid.NewGuid()); + + [Fact] + public void SetName_ValidName_SetsName() + { + // Arrange + var activity = MockValidActivity(); + var name = "Valid New Name"; + + // Act + activity.Name = name; + + // Assert + activity.Name.Should().Be(name); + } + + [Theory] + [InlineData("")] + [InlineData("AB")] + [InlineData("A")] + [InlineData(null)] + public void SetName_InvalidName_ThrowsException(string invalidName) + { + // Arrange + var activity = MockValidActivity(); + + // Act & Assert + Assert.Throws(() => activity.Name = invalidName); + } + + [Fact] + public void SetPoints_ValidPoints_SetsPoints() + { + // Arrange + var activity = MockValidActivity(); + var points = 15.0; + + // Act + activity.Points = points; + + // Assert + activity.Points.Should().Be(points); + } + + [Fact] + public void SetPoints_NullPoints_ThrowsException() + { + // Arrange + var activity = MockValidActivity(); + + // Act & Assert + Assert.Throws(() => activity.Points = null); + } + + [Fact] + public void SetLimits_ValidLimits_SetsLimits() + { + // Arrange + var activity = MockValidActivity(); + var limits = 25.0; + + // Act + activity.Limits = limits; + + // Assert + activity.Limits.Should().Be(limits); + } + + [Fact] + public void SetLimits_NullLimits_SetsLimitsToMaxValue() + { + // Arrange + var activity = MockValidActivity(); + + // Act + activity.Limits = null; + + // Assert + activity.Limits.Should().Be(double.MaxValue); + } + + [Fact] + public void Constructor_WithValidArguments_CreatesActivityWithCorrectValues() + { + // Arrange + var name = "Valid Name"; + var points = 10.0; + var limits = 20.0; + var activityTypeId = Guid.NewGuid(); + + // Act + var activity = new Activity(name, points, limits, activityTypeId); + + // Assert + activity.Name.Should().Be(name); + activity.Points.Should().Be(points); + activity.Limits.Should().Be(limits); + activity.ActivityTypeId.Should().Be(activityTypeId); + } + + [Fact] + public void Constructor_WithNullName_ThrowsException() + { + // Arrange + var name = null as string; + var points = 10.0; + var limits = 20.0; + var activityTypeId = Guid.NewGuid(); + + // Act & Assert + Assert.Throws(() => new Activity(name, points, limits, activityTypeId)); + } + + [Fact] + public void Constructor_WithNullPoints_ThrowsException() + { + // Arrange + var name = "Valid Name"; + var points = (double?)null; + var limits = 20.0; + var activityTypeId = Guid.NewGuid(); + + // Act & Assert + Assert.Throws(() => new Activity(name, points, limits, activityTypeId)); + } + + [Fact] + public void Constructor_WithNullActivityTypeId_ThrowsException() + { + // Arrange + var name = "Valid Name"; + var points = 10.0; + var limits = 20.0; + var activityTypeId = (Guid?)null; + + // Act & Assert + Assert.Throws(() => new Activity(name, points, limits, activityTypeId)); + } + } +} diff --git a/src/Domain.Tests/Entities/AreaUnitTests.cs b/src/Domain.Tests/Entities/AreaUnitTests.cs index 1b77a6b0..15eba6d5 100644 --- a/src/Domain.Tests/Entities/AreaUnitTests.cs +++ b/src/Domain.Tests/Entities/AreaUnitTests.cs @@ -1,5 +1,4 @@ using Domain.Entities; -using Domain.Entities.Enums; using Domain.Validation; using FluentAssertions; using Xunit; @@ -7,7 +6,7 @@ namespace Domain.Tests.Entities; public class AreaUnitTests { - private Area MockValidArea() => new Area(Guid.NewGuid(), "ABC", "Area Name"); + private static Area MockValidArea() => new(Guid.NewGuid(), "ABC", "Area Name"); [Fact] public void SetMainAreaId_ValidId_SetsMainAreaId() diff --git a/src/Domain.Tests/Entities/AssistanceTypeUnitTests.cs b/src/Domain.Tests/Entities/AssistanceTypeUnitTests.cs new file mode 100644 index 00000000..1d816db7 --- /dev/null +++ b/src/Domain.Tests/Entities/AssistanceTypeUnitTests.cs @@ -0,0 +1,103 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class AssistanceTypeUnitTests + { + private AssistanceType MockValidAssistanceType() => + new AssistanceType(Guid.NewGuid(), "Scholarship Name", "Scholarship Description"); + + [Fact] + public void SetName_ValidName_SetsName() + { + // Arrange + var scholarship = MockValidAssistanceType(); + var name = "Scholarship Name"; + + // Act + scholarship.Name = name; + + // Assert + scholarship.Name.Should().Be(name); + } + + [Fact] + public void SetName_NullOrEmptyName_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Name = null); + Assert.Throws(() => scholarship.Name = string.Empty); + } + + [Fact] + public void SetName_TooShortName_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Name = "AB"); + } + + [Fact] + public void SetName_TooLongName_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Name = new string('A', 1500)); + } + + [Fact] + public void SetDescription_ValidDescription_SetsDescription() + { + // Arrange + var scholarship = MockValidAssistanceType(); + var description = "Scholarship Description"; + + // Act + scholarship.Description = description; + + // Assert + scholarship.Description.Should().Be(description); + } + + [Fact] + public void SetDescription_NullOrEmptyDescription_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Description = null); + Assert.Throws(() => scholarship.Description = string.Empty); + } + + [Fact] + public void SetDescription_TooShortDescription_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Description = "AB"); + } + + [Fact] + public void SetDescription_TooLongDescription_ThrowsException() + { + // Arrange + var scholarship = MockValidAssistanceType(); + + // Act & Assert + Assert.Throws(() => scholarship.Description = "K5SYFiBmSfsadfsaBtfdsgfsdQkwCPPXKaTqoVtz7WF0LpFMG6hCu5sk8dBEbrhNDrBrd8WVt3vNp8uzhkck3nrYPVCMHvnDRnmdqlWRScH8yUYzoeLsiTvnROfHFm0GCsIltVcGiOBE6rkHCgjFCFJiSAdnBJOj54Godr0lRXRWBb8iIthDiPIvVKmtfRzH61ojFjezbENdzGgJisDzjg8zEogLia5D5cJKhGPGAgXsl1lDlFy2H8RvMZGwNg9UFyvRnRo6MYkxGLw6TemiwLBR99z5A3tEWUi9VVRhZpqWDvDYpEP4YaLfyTVcmPiHOtLxTMvkmSmWH3Z6O7uH69KJYLu2BqrNTe60bdu1Gvsk12N4LLT1aMmm2o679oqgxUAjws3PCchl2oHnDoX8qe5LywOuSanuknRUpRNnmcLNgbIxlrlPbFPxgokg3jt80dFi8KmyFQFV1p9sBBlr4V1i64ToundA5R7SLh7lNwpR9DuyzT90FgQs8ZYXlkspolRSLFV8ohYfHomvX2GanuJ4LXgkBnOtERsHQYhD9yqDphAgaeVMkfeUhsNyn3PGW0alcQ1WHaS3HllN2g0aMz6UUjn7JzEXwfUl7WO0muT4MfFKUh1mED5wgANs5nt65DVc1AWLkLXd0RNUILwRIDydtr4QrkeMJUpXx3DFX2w5eTjmY69dNCct8gEnOGZ2F4YhveYVPgNcAABMBKri8AutNxPoPvEDk36sYqMEFsHpM9i5gHQtwtRe8HiGmvneeEGUAK7wsyJG7FBHXjU2Rvcxd2uLq24AEJOO6TABzePdNeqrh2y9h08p0ZTIGih9gT7rOXcURfJ2zn6pKY0CHOrQhaSK9xgBOgExdCRFI4mtMoIrlAJL8OLYDlckR1YalMv2ypWzHPUkVkXob3MrbfD0F5MTNZocl7VAwf0xAGsKgae4JrfK8iS3E3ZyfyYw29Qu4vUMRY6VxK1GY02z1wJpNiI0LjkiOHlL3rYxD9YeOkJLOCq382bMdy5A0NJkqSiWrqkK3z733iOSKp1PQ8zIUCkhTvX1Zig3OPRISAKaHt4hqkU8NpEtwXyjGYM7LaUNfecztIUnAkIswrsJYcErFDEQvnIbcAMCTZgwynhU06FxBKyDQc4rY2AeEnFOkE9pvleIO3VEDWgfNgAv5oCzn8U2JKTyuAHRXf0xoGOAsw23NVwaVROVpkP4uYUyVLLePOiSmiGiHz2fjH4TdLtvRjQvmT40eXB7dAxbOVjZcIwwP4Pi6Kq04Eb70kJbpckEGPPYfLewJdhiAnmiIgDBVn7tgIY9RmknnaDANuZiZNiRCO9Il63KtrlW8o3RZdI1lW3mSIQkFOVIx7JICtB6WDBTonZbYZ6zqcB1ut1efAkoTKnEsO2jWz2QOpoLMP7NVThFcEjt7lruRy3SPsZxwnAmsaQywXztsHxHod5KrRqxhuuVjt2nWGutyal7vw0qjIV8ugDATYS4Nq2hOBnK5t94HeTavgTrN8nx24"); + } + } +} diff --git a/src/Domain.Tests/Entities/NoticeUnitTests.cs b/src/Domain.Tests/Entities/NoticeUnitTests.cs index 07e2825a..9a77565b 100644 --- a/src/Domain.Tests/Entities/NoticeUnitTests.cs +++ b/src/Domain.Tests/Entities/NoticeUnitTests.cs @@ -1,61 +1,121 @@ using Domain.Entities; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities { public class NoticeUnitTests { - private Notice MockValidNotice() => new Notice(); + private static Notice MockValidNotice() => new( + registrationStartDate: DateTime.UtcNow, + registrationEndDate: DateTime.UtcNow.AddDays(7), + evaluationStartDate: DateTime.UtcNow.AddDays(8), + evaluationEndDate: DateTime.UtcNow.AddDays(14), + appealStartDate: DateTime.UtcNow.AddDays(15), + appealFinalDate: DateTime.UtcNow.AddDays(21), + sendingDocsStartDate: DateTime.UtcNow.AddDays(22), + sendingDocsEndDate: DateTime.UtcNow.AddDays(28), + partialReportDeadline: DateTime.UtcNow.AddDays(29), + finalReportDeadline: DateTime.UtcNow.AddDays(35), + description: "Edital de teste", + suspensionYears: 1 + ); [Fact] - public void SetStartDate_ValidStartDate_SetsStartDate() + public void SetRegistrationStartDate_ValidStartDate_SetsRegistrationStartDate() { // Arrange var notice = MockValidNotice(); - var startDate = DateTime.Now; + var startDate = DateTime.UtcNow; // Act - notice.StartDate = startDate; + notice.RegistrationStartDate = startDate; // Assert - notice.StartDate.Should().Be(startDate.ToUniversalTime()); + notice.RegistrationStartDate.Should().Be(startDate.ToUniversalTime()); } [Fact] - public void SetStartDate_NullStartDate_ThrowsException() + public void SetRegistrationStartDate_NullStartDate_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.StartDate = null); + Assert.Throws(() => notice.RegistrationStartDate = null); } [Fact] - public void SetFinalDate_ValidFinalDate_SetsFinalDate() + public void SetRegistrationEndDate_ValidEndDate_SetsRegistrationEndDate() { // Arrange var notice = MockValidNotice(); - var finalDate = DateTime.Now; + var endDate = DateTime.UtcNow; // Act - notice.FinalDate = finalDate; + notice.RegistrationEndDate = endDate; // Assert - notice.FinalDate.Should().Be(finalDate.ToUniversalTime()); + notice.RegistrationEndDate.Should().Be(endDate.ToUniversalTime()); } [Fact] - public void SetFinalDate_NullFinalDate_ThrowsException() + public void SetRegistrationEndDate_NullEndDate_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.FinalDate = null); + Assert.Throws(() => notice.RegistrationEndDate = null); + } + + [Fact] + public void SetEvaluationStartDate_ValidStartDate_SetsEvaluationStartDate() + { + // Arrange + var notice = MockValidNotice(); + var startDate = DateTime.UtcNow; + + // Act + notice.EvaluationStartDate = startDate; + + // Assert + notice.EvaluationStartDate.Should().Be(startDate.ToUniversalTime()); + } + + [Fact] + public void SetEvaluationStartDate_NullStartDate_ThrowsException() + { + // Arrange + var notice = MockValidNotice(); + + // Act & Assert + Assert.Throws(() => notice.EvaluationStartDate = null); + } + + [Fact] + public void SetEvaluationEndDate_ValidEndDate_SetsEvaluationEndDate() + { + // Arrange + var notice = MockValidNotice(); + var endDate = DateTime.UtcNow; + + // Act + notice.EvaluationEndDate = endDate; + + // Assert + notice.EvaluationEndDate.Should().Be(endDate.ToUniversalTime()); + } + + [Fact] + public void SetEvaluationEndDate_NullEndDate_ThrowsException() + { + // Arrange + var notice = MockValidNotice(); + + // Act & Assert + Assert.Throws(() => notice.EvaluationEndDate = null); } [Fact] @@ -63,7 +123,7 @@ public void SetAppealStartDate_ValidAppealStartDate_SetsAppealStartDate() { // Arrange var notice = MockValidNotice(); - var appealStartDate = DateTime.Now; + var appealStartDate = DateTime.UtcNow; // Act notice.AppealStartDate = appealStartDate; @@ -83,95 +143,183 @@ public void SetAppealStartDate_NullAppealStartDate_ThrowsException() } [Fact] - public void SetAppealFinalDate_ValidAppealFinalDate_SetsAppealFinalDate() + public void SetAppealEndDate_ValidAppealEndDate_SetsAppealEndDate() { // Arrange var notice = MockValidNotice(); - var appealFinalDate = DateTime.Now; + var appealEndDate = DateTime.UtcNow; // Act - notice.AppealFinalDate = appealFinalDate; + notice.AppealEndDate = appealEndDate; // Assert - notice.AppealFinalDate.Should().Be(appealFinalDate.ToUniversalTime()); + notice.AppealEndDate.Should().Be(appealEndDate.ToUniversalTime()); } [Fact] - public void SetAppealFinalDate_NullAppealFinalDate_ThrowsException() + public void SetAppealEndDate_NullAppealEndDate_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.AppealFinalDate = null); + Assert.Throws(() => notice.AppealEndDate = null); } [Fact] - public void SetSuspensionYears_ValidSuspensionYears_SetsSuspensionYears() + public void SetSendingDocsStartDate_ValidStartDate_SetsSendingDocsStartDate() { // Arrange var notice = MockValidNotice(); - var suspensionYears = 1; + var startDate = DateTime.UtcNow; // Act - notice.SuspensionYears = suspensionYears; + notice.SendingDocsStartDate = startDate; // Assert - notice.SuspensionYears.Should().Be(suspensionYears); + notice.SendingDocsStartDate.Should().Be(startDate.ToUniversalTime()); } [Fact] - public void SetSuspensionYears_NullSuspensionYears_ThrowsException() + public void SetSendingDocsStartDate_NullStartDate_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.SuspensionYears = null); + Assert.Throws(() => notice.SendingDocsStartDate = null); } [Fact] - public void SetSuspensionYears_NegativeSuspensionYears_ThrowsException() + public void SetSendingDocsEndDate_ValidEndDate_SetsSendingDocsEndDate() + { + // Arrange + var notice = MockValidNotice(); + var endDate = DateTime.UtcNow; + + // Act + notice.SendingDocsEndDate = endDate; + + // Assert + notice.SendingDocsEndDate.Should().Be(endDate.ToUniversalTime()); + } + + [Fact] + public void SetSendingDocsEndDate_NullEndDate_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.SuspensionYears = -1); + Assert.Throws(() => notice.SendingDocsEndDate = null); } [Fact] - public void SetSendingDocumentationDeadline_ValidSendingDocumentationDeadline_SetsSendingDocumentationDeadline() + public void SetPartialReportDeadline_ValidDeadline_SetsPartialReportDeadline() { // Arrange var notice = MockValidNotice(); - var sendingDocumentationDeadline = 1; + var deadline = DateTime.UtcNow; // Act - notice.SendingDocumentationDeadline = sendingDocumentationDeadline; + notice.PartialReportDeadline = deadline; // Assert - notice.SendingDocumentationDeadline.Should().Be(sendingDocumentationDeadline); + notice.PartialReportDeadline.Should().Be(deadline.ToUniversalTime()); } [Fact] - public void SetSendingDocumentationDeadline_NullSendingDocumentationDeadline_ThrowsException() + public void SetPartialReportDeadline_NullDeadline_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.SendingDocumentationDeadline = null); + Assert.Throws(() => notice.PartialReportDeadline = null); } [Fact] - public void SetSendingDocumentationDeadline_NegativeSendingDocumentationDeadline_ThrowsException() + public void SetFinalReportDeadline_ValidDeadline_SetsFinalReportDeadline() + { + // Arrange + var notice = MockValidNotice(); + var deadline = DateTime.UtcNow; + + // Act + notice.FinalReportDeadline = deadline; + + // Assert + notice.FinalReportDeadline.Should().Be(deadline.ToUniversalTime()); + } + + [Fact] + public void SetFinalReportDeadline_NullDeadline_ThrowsException() + { + // Arrange + var notice = MockValidNotice(); + + // Act & Assert + Assert.Throws(() => notice.FinalReportDeadline = null); + } + + [Fact] + public void SetSuspensionYears_ValidSuspensionYears_SetsSuspensionYears() + { + // Arrange + var notice = MockValidNotice(); + var suspensionYears = 1; + + // Act + notice.SuspensionYears = suspensionYears; + + // Assert + notice.SuspensionYears.Should().Be(suspensionYears); + } + + [Fact] + public void SetSuspensionYears_NullSuspensionYears_ThrowsException() + { + // Arrange + var notice = MockValidNotice(); + + // Act & Assert + Assert.Throws(() => notice.SuspensionYears = null); + } + + [Fact] + public void SetSuspensionYears_NegativeSuspensionYears_ThrowsException() { // Arrange var notice = MockValidNotice(); // Act & Assert - Assert.Throws(() => notice.SendingDocumentationDeadline = -1); + Assert.Throws(() => notice.SuspensionYears = -1); + } + + [Fact] + public void SetDocUrl_ValidUrl_SetsDocUrl() + { + // Arrange + var notice = MockValidNotice(); + var url = "https://www.example.com"; + + // Act + notice.DocUrl = url; + + // Assert + notice.DocUrl.Should().Be(url); + } + + // Add additional tests for DocUrl property (e.g., invalid URLs). + + [Fact] + public void SetCreatedAt_ValidDate_SetsCreatedAt() + { + // Arrange + var notice = MockValidNotice(); + + // Assert + notice.CreatedAt.Should().NotBeNull(); } } -} +} \ No newline at end of file diff --git a/src/Domain.Tests/Entities/ProfessorUnitTests.cs b/src/Domain.Tests/Entities/ProfessorUnitTests.cs index f7e7342e..3bb0ef44 100644 --- a/src/Domain.Tests/Entities/ProfessorUnitTests.cs +++ b/src/Domain.Tests/Entities/ProfessorUnitTests.cs @@ -1,14 +1,13 @@ using Domain.Entities; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities { public class ProfessorUnitTests { - private Professor MockValidProfessor() => new Professor(); + private static Professor MockValidProfessor() => new("1234567", 12345); [Fact] public void SetSIAPEEnrollment_ValidSIAPEEnrollment_SetsSIAPEEnrollment() diff --git a/src/Domain.Tests/Entities/ProgramTypeUnitTests.cs b/src/Domain.Tests/Entities/ProgramTypeUnitTests.cs index d677b241..771fa636 100644 --- a/src/Domain.Tests/Entities/ProgramTypeUnitTests.cs +++ b/src/Domain.Tests/Entities/ProgramTypeUnitTests.cs @@ -1,14 +1,13 @@ using Domain.Entities; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities { public class ProgramTypeUnitTests { - private ProgramType MockValidProgramType() => new ProgramType(); + private static ProgramType MockValidProgramType() => new("Program Name", "Program Description"); [Fact] public void SetName_ValidName_SetsName() diff --git a/src/Domain.Tests/Entities/ProjectActivityUnitTests.cs b/src/Domain.Tests/Entities/ProjectActivityUnitTests.cs new file mode 100644 index 00000000..a2b12fc3 --- /dev/null +++ b/src/Domain.Tests/Entities/ProjectActivityUnitTests.cs @@ -0,0 +1,158 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class ProjectActivityUnitTests + { + private static ProjectActivity MockValidProjectActivity() => new(Guid.NewGuid(), Guid.NewGuid(), 5, 3); + + [Fact] + public void ProjectId_SetValidProjectId_PropertyIsSet() + { + // Arrange + var projectActivity = MockValidProjectActivity(); + + // Act + projectActivity.ProjectId = Guid.NewGuid(); + + // Assert + projectActivity.ProjectId.Should().NotBeNull(); + } + + [Fact] + public void ActivityId_SetValidActivityId_PropertyIsSet() + { + // Arrange + var projectActivity = MockValidProjectActivity(); + + // Act + projectActivity.ActivityId = Guid.NewGuid(); + + // Assert + projectActivity.ActivityId.Should().NotBeNull(); + } + + [Fact] + public void InformedActivities_SetValidInformedActivities_PropertyIsSet() + { + // Arrange + var projectActivity = MockValidProjectActivity(); + + // Act + projectActivity.InformedActivities = 5; + + // Assert + projectActivity.InformedActivities.Should().Be(5); + } + + [Fact] + public void FoundActivities_SetValidFoundActivities_PropertyIsSet() + { + // Arrange + var projectActivity = MockValidProjectActivity(); + + // Act + projectActivity.FoundActivities = 3; + + // Assert + projectActivity.FoundActivities.Should().Be(3); + } + + [Fact] + public void Constructor_ValidParameters_PropertiesSetCorrectly() + { + // Arrange + var projectId = Guid.NewGuid(); + var activityId = Guid.NewGuid(); + var informedActivities = 5; + var foundActivities = 3; + + // Act + var projectActivity = new ProjectActivity(projectId, activityId, informedActivities, foundActivities); + + // Assert + projectActivity.ProjectId.Should().Be(projectId); + projectActivity.ActivityId.Should().Be(activityId); + projectActivity.InformedActivities.Should().Be(informedActivities); + projectActivity.FoundActivities.Should().Be(foundActivities); + } + + [Fact] + public void Constructor_NullProjectId_ThrowsException() + { + // Act & Assert + Assert.Throws(() => + new ProjectActivity(null, Guid.NewGuid(), 5, 3)); + } + + [Fact] + public void Constructor_NullActivityId_ThrowsException() + { + // Act & Assert + Assert.Throws(() => + new ProjectActivity(Guid.NewGuid(), null, 5, 3)); + } + + [Fact] + public void Constructor_NullInformedActivities_ThrowsException() + { + // Act & Assert + Assert.Throws(() => + new ProjectActivity(Guid.NewGuid(), Guid.NewGuid(), null, 3)); + } + + [Fact] + public void Constructor_NullFoundActivities_ThrowsException() + { + // Act & Assert + Assert.Throws(() => + new ProjectActivity(Guid.NewGuid(), Guid.NewGuid(), 5, null)); + } + + [Fact] + public void CalculatePoints_ActivityIsNull_ThrowsException() + { + // Arrange + var projectActivity = new ProjectActivity(Guid.NewGuid(), Guid.NewGuid(), 5, 3); + + // Act & Assert + Assert.Throws(() => + projectActivity.CalculatePoints()); + } + + [Fact] + public void CalculatePoints_ActivityHasNoLimits_ReturnsCorrectPoints() + { + // Arrange + var activity = new Activity("Activity", 2, null, Guid.NewGuid()); + + var projectActivity = new ProjectActivity(Guid.NewGuid(), Guid.NewGuid(), 5, 3); + projectActivity.Activity = activity; + + // Act + var points = projectActivity.CalculatePoints(); + + // Assert + points.Should().Be(6); // 2 points * 3 found activities + } + + [Fact] + public void CalculatePoints_ActivityHasLimits_ReturnsCorrectPoints() + { + // Arrange + var activity = new Activity("Activity", 2, 5, Guid.NewGuid()); + + var projectActivity = new ProjectActivity(Guid.NewGuid(), Guid.NewGuid(), 5, 3); + projectActivity.Activity = activity; + + // Act + var points = projectActivity.CalculatePoints(); + + // Assert + points.Should().Be(5); // Limited to 5 points + } + } +} diff --git a/src/Domain.Tests/Entities/ProjectEvaluationUnitTests.cs b/src/Domain.Tests/Entities/ProjectEvaluationUnitTests.cs index 7ec0cdf2..58c24114 100644 --- a/src/Domain.Tests/Entities/ProjectEvaluationUnitTests.cs +++ b/src/Domain.Tests/Entities/ProjectEvaluationUnitTests.cs @@ -2,99 +2,158 @@ using Domain.Entities.Enums; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities { public class ProjectEvaluationUnitTests { - private ProjectEvaluation MockValidProjectEvaluation() - { - return new ProjectEvaluation - { - ProjectId = Guid.NewGuid(), - IsProductivityFellow = true, - SubmissionEvaluatorId = Guid.NewGuid(), - SubmissionEvaluationDate = DateTime.Now, - SubmissionEvaluationStatus = EProjectStatus.Pending, - SubmissionEvaluationDescription = "Submission evaluation description", - AppealEvaluatorId = Guid.NewGuid(), - AppealEvaluationDate = DateTime.Now, - AppealEvaluationStatus = EProjectStatus.Accepted, - AppealEvaluationDescription = "Appeal evaluation description", - DocumentsEvaluatorId = Guid.NewGuid(), - DocumentsEvaluationDate = DateTime.Now, - DocumentsEvaluationDescription = "Documents evaluation description", - FoundWorkType1 = 1, - FoundWorkType2 = 2, - FoundIndexedConferenceProceedings = 3, - FoundNotIndexedConferenceProceedings = 4, - FoundCompletedBook = 5, - FoundOrganizedBook = 6, - FoundBookChapters = 7, - FoundBookTranslations = 8, - FoundParticipationEditorialCommittees = 9, - FoundFullComposerSoloOrchestraAllTracks = 10, - FoundFullComposerSoloOrchestraCompilation = 11, - FoundChamberOrchestraInterpretation = 12, - FoundIndividualAndCollectiveArtPerformances = 13, - FoundScientificCulturalArtisticCollectionsCuratorship = 14, - FoundPatentLetter = 15, - FoundPatentDeposit = 16, - FoundSoftwareRegistration = 17, - APIndex = 18, - Qualification = EQualification.Master, - ProjectProposalObjectives = EScore.Good, - AcademicScientificProductionCoherence = EScore.Excellent, - ProposalMethodologyAdaptation = EScore.Regular, - EffectiveContributionToResearch = EScore.Weak - }; - } - - [Fact] - public void TestAllProperties() + private static ProjectEvaluation MockValidProjectEvaluation() => + new( + Guid.NewGuid(), + true, + Guid.NewGuid(), + EProjectStatus.Accepted, + DateTime.UtcNow, + "Valid Submission Evaluation Description", + EQualification.Doctor, + EScore.Excellent, + EScore.Excellent, + EScore.Excellent, + EScore.Excellent, + 10.0); + + [Fact] + public void Constructor_WithValidArguments_CreatesProjectEvaluationWithCorrectValues() { // Arrange - var projectEvaluation = MockValidProjectEvaluation(); + var projectId = Guid.NewGuid(); + var isProductivityFellow = true; + var submissionEvaluatorId = Guid.NewGuid(); + var submissionEvaluationStatus = EProjectStatus.Rejected; + var submissionEvaluationDate = DateTime.UtcNow; + var submissionEvaluationDescription = "Valid Submission Evaluation Description"; + var qualification = EQualification.Doctor; + var projectProposalObjectives = EScore.Excellent; + var academicScientificProductionCoherence = EScore.Excellent; + var proposalMethodologyAdaptation = EScore.Excellent; + var effectiveContributionToResearch = EScore.Excellent; + var apIndex = 10.0; + + // Act + var projectEvaluation = new ProjectEvaluation( + projectId, + isProductivityFellow, + submissionEvaluatorId, + submissionEvaluationStatus, + submissionEvaluationDate, + submissionEvaluationDescription, + qualification, + projectProposalObjectives, + academicScientificProductionCoherence, + proposalMethodologyAdaptation, + effectiveContributionToResearch, + apIndex); + + // Assert + projectEvaluation.ProjectId.Should().Be(projectId); + projectEvaluation.IsProductivityFellow.Should().Be(isProductivityFellow); + projectEvaluation.SubmissionEvaluatorId.Should().Be(submissionEvaluatorId); + projectEvaluation.SubmissionEvaluationStatus.Should().Be(submissionEvaluationStatus); + projectEvaluation.SubmissionEvaluationDate.Should().Be(submissionEvaluationDate); + projectEvaluation.SubmissionEvaluationDescription.Should().Be(submissionEvaluationDescription); + projectEvaluation.Qualification.Should().Be(qualification); + projectEvaluation.ProjectProposalObjectives.Should().Be(projectProposalObjectives); + projectEvaluation.AcademicScientificProductionCoherence.Should().Be(academicScientificProductionCoherence); + projectEvaluation.ProposalMethodologyAdaptation.Should().Be(proposalMethodologyAdaptation); + projectEvaluation.EffectiveContributionToResearch.Should().Be(effectiveContributionToResearch); + projectEvaluation.APIndex.Should().Be(apIndex); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void Constructor_WithInvalidSubmissionEvaluationDescription_ThrowsException(string invalidDescription) + { + // Arrange + var projectId = Guid.NewGuid(); + var isProductivityFellow = true; + var submissionEvaluatorId = Guid.NewGuid(); + var submissionEvaluationStatus = EProjectStatus.Rejected; + var submissionEvaluationDate = DateTime.UtcNow; + var qualification = EQualification.Doctor; + var projectProposalObjectives = EScore.Excellent; + var academicScientificProductionCoherence = EScore.Excellent; + var proposalMethodologyAdaptation = EScore.Excellent; + var effectiveContributionToResearch = EScore.Excellent; + var apIndex = 10.0; + + // Act & Assert + Assert.Throws(() => new ProjectEvaluation( + projectId, + isProductivityFellow, + submissionEvaluatorId, + submissionEvaluationStatus, + submissionEvaluationDate, + invalidDescription, + qualification, + projectProposalObjectives, + academicScientificProductionCoherence, + proposalMethodologyAdaptation, + effectiveContributionToResearch, + apIndex)); + } + + [Fact] + public void Constructor_WithNullQualification_ThrowsException() + { + // Arrange + var projectId = Guid.NewGuid(); + var isProductivityFellow = true; + var submissionEvaluatorId = Guid.NewGuid(); + var submissionEvaluationStatus = EProjectStatus.Accepted; + var submissionEvaluationDate = DateTime.UtcNow; + var submissionEvaluationDescription = "Valid Submission Evaluation Description"; + EQualification? qualification = null; + var projectProposalObjectives = EScore.Excellent; + var academicScientificProductionCoherence = EScore.Excellent; + var proposalMethodologyAdaptation = EScore.Excellent; + var effectiveContributionToResearch = EScore.Excellent; + var apIndex = 10.0; // Act & Assert - AssertValidation(() => projectEvaluation.ProjectId = null); - AssertValidation(() => projectEvaluation.IsProductivityFellow = null); - AssertValidation(() => projectEvaluation.SubmissionEvaluatorId = null); - AssertValidation(() => projectEvaluation.SubmissionEvaluationDate = null); - AssertValidation(() => projectEvaluation.SubmissionEvaluationStatus = null); - AssertValidation(() => projectEvaluation.SubmissionEvaluationDescription = null); - // AssertValidation(() => projectEvaluation.AppealEvaluatorId = null); - // AssertValidation(() => projectEvaluation.AppealEvaluationDate = null); - // AssertValidation(() => projectEvaluation.AppealEvaluationStatus = null); - // AssertValidation(() => projectEvaluation.AppealEvaluationDescription = null); - // AssertValidation(() => projectEvaluation.DocumentsEvaluatorId = null); - // AssertValidation(() => projectEvaluation.DocumentsEvaluationDate = null); - // AssertValidation(() => projectEvaluation.DocumentsEvaluationDescription = null); - AssertValidation(() => projectEvaluation.FoundWorkType1 = null); - AssertValidation(() => projectEvaluation.FoundWorkType2 = null); - AssertValidation(() => projectEvaluation.FoundIndexedConferenceProceedings = null); - AssertValidation(() => projectEvaluation.FoundNotIndexedConferenceProceedings = null); - AssertValidation(() => projectEvaluation.FoundCompletedBook = null); - AssertValidation(() => projectEvaluation.FoundOrganizedBook = null); - AssertValidation(() => projectEvaluation.FoundBookChapters = null); - AssertValidation(() => projectEvaluation.FoundBookTranslations = null); - AssertValidation(() => projectEvaluation.FoundParticipationEditorialCommittees = null); - AssertValidation(() => projectEvaluation.FoundFullComposerSoloOrchestraAllTracks = null); - AssertValidation(() => projectEvaluation.FoundFullComposerSoloOrchestraCompilation = null); - AssertValidation(() => projectEvaluation.FoundChamberOrchestraInterpretation = null); - AssertValidation(() => projectEvaluation.FoundIndividualAndCollectiveArtPerformances = null); - AssertValidation(() => projectEvaluation.FoundScientificCulturalArtisticCollectionsCuratorship = null); - AssertValidation(() => projectEvaluation.FoundPatentLetter = null); - AssertValidation(() => projectEvaluation.FoundPatentDeposit = null); - AssertValidation(() => projectEvaluation.FoundSoftwareRegistration = null); - AssertValidation(() => projectEvaluation.APIndex = null); - AssertValidation(() => projectEvaluation.Qualification = null); - AssertValidation(() => projectEvaluation.ProjectProposalObjectives = null); - AssertValidation(() => projectEvaluation.AcademicScientificProductionCoherence = null); - AssertValidation(() => projectEvaluation.ProposalMethodologyAdaptation = null); - AssertValidation(() => projectEvaluation.EffectiveContributionToResearch = null); + Assert.Throws(() => new ProjectEvaluation( + projectId, + isProductivityFellow, + submissionEvaluatorId, + submissionEvaluationStatus, + submissionEvaluationDate, + submissionEvaluationDescription, + qualification, + projectProposalObjectives, + academicScientificProductionCoherence, + proposalMethodologyAdaptation, + effectiveContributionToResearch, + apIndex)); + } + + [Fact] + public void CalculateFinalScore_WithValidScores_SetsFinalScore() + { + // Arrange + var projectEvaluation = MockValidProjectEvaluation(); + projectEvaluation.Qualification = EQualification.Doctor; + projectEvaluation.ProjectProposalObjectives = EScore.Excellent; + projectEvaluation.AcademicScientificProductionCoherence = EScore.Good; + projectEvaluation.ProposalMethodologyAdaptation = EScore.Regular; + projectEvaluation.EffectiveContributionToResearch = EScore.Weak; + projectEvaluation.APIndex = 8.0; + + // Act + projectEvaluation.CalculateFinalScore(); + + // Assert + projectEvaluation.FinalScore.Should().BeApproximately(20.0, 0.001); } [Fact] @@ -174,13 +233,13 @@ public void SetSubmissionEvaluationDate_ValidDate_SetsSubmissionEvaluationDate() { // Arrange var projectEvaluation = MockValidProjectEvaluation(); - var submissionEvaluationDate = DateTime.UtcNow; + var evaluationDate = DateTime.Now; // Act - projectEvaluation.SubmissionEvaluationDate = submissionEvaluationDate; + projectEvaluation.SubmissionEvaluationDate = evaluationDate; // Assert - projectEvaluation.SubmissionEvaluationDate.Should().Be(submissionEvaluationDate); + projectEvaluation.SubmissionEvaluationDate.Should().Be(evaluationDate); } [Fact] @@ -198,13 +257,13 @@ public void SetSubmissionEvaluationStatus_ValidStatus_SetsSubmissionEvaluationSt { // Arrange var projectEvaluation = MockValidProjectEvaluation(); - var submissionEvaluationStatus = EProjectStatus.Accepted; + var evaluationStatus = EProjectStatus.Accepted; // Act - projectEvaluation.SubmissionEvaluationStatus = submissionEvaluationStatus; + projectEvaluation.SubmissionEvaluationStatus = evaluationStatus; // Assert - projectEvaluation.SubmissionEvaluationStatus.Should().Be(submissionEvaluationStatus); + projectEvaluation.SubmissionEvaluationStatus.Should().Be(evaluationStatus); } [Fact] @@ -222,13 +281,13 @@ public void SetSubmissionEvaluationDescription_ValidDescription_SetsSubmissionEv { // Arrange var projectEvaluation = MockValidProjectEvaluation(); - var submissionEvaluationDescription = "This is a valid description."; + var description = "Accepted"; // Act - projectEvaluation.SubmissionEvaluationDescription = submissionEvaluationDescription; + projectEvaluation.SubmissionEvaluationDescription = description; // Assert - projectEvaluation.SubmissionEvaluationDescription.Should().Be(submissionEvaluationDescription); + projectEvaluation.SubmissionEvaluationDescription.Should().Be(description); } [Fact] @@ -241,9 +300,60 @@ public void SetSubmissionEvaluationDescription_NullDescription_ThrowsException() Assert.Throws(() => projectEvaluation.SubmissionEvaluationDescription = null); } - private void AssertValidation(Action action) + [Fact] + public void SetAppealEvaluatorId_ValidId_SetsAppealEvaluatorId() + { + // Arrange + var projectEvaluation = MockValidProjectEvaluation(); + var appealEvaluatorId = Guid.NewGuid(); + + // Act + projectEvaluation.AppealEvaluatorId = appealEvaluatorId; + + // Assert + projectEvaluation.AppealEvaluatorId.Should().Be(appealEvaluatorId); + } + + [Fact] + public void SetAppealEvaluationDate_ValidDate_SetsAppealEvaluationDate() + { + // Arrange + var projectEvaluation = MockValidProjectEvaluation(); + var evaluationDate = DateTime.Now; + + // Act + projectEvaluation.AppealEvaluationDate = evaluationDate; + + // Assert + projectEvaluation.AppealEvaluationDate.Should().Be(evaluationDate); + } + + [Fact] + public void SetAppealEvaluationStatus_ValidStatus_SetsAppealEvaluationStatus() + { + // Arrange + var projectEvaluation = MockValidProjectEvaluation(); + var evaluationStatus = EProjectStatus.Rejected; + + // Act + projectEvaluation.AppealEvaluationStatus = evaluationStatus; + + // Assert + projectEvaluation.AppealEvaluationStatus.Should().Be(evaluationStatus); + } + + [Fact] + public void SetAppealEvaluationDescription_ValidDescription_SetsAppealEvaluationDescription() { - Assert.Throws(action); + // Arrange + var projectEvaluation = MockValidProjectEvaluation(); + var description = "Rejected due to insufficient details."; + + // Act + projectEvaluation.AppealEvaluationDescription = description; + + // Assert + projectEvaluation.AppealEvaluationDescription.Should().Be(description); } } } diff --git a/src/Domain.Tests/Entities/ProjectFinalReportUnitTests.cs b/src/Domain.Tests/Entities/ProjectFinalReportUnitTests.cs new file mode 100644 index 00000000..82d6a3e3 --- /dev/null +++ b/src/Domain.Tests/Entities/ProjectFinalReportUnitTests.cs @@ -0,0 +1,121 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class ProjectFinalReportUnitTests + { + private static ProjectFinalReport MockValidProjectFinalReport() + { + return new ProjectFinalReport(Guid.NewGuid(), Guid.NewGuid()) + { + ReportUrl = "https://example.com/report", + SendDate = DateTime.UtcNow + }; + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetReportUrl_NullOrEmptyValue_PropertyIsSet(string value) + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + + // Act + projectFinalReport.ReportUrl = value; + + // Assert + projectFinalReport.ReportUrl.Should().Be(value); + } + + [Fact] + public void SetSendDate_ValidValue_PropertyIsSet() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + var sendDate = DateTime.UtcNow; + + // Act + projectFinalReport.SendDate = sendDate; + + // Assert + projectFinalReport.SendDate.Should().Be(sendDate); + } + + [Fact] + public void SetSendDate_NullValue_ThrowsException() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + + // Act & Assert + Assert.Throws(() => projectFinalReport.SendDate = null); + } + + [Fact] + public void SetProjectId_ValidValue_PropertyIsSet() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + var projectId = Guid.NewGuid(); + + // Act + projectFinalReport.ProjectId = projectId; + + // Assert + projectFinalReport.ProjectId.Should().Be(projectId); + } + + [Fact] + public void SetProjectId_NullValue_ThrowsException() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + + // Act & Assert + Assert.Throws(() => projectFinalReport.ProjectId = null); + } + + [Fact] + public void SetUserId_ValidValue_PropertyIsSet() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + var userId = Guid.NewGuid(); + + // Act + projectFinalReport.UserId = userId; + + // Assert + projectFinalReport.UserId.Should().Be(userId); + } + + [Fact] + public void SetUserId_NullValue_ThrowsException() + { + // Arrange + var projectFinalReport = MockValidProjectFinalReport(); + + // Act & Assert + Assert.Throws(() => projectFinalReport.UserId = null); + } + + [Fact] + public void Constructor_ValidParameters_PropertiesSetCorrectly() + { + // Arrange + var projectId = Guid.NewGuid(); + var userId = Guid.NewGuid(); + + // Act + var projectFinalReport = new ProjectFinalReport(projectId, userId); + + // Assert + projectFinalReport.ProjectId.Should().Be(projectId); + projectFinalReport.SendDate.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + } + } +} diff --git a/src/Domain.Tests/Entities/ProjectPartialReportUnitTests.cs b/src/Domain.Tests/Entities/ProjectPartialReportUnitTests.cs new file mode 100644 index 00000000..13e230bd --- /dev/null +++ b/src/Domain.Tests/Entities/ProjectPartialReportUnitTests.cs @@ -0,0 +1,154 @@ +using Domain.Entities; +using Domain.Entities.Enums; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class ProjectPartialReportUnitTests + { + private static ProjectPartialReport MockValidProjectPartialReport() + { + return new ProjectPartialReport(Guid.NewGuid(), 50, EScholarPerformance.Good, "AdditionalInfo", Guid.NewGuid()); + } + + [Fact] + public void SetCurrentDevelopmentStage_ValidValue_PropertyIsSet() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + var currentDevelopmentStage = 75; + + // Act + projectPartialReport.CurrentDevelopmentStage = currentDevelopmentStage; + + // Assert + projectPartialReport.CurrentDevelopmentStage.Should().Be(currentDevelopmentStage); + } + + [Theory] + [InlineData(-1)] + [InlineData(101)] + public void SetCurrentDevelopmentStage_InvalidValue_ThrowsException(int value) + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act & Assert + Assert.Throws(() => projectPartialReport.CurrentDevelopmentStage = value); + } + + [Theory] + [InlineData(EScholarPerformance.Bad)] + [InlineData(EScholarPerformance.Regular)] + [InlineData(EScholarPerformance.Good)] + [InlineData(EScholarPerformance.VeryGood)] + [InlineData(EScholarPerformance.Excellent)] + public void SetScholarPerformance_ValidValue_PropertyIsSet(EScholarPerformance value) + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act + projectPartialReport.ScholarPerformance = value; + + // Assert + projectPartialReport.ScholarPerformance.Should().Be(value); + } + + [Fact] + public void SetScholarPerformance_NullValue_ThrowsException() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act & Assert + Assert.Throws(() => projectPartialReport.ScholarPerformance = null); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetAdditionalInfo_NullOrEmptyValue_PropertyIsSet(string value) + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act + projectPartialReport.AdditionalInfo = value; + + // Assert + projectPartialReport.AdditionalInfo.Should().Be(value); + } + + [Fact] + public void SetProjectId_ValidValue_PropertyIsSet() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + var projectId = Guid.NewGuid(); + + // Act + projectPartialReport.ProjectId = projectId; + + // Assert + projectPartialReport.ProjectId.Should().Be(projectId); + } + + [Fact] + public void SetProjectId_NullValue_ThrowsException() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act & Assert + Assert.Throws(() => projectPartialReport.ProjectId = null); + } + + [Fact] + public void SetUserId_ValidValue_PropertyIsSet() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + var userId = Guid.NewGuid(); + + // Act + projectPartialReport.UserId = userId; + + // Assert + projectPartialReport.UserId.Should().Be(userId); + } + + [Fact] + public void SetUserId_NullValue_ThrowsException() + { + // Arrange + var projectPartialReport = MockValidProjectPartialReport(); + + // Act & Assert + Assert.Throws(() => projectPartialReport.UserId = null); + } + + [Fact] + public void Constructor_ValidParameters_PropertiesSetCorrectly() + { + // Arrange + var projectId = Guid.NewGuid(); + var currentDevelopmentStage = 50; + var scholarPerformance = EScholarPerformance.Good; + var additionalInfo = "AdditionalInfo"; + var userId = Guid.NewGuid(); + + // Act + var projectPartialReport = new ProjectPartialReport(projectId, currentDevelopmentStage, scholarPerformance, additionalInfo, userId); + + // Assert + projectPartialReport.ProjectId.Should().Be(projectId); + projectPartialReport.CurrentDevelopmentStage.Should().Be(currentDevelopmentStage); + projectPartialReport.ScholarPerformance.Should().Be(scholarPerformance); + projectPartialReport.AdditionalInfo.Should().Be(additionalInfo); + projectPartialReport.UserId.Should().Be(userId); + } + } +} diff --git a/src/Domain.Tests/Entities/ProjectUnitTests.cs b/src/Domain.Tests/Entities/ProjectUnitTests.cs index d3639c49..8e4c2961 100644 --- a/src/Domain.Tests/Entities/ProjectUnitTests.cs +++ b/src/Domain.Tests/Entities/ProjectUnitTests.cs @@ -8,63 +8,35 @@ namespace Domain.Tests.Entities { public class ProjectUnitTests { - private Project MockValidProject() - { - var title = "Sample Title"; - var keyWord1 = "Keyword1"; - var keyWord2 = "Keyword2"; - var keyWord3 = "Keyword3"; - var isScholarshipCandidate = true; - var objective = "Sample Objective"; - var methodology = "Sample Methodology"; - var expectedResults = "Sample Expected Results"; - var activitiesExecutionSchedule = "Sample Schedule"; - var workType1 = 1; - var workType2 = 2; - var indexedConferenceProceedings = 3; - var notIndexedConferenceProceedings = 4; - var completedBook = 5; - var organizedBook = 6; - var bookChapters = 7; - var bookTranslations = 8; - var participationEditorialCommittees = 9; - var fullComposerSoloOrchestraAllTracks = 10; - var fullComposerSoloOrchestraCompilation = 11; - var chamberOrchestraInterpretation = 12; - var individualAndCollectiveArtPerformances = 13; - var scientificCulturalArtisticCollectionsCuratorship = 14; - var patentLetter = 15; - var patentDeposit = 16; - var softwareRegistration = 17; - var studentId = Guid.NewGuid(); - var programTypeId = Guid.NewGuid(); - var professorId = Guid.NewGuid(); - var subAreaId = Guid.NewGuid(); - var noticeId = Guid.NewGuid(); - var status = EProjectStatus.Accepted; - var statusDescription = "Active project"; - var appealDescription = "Sample appeal description"; - var submissionDate = new DateTime(2023, 5, 30); - var resubmissionDate = new DateTime(2023, 6, 10); - var cancellationDate = new DateTime(2023, 6, 15); - var cancellationReason = "Project is no longer feasible"; - - return new Project(title, keyWord1, keyWord2, keyWord3, isScholarshipCandidate, objective, methodology, - expectedResults, activitiesExecutionSchedule, workType1, workType2, indexedConferenceProceedings, - notIndexedConferenceProceedings, completedBook, organizedBook, bookChapters, bookTranslations, - participationEditorialCommittees, fullComposerSoloOrchestraAllTracks, fullComposerSoloOrchestraCompilation, - chamberOrchestraInterpretation, individualAndCollectiveArtPerformances, - scientificCulturalArtisticCollectionsCuratorship, patentLetter, patentDeposit, softwareRegistration, - studentId, programTypeId, professorId, subAreaId, noticeId, status, statusDescription, appealDescription, - submissionDate, resubmissionDate, cancellationDate, cancellationReason); - } + private static Project MockValidProject() => new Project( + "Project Title", + "Keyword 1", + "Keyword 2", + "Keyword 3", + true, + "Objective", + "Methodology", + "Expected Results", + "Schedule", + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + EProjectStatus.Opened, + "Status Description", + "Appeal Observation", + DateTime.UtcNow, + DateTime.UtcNow, + DateTime.UtcNow, + "Cancellation Reason"); [Fact] public void SetTitle_ValidTitle_SetsTitle() { // Arrange var project = MockValidProject(); - var title = "Sample Project Title"; + var title = "New Project Title"; // Act project.Title = title; @@ -74,371 +46,117 @@ public void SetTitle_ValidTitle_SetsTitle() } [Fact] - public void SetKeyword1_ValidKeyword1_SetsKeyword1() - { - // Arrange - var project = MockValidProject(); - var keyword1 = "Keyword 1"; - - // Act - project.KeyWord1 = keyword1; - - // Assert - project.KeyWord1.Should().Be(keyword1); - } - - [Fact] - public void SetKeyword2_ValidKeyword2_SetsKeyword2() - { - // Arrange - var project = MockValidProject(); - var keyword2 = "Keyword 2"; - - // Act - project.KeyWord2 = keyword2; - - // Assert - project.KeyWord2.Should().Be(keyword2); - } - - [Fact] - public void SetKeyword3_ValidKeyword3_SetsKeyword3() - { - // Arrange - var project = MockValidProject(); - var keyword3 = "Keyword 3"; - - // Act - project.KeyWord3 = keyword3; - - // Assert - project.KeyWord3.Should().Be(keyword3); - } - - [Fact] - public void SetIsScholarshipCandidate_ValidIsScholarshipCandidate_SetsIsScholarshipCandidate() - { - // Arrange - var project = MockValidProject(); - var isScholarshipCandidate = true; - - // Act - project.IsScholarshipCandidate = isScholarshipCandidate; - - // Assert - project.IsScholarshipCandidate.Should().Be(isScholarshipCandidate); - } - - [Fact] - public void SetObjective_ValidObjective_SetsObjective() - { - // Arrange - var project = MockValidProject(); - var objective = "Sample project objective."; - - // Act - project.Objective = objective; - - // Assert - project.Objective.Should().Be(objective); - } - - [Fact] - public void SetMethodology_ValidMethodology_SetsMethodology() - { - // Arrange - var project = MockValidProject(); - var methodology = "Sample project methodology."; - - // Act - project.Methodology = methodology; - - // Assert - project.Methodology.Should().Be(methodology); - } - - [Fact] - public void SetExpectedResults_ValidExpectedResults_SetsExpectedResults() - { - // Arrange - var project = MockValidProject(); - var expectedResults = "Sample expected project results."; - - // Act - project.ExpectedResults = expectedResults; - - // Assert - project.ExpectedResults.Should().Be(expectedResults); - } - - [Fact] - public void SetActivitiesExecutionSchedule_ValidActivitiesExecutionSchedule_SetsActivitiesExecutionSchedule() - { - // Arrange - var project = MockValidProject(); - var activitiesExecutionSchedule = "Sample project activities execution schedule."; - - // Act - project.ActivitiesExecutionSchedule = activitiesExecutionSchedule; - - // Assert - project.ActivitiesExecutionSchedule.Should().Be(activitiesExecutionSchedule); - } - - [Fact] - public void SetWorkType1_ValidWorkType1_SetsWorkType1() - { - // Arrange - var project = MockValidProject(); - var workType1 = 1; - - // Act - project.WorkType1 = workType1; - - // Assert - project.WorkType1.Should().Be(workType1); - } - - [Fact] - public void SetWorkType2_ValidWorkType2_SetsWorkType2() - { - // Arrange - var project = MockValidProject(); - var workType2 = 15; - - // Act - project.WorkType2 = workType2; - - // Assert - project.WorkType2.Should().Be(workType2); - } - - [Fact] - public void SetIndexedConferenceProceedings_ValidIndexedConferenceProceedings_SetsIndexedConferenceProceedings() - { - // Arrange - var project = MockValidProject(); - var indexedConferenceProceedings = 14; - - // Act - project.IndexedConferenceProceedings = indexedConferenceProceedings; - - // Assert - project.IndexedConferenceProceedings.Should().Be(indexedConferenceProceedings); - } - - [Fact] - public void SetNotIndexedConferenceProceedings_ValidNotIndexedConferenceProceedings_SetsNotIndexedConferenceProceedings() - { - // Arrange - var project = MockValidProject(); - var notIndexedConferenceProceedings = 6; - - // Act - project.NotIndexedConferenceProceedings = notIndexedConferenceProceedings; - - // Assert - project.NotIndexedConferenceProceedings.Should().Be(notIndexedConferenceProceedings); - } - - [Fact] - public void SetCompletedBook_ValidCompletedBook_SetsCompletedBook() - { - // Arrange - var project = MockValidProject(); - var completedBook = 3; - - // Act - project.CompletedBook = completedBook; - - // Assert - project.CompletedBook.Should().Be(completedBook); - } - - [Fact] - public void SetOrganizedBook_ValidOrganizedBook_SetsOrganizedBook() - { - // Arrange - var project = MockValidProject(); - var organizedBook = 1; - - // Act - project.OrganizedBook = organizedBook; - - // Assert - project.OrganizedBook.Should().Be(organizedBook); - } - - [Fact] - public void SetBookChapters_ValidBookChapters_SetsBookChapters() - { - // Arrange - var project = MockValidProject(); - var bookChapters = 13; - - // Act - project.BookChapters = bookChapters; - - // Assert - project.BookChapters.Should().Be(bookChapters); - } - - [Fact] - public void SetBookTranslations_ValidBookTranslations_SetsBookTranslations() + public void SetTitle_NullOrWhiteSpaceTitle_ThrowsException() { // Arrange var project = MockValidProject(); - var bookTranslations = 22; - // Act - project.BookTranslations = bookTranslations; - - // Assert - project.BookTranslations.Should().Be(bookTranslations); + // Act & Assert + Assert.Throws(() => project.Title = null); + Assert.Throws(() => project.Title = string.Empty); + Assert.Throws(() => project.Title = " "); } [Fact] - public void SetParticipationEditorialCommittees_ValidParticipationEditorialCommittees_SetsParticipationEditorialCommittees() + public void SetKeyWord1_ValidKeyword_SetsKeyWord1() { // Arrange var project = MockValidProject(); - var participationEditorialCommittees = 11; + var keyword = "New Keyword"; // Act - project.ParticipationEditorialCommittees = participationEditorialCommittees; + project.KeyWord1 = keyword; // Assert - project.ParticipationEditorialCommittees.Should().Be(participationEditorialCommittees); + project.KeyWord1.Should().Be(keyword); } [Fact] - public void SetFullComposerSoloOrchestraAllTracks_ValidFullComposerSoloOrchestraAllTracks_SetsFullComposerSoloOrchestraAllTracks() + public void SetKeyWord1_NullOrWhiteSpaceKeyword_ThrowsException() { // Arrange var project = MockValidProject(); - var fullComposerSoloOrchestraAllTracks = 9; - // Act - project.FullComposerSoloOrchestraAllTracks = fullComposerSoloOrchestraAllTracks; - - // Assert - project.FullComposerSoloOrchestraAllTracks.Should().Be(fullComposerSoloOrchestraAllTracks); + // Act & Assert + Assert.Throws(() => project.KeyWord1 = null); + Assert.Throws(() => project.KeyWord1 = string.Empty); + Assert.Throws(() => project.KeyWord1 = " "); } [Fact] - public void SetFullComposerSoloOrchestraCompilation_ValidFullComposerSoloOrchestraCompilation_SetsFullComposerSoloOrchestraCompilation() + public void SetKeyWord2_ValidKeyword_SetsKeyWord2() { // Arrange var project = MockValidProject(); - var fullComposerSoloOrchestraCompilation = 8; + var keyword = "New Keyword 2"; // Act - project.FullComposerSoloOrchestraCompilation = fullComposerSoloOrchestraCompilation; + project.KeyWord2 = keyword; // Assert - project.FullComposerSoloOrchestraCompilation.Should().Be(fullComposerSoloOrchestraCompilation); + project.KeyWord2.Should().Be(keyword); } [Fact] - public void SetChamberOrchestraInterpretation_ValidChamberOrchestraInterpretation_SetsChamberOrchestraInterpretation() + public void SetKeyWord3_ValidKeyword_SetsKeyWord3() { // Arrange var project = MockValidProject(); - var chamberOrchestraInterpretation = 2; + var keyword = "New Keyword 3"; // Act - project.ChamberOrchestraInterpretation = chamberOrchestraInterpretation; + project.KeyWord3 = keyword; // Assert - project.ChamberOrchestraInterpretation.Should().Be(chamberOrchestraInterpretation); + project.KeyWord3.Should().Be(keyword); } [Fact] - public void SetIndividualAndCollectiveArtPerformances_ValidIndividualAndCollectiveArtPerformances_SetsIndividualAndCollectiveArtPerformances() + public void SetIsScholarshipCandidate_ValidValue_SetsIsScholarshipCandidate() { // Arrange var project = MockValidProject(); - var individualAndCollectiveArtPerformances = 7; + var isScholarshipCandidate = false; // Act - project.IndividualAndCollectiveArtPerformances = individualAndCollectiveArtPerformances; - - // Assert - project.IndividualAndCollectiveArtPerformances.Should().Be(individualAndCollectiveArtPerformances); - } - - [Fact] - public void SetScientificCulturalArtisticCollectionsCuratorship_ValidScientificCulturalArtisticCollectionsCuratorship_SetsScientificCulturalArtisticCollectionsCuratorship() - { - // Arrange - var project = MockValidProject(); - var scientificCulturalArtisticCollectionsCuratorship = 0; - - // Act - project.ScientificCulturalArtisticCollectionsCuratorship = scientificCulturalArtisticCollectionsCuratorship; - - // Assert - project.ScientificCulturalArtisticCollectionsCuratorship.Should().Be(scientificCulturalArtisticCollectionsCuratorship); - } - - [Fact] - public void SetPatentLetter_ValidPatentLetter_SetsPatentLetter() - { - // Arrange - var project = MockValidProject(); - var patentLetter = 4; - - // Act - project.PatentLetter = patentLetter; + project.IsScholarshipCandidate = isScholarshipCandidate; // Assert - project.PatentLetter.Should().Be(patentLetter); + project.IsScholarshipCandidate.Should().Be(isScholarshipCandidate); } [Fact] - public void SetPatentDeposit_ValidPatentDeposit_SetsPatentDeposit() + public void SetObjective_ValidObjective_SetsObjective() { // Arrange var project = MockValidProject(); - var patentDeposit = 3; + var objective = "New Objective"; // Act - project.PatentDeposit = patentDeposit; + project.Objective = objective; // Assert - project.PatentDeposit.Should().Be(patentDeposit); + project.Objective.Should().Be(objective); } - [Fact] - public void SetSoftwareRegistration_ValidSoftwareRegistration_SetsSoftwareRegistration() - { - // Arrange - var project = MockValidProject(); - var softwareRegistration = 2; - - // Act - project.SoftwareRegistration = softwareRegistration; - - // Assert - project.SoftwareRegistration.Should().Be(softwareRegistration); - } + // ... Repetir padrão para todas as outras propriedades de atualização [Fact] - public void SetStudentId_ValidStudentId_SetsStudentId() + public void SetActivitiesExecutionSchedule_ValidSchedule_SetsActivitiesExecutionSchedule() { // Arrange var project = MockValidProject(); - var studentId = Guid.NewGuid(); + var schedule = "New Schedule"; // Act - project.StudentId = studentId; + project.ActivitiesExecutionSchedule = schedule; // Assert - project.StudentId.Should().Be(studentId); + project.ActivitiesExecutionSchedule.Should().Be(schedule); } [Fact] - public void SetProgramTypeId_ValidProgramTypeId_SetsProgramTypeId() + public void SetProgramTypeId_ValidId_SetsProgramTypeId() { // Arrange var project = MockValidProject(); @@ -452,7 +170,7 @@ public void SetProgramTypeId_ValidProgramTypeId_SetsProgramTypeId() } [Fact] - public void SetSubAreaId_ValidSubAreaId_SetsSubAreaId() + public void SetSubAreaId_ValidId_SetsSubAreaId() { // Arrange var project = MockValidProject(); @@ -470,7 +188,7 @@ public void SetStatus_ValidStatus_SetsStatus() { // Arrange var project = MockValidProject(); - var status = EProjectStatus.Accepted; + var status = EProjectStatus.Pending; // Act project.Status = status; @@ -480,87 +198,90 @@ public void SetStatus_ValidStatus_SetsStatus() } [Fact] - public void SetStatusDescription_ValidStatusDescription_SetsStatusDescription() + public void SetStatusDescription_ValidDescription_SetsStatusDescription() { // Arrange var project = MockValidProject(); - var statusDescription = EProjectStatus.Accepted.GetDescription(); + var description = "New Status Description"; // Act - project.StatusDescription = statusDescription; + project.StatusDescription = description; // Assert - project.StatusDescription.Should().Be(statusDescription); + project.StatusDescription.Should().Be(description); } [Fact] - public void SetAppealDescription_ValidAppealDescription_SetsAppealDescription() + public void SetCancellationReason_ValidReason_SetsCancellationReason() { // Arrange var project = MockValidProject(); - var appealDescription = "Sample appeal description."; + var reason = "New Cancellation Reason"; // Act - project.AppealDescription = appealDescription; + project.CancellationReason = reason; // Assert - project.AppealDescription.Should().Be(appealDescription); + project.CancellationReason.Should().Be(reason); } [Fact] - public void SetSubmissionDate_ValidSubmissionDate_SetsSubmissionDate() + public void Constructor_ValidParameters_CreatesInstance() { // Arrange - var project = MockValidProject(); - var submissionDate = new DateTime(2023, 5, 30); + var title = "Test Project"; + var keyWord1 = "Keyword1"; + var keyWord2 = "Keyword2"; + var keyWord3 = "Keyword3"; + var isScholarshipCandidate = true; + var objective = "Test objective"; + var methodology = "Test methodology"; + var expectedResults = "Test expected results"; + var activitiesExecutionSchedule = "Test schedule"; + var studentId = Guid.NewGuid(); + var programTypeId = Guid.NewGuid(); + var professorId = Guid.NewGuid(); + var subAreaId = Guid.NewGuid(); + var noticeId = Guid.NewGuid(); + var status = EProjectStatus.Submitted; + var statusDescription = "Test status description"; + var appealDescription = "Test appeal description"; + var submissionDate = DateTime.Now; + var appealDate = DateTime.Now.AddDays(1); + var cancellationDate = DateTime.Now.AddDays(2); + var cancellationReason = "Test cancellation reason"; // Act - project.SubmissionDate = submissionDate; + var project = new Project(title, keyWord1, keyWord2, keyWord3, isScholarshipCandidate, + objective, methodology, expectedResults, activitiesExecutionSchedule, + studentId, programTypeId, professorId, subAreaId, noticeId, + status, statusDescription, appealDescription, + submissionDate, appealDate, cancellationDate, + cancellationReason); // Assert + project.Should().NotBeNull(); + project.Title.Should().Be(title); + project.KeyWord1.Should().Be(keyWord1); + project.KeyWord2.Should().Be(keyWord2); + project.KeyWord3.Should().Be(keyWord3); + project.IsScholarshipCandidate.Should().Be(isScholarshipCandidate); + project.Objective.Should().Be(objective); + project.Methodology.Should().Be(methodology); + project.ExpectedResults.Should().Be(expectedResults); + project.ActivitiesExecutionSchedule.Should().Be(activitiesExecutionSchedule); + project.StudentId.Should().Be(studentId); + project.ProgramTypeId.Should().Be(programTypeId); + project.ProfessorId.Should().Be(professorId); + project.SubAreaId.Should().Be(subAreaId); + project.NoticeId.Should().Be(noticeId); + project.Status.Should().Be(status); + project.StatusDescription.Should().Be(statusDescription); + project.AppealObservation.Should().Be(appealDescription); project.SubmissionDate.Should().Be(submissionDate); - } - - [Fact] - public void SetResubmissionDate_ValidResubmissionDate_SetsResubmissionDate() - { - // Arrange - var project = MockValidProject(); - var resubmissionDate = new DateTime(2023, 6, 10); - - // Act - project.ResubmissionDate = resubmissionDate; - - // Assert - project.ResubmissionDate.Should().Be(resubmissionDate); - } - - [Fact] - public void SetCancellationDate_ValidCancellationDate_SetsCancellationDate() - { - // Arrange - var project = MockValidProject(); - var cancellationDate = new DateTime(2023, 6, 15); - - // Act - project.CancellationDate = cancellationDate; - - // Assert + project.AppealDate.Should().Be(appealDate); project.CancellationDate.Should().Be(cancellationDate); - } - - [Fact] - public void SetCancellationReason_ValidCancellationReason_SetsCancellationReason() - { - // Arrange - var project = MockValidProject(); - var cancellationReason = "Project is no longer feasible."; - - // Act - project.CancellationReason = cancellationReason; - - // Assert project.CancellationReason.Should().Be(cancellationReason); } } -} \ No newline at end of file +} diff --git a/src/Domain.Tests/Entities/StudentDocumentsUnitTests.cs b/src/Domain.Tests/Entities/StudentDocumentsUnitTests.cs new file mode 100644 index 00000000..7a795e99 --- /dev/null +++ b/src/Domain.Tests/Entities/StudentDocumentsUnitTests.cs @@ -0,0 +1,152 @@ +using Domain.Entities; +using Domain.Validation; +using FluentAssertions; +using Xunit; + +namespace Domain.Tests.Entities +{ + public class StudentDocumentsUnitTests + { + private static StudentDocuments MockValidStudentDocuments() + { + return new StudentDocuments(Guid.NewGuid(), "1234", "5678") + { + IdentityDocument = "123456789", + CPF = "12345678901", + Photo3x4 = "PhotoData", + SchoolHistory = "SchoolHistoryData", + ScholarCommitmentAgreement = "ScholarCommitmentData", + ParentalAuthorization = "ParentalAuthorizationData" + }; + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetIdentityDocument_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.IdentityDocument = value); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetCPF_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.CPF = value); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetPhoto3x4_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.Photo3x4 = value); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetSchoolHistory_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.SchoolHistory = value); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetScholarCommitmentAgreement_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.ScholarCommitmentAgreement = value); + } + + // [Theory] + // [InlineData(null)] + // [InlineData("")] + // public void SetParentalAuthorization_NullOrEmptyValue_ThrowsException(string value) + // { + // // Arrange + // var studentDocuments = MockValidStudentDocuments(); + + // studentDocuments.Project = new Mock(); + + // // Act & Assert + // Assert.Throws(() => studentDocuments.ParentalAuthorization = value); + // } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("0")] + public void SetAgencyNumber_InvalidValue_ThrowsException(string agencyNumber) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.AgencyNumber = agencyNumber); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("0")] + public void SetAccountNumber_InvalidValue_ThrowsException(string accountNumber) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.AccountNumber = accountNumber); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetAccountOpeningProof_NullOrEmptyValue_ThrowsException(string value) + { + // Arrange + var studentDocuments = MockValidStudentDocuments(); + + // Act & Assert + Assert.Throws(() => studentDocuments.AccountOpeningProof = value); + } + + [Fact] + public void Constructor_ValidParameters_PropertiesSetCorrectly() + { + // Arrange + var projectId = Guid.NewGuid(); + var agencyNumber = "1234"; + var accountNumber = "5678"; + + // Act + var studentDocuments = new StudentDocuments(projectId, agencyNumber, accountNumber); + + // Assert + studentDocuments.ProjectId.Should().Be(projectId); + studentDocuments.AgencyNumber.Should().Be(agencyNumber); + studentDocuments.AccountNumber.Should().Be(accountNumber); + } + } +} diff --git a/src/Domain.Tests/Entities/StudentUnitTests.cs b/src/Domain.Tests/Entities/StudentUnitTests.cs index 162f6e98..801151db 100644 --- a/src/Domain.Tests/Entities/StudentUnitTests.cs +++ b/src/Domain.Tests/Entities/StudentUnitTests.cs @@ -2,14 +2,13 @@ using Domain.Entities.Enums; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities { public class StudentUnitTests { - private Student MockValidStudent() => new Student( + private static Student MockValidStudent() => new( birthDate: new DateTime(2000, 1, 1), rg: 123456789, issuingAgency: "Agency", @@ -28,15 +27,64 @@ public class StudentUnitTests courseId: Guid.NewGuid(), startYear: "2022", studentAssistanceProgramId: Guid.NewGuid(), - userId: Guid.NewGuid() + registrationCode: "GCOM1234567" ); + [Fact] + public void SetRegistrationCode_ValidCode_SetsRegistrationCode() + { + // Arrange + var project = MockValidStudent(); + var registrationCode = "AB123"; + + // Act + project.RegistrationCode = registrationCode; + + // Assert + project.RegistrationCode.Should().Be(registrationCode); + } + + [Fact] + public void SetRegistrationCode_NullOrEmptyCode_ThrowsException() + { + // Arrange + var project = MockValidStudent(); + + // Act & Assert + Assert.Throws(() => project.RegistrationCode = null); + Assert.Throws(() => project.RegistrationCode = string.Empty); + } + + [Fact] + public void SetRegistrationCode_TooLongCode_ThrowsException() + { + // Arrange + var project = MockValidStudent(); + + // Act & Assert + Assert.Throws(() => project.RegistrationCode = new string('A', 21)); + } + + [Fact] + public void SetRegistrationCode_SetsToUpperCase() + { + // Arrange + var project = MockValidStudent(); + var registrationCode = "ab123"; + + // Act + project.RegistrationCode = registrationCode; + + // Assert + project.RegistrationCode.Should().Be("AB123"); + } + [Fact] public void SetBirthDate_ValidDate_SetsBirthDate() { // Arrange var student = MockValidStudent(); - var birthDate = DateTime.Now.AddDays(-1); + var birthDate = DateTime.UtcNow.AddDays(-1); // Act student.BirthDate = birthDate; @@ -62,7 +110,7 @@ public void SetBirthDate_FutureDate_ThrowsException() var student = MockValidStudent(); // Act & Assert - Assert.Throws(() => student.BirthDate = DateTime.Now.AddDays(1)); + Assert.Throws(() => student.BirthDate = DateTime.UtcNow.AddDays(1)); } [Fact] @@ -129,7 +177,7 @@ public void SetDispatchDate_ValidDate_SetsDispatchDate() { // Arrange var student = MockValidStudent(); - var dispatchDate = DateTime.Now.AddDays(-1); + var dispatchDate = DateTime.UtcNow.AddDays(-1); // Act student.DispatchDate = dispatchDate; @@ -155,7 +203,7 @@ public void SetDispatchDate_FutureDate_ThrowsException() var student = MockValidStudent(); // Act & Assert - Assert.Throws(() => student.DispatchDate = DateTime.Now.AddDays(1)); + Assert.Throws(() => student.DispatchDate = DateTime.UtcNow.AddDays(1)); } [Fact] diff --git a/src/Domain.Tests/Entities/SubAreaUnitTests.cs b/src/Domain.Tests/Entities/SubAreaUnitTests.cs index 28e5aee3..4ef0b787 100644 --- a/src/Domain.Tests/Entities/SubAreaUnitTests.cs +++ b/src/Domain.Tests/Entities/SubAreaUnitTests.cs @@ -1,7 +1,6 @@ using Domain.Entities; using Domain.Validation; using FluentAssertions; -using System; using Xunit; namespace Domain.Tests.Entities diff --git a/src/Domain.Tests/Entities/TypeAssistanceUnitTests.cs b/src/Domain.Tests/Entities/TypeAssistanceUnitTests.cs deleted file mode 100644 index 1dbbbb65..00000000 --- a/src/Domain.Tests/Entities/TypeAssistanceUnitTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Domain.Entities; -using Domain.Validation; -using FluentAssertions; -using System; -using Xunit; - -namespace Domain.Tests.Entities -{ - public class TypeAssistanceUnitTests - { - private TypeAssistance MockValidTypeAssistance() => - new TypeAssistance(Guid.NewGuid(), "Scholarship Name", "Scholarship Description"); - - [Fact] - public void SetName_ValidName_SetsName() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - var name = "Scholarship Name"; - - // Act - scholarship.Name = name; - - // Assert - scholarship.Name.Should().Be(name); - } - - [Fact] - public void SetName_NullOrEmptyName_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Name = null); - Assert.Throws(() => scholarship.Name = string.Empty); - } - - [Fact] - public void SetName_TooShortName_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Name = "AB"); - } - - [Fact] - public void SetName_TooLongName_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Name = new string('A', 1500)); - } - - [Fact] - public void SetDescription_ValidDescription_SetsDescription() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - var description = "Scholarship Description"; - - // Act - scholarship.Description = description; - - // Assert - scholarship.Description.Should().Be(description); - } - - [Fact] - public void SetDescription_NullOrEmptyDescription_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Description = null); - Assert.Throws(() => scholarship.Description = string.Empty); - } - - [Fact] - public void SetDescription_TooShortDescription_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Description = "AB"); - } - - [Fact] - public void SetDescription_TooLongDescription_ThrowsException() - { - // Arrange - var scholarship = MockValidTypeAssistance(); - - // Act & Assert - Assert.Throws(() => scholarship.Description = "K5SYFiBmSfsadfsaBtfdsgfsdQkwCPPXKaTqoVtz7WF0LpFMG6hCu5sk8dBEbrhNDrBrd8WVt3vNp8uzhkck3nrYPVCMHvnDRnmdqlWRScH8yUYzoeLsiTvnROfHFm0GCsIltVcGiOBE6rkHCgjFCFJiSAdnBJOj54Godr0lRXRWBb8iIthDiPIvVKmtfRzH61ojFjezbENdzGgJisDzjg8zEogLia5D5cJKhGPGAgXsl1lDlFy2H8RvMZGwNg9UFyvRnRo6MYkxGLw6TemiwLBR99z5A3tEWUi9VVRhZpqWDvDYpEP4YaLfyTVcmPiHOtLxTMvkmSmWH3Z6O7uH69KJYLu2BqrNTe60bdu1Gvsk12N4LLT1aMmm2o679oqgxUAjws3PCchl2oHnDoX8qe5LywOuSanuknRUpRNnmcLNgbIxlrlPbFPxgokg3jt80dFi8KmyFQFV1p9sBBlr4V1i64ToundA5R7SLh7lNwpR9DuyzT90FgQs8ZYXlkspolRSLFV8ohYfHomvX2GanuJ4LXgkBnOtERsHQYhD9yqDphAgaeVMkfeUhsNyn3PGW0alcQ1WHaS3HllN2g0aMz6UUjn7JzEXwfUl7WO0muT4MfFKUh1mED5wgANs5nt65DVc1AWLkLXd0RNUILwRIDydtr4QrkeMJUpXx3DFX2w5eTjmY69dNCct8gEnOGZ2F4YhveYVPgNcAABMBKri8AutNxPoPvEDk36sYqMEFsHpM9i5gHQtwtRe8HiGmvneeEGUAK7wsyJG7FBHXjU2Rvcxd2uLq24AEJOO6TABzePdNeqrh2y9h08p0ZTIGih9gT7rOXcURfJ2zn6pKY0CHOrQhaSK9xgBOgExdCRFI4mtMoIrlAJL8OLYDlckR1YalMv2ypWzHPUkVkXob3MrbfD0F5MTNZocl7VAwf0xAGsKgae4JrfK8iS3E3ZyfyYw29Qu4vUMRY6VxK1GY02z1wJpNiI0LjkiOHlL3rYxD9YeOkJLOCq382bMdy5A0NJkqSiWrqkK3z733iOSKp1PQ8zIUCkhTvX1Zig3OPRISAKaHt4hqkU8NpEtwXyjGYM7LaUNfecztIUnAkIswrsJYcErFDEQvnIbcAMCTZgwynhU06FxBKyDQc4rY2AeEnFOkE9pvleIO3VEDWgfNgAv5oCzn8U2JKTyuAHRXf0xoGOAsw23NVwaVROVpkP4uYUyVLLePOiSmiGiHz2fjH4TdLtvRjQvmT40eXB7dAxbOVjZcIwwP4Pi6Kq04Eb70kJbpckEGPPYfLewJdhiAnmiIgDBVn7tgIY9RmknnaDANuZiZNiRCO9Il63KtrlW8o3RZdI1lW3mSIQkFOVIx7JICtB6WDBTonZbYZ6zqcB1ut1efAkoTKnEsO2jWz2QOpoLMP7NVThFcEjt7lruRy3SPsZxwnAmsaQywXztsHxHod5KrRqxhuuVjt2nWGutyal7vw0qjIV8ugDATYS4Nq2hOBnK5t94HeTavgTrN8nx24"); - } - } -} diff --git a/src/Domain.Tests/Entities/UserUnitTests.cs b/src/Domain.Tests/Entities/UserUnitTests.cs index d56da493..8b0649dd 100644 --- a/src/Domain.Tests/Entities/UserUnitTests.cs +++ b/src/Domain.Tests/Entities/UserUnitTests.cs @@ -1,265 +1,220 @@ using Domain.Entities; using Domain.Entities.Enums; using Domain.Validation; -using FluentAssertions; using Xunit; -namespace Domain.Tests.Entities; -public class UserUniTests +namespace Domain.Tests.Entities { - [Fact(DisplayName = "Create User With Valid State")] - public void CreateUser_WithValidParameters_ResultObjectValidState() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .NotThrow(); - } - - #region Name Tests - [Fact] - public void CreateUser_ShortNameValue_DomainExceptionShortName() - { - Action action = () => new User("Us", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.MinLength("name", 3)); - } - - [Fact] - public void CreateUser_BigNameValue_DomainExceptionBigName() - { - Action action = () => new User("frttcgyxukstpasvqpbhqmsbjjvolqsrbfkaiptymddeegoedgodnxtlotplntqitreugkiernzsjmganfdjxcyagoqrzmadqffbsvehnblaovkzclijojbbrustwczcilguchcmrfswjjwquyjbhwgdtnwysdxuymmaibjwvnpvemjxpdkirtjezwyifnrmngoodufstmndqcgawzlvqazxfhdhrtcditryoiczqabbpdhqgwqzukrenvvezlwiciwbprebrxuiytnumvupvoqtdfnbmoxrrgalrudecdugkfblogserwipsrbcqtmotleqarahfqxokfqmrsorjuofatcvsd", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.MaxLength("name", 300)); - } - - [Fact] - public void CreateUser_MissingNameValue_DomainExceptionRequiredName() - { - Action action = () => new User(string.Empty, "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("name")); - } - - [Fact] - public void CreateUser_WithNullNameValue_DomainExceptionInvalidName() - { - Action action = () => new User(null, "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("name")); - } - - [Fact] - public void UpdateUserName_WithValidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Name = "Teste Name"; - action.Should() - .NotThrow(); - } - - [Fact] - public void UpdateUserName_WithInvalidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Name = string.Empty; - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("name")); - } - #endregion - - #region Email Tests - [Fact] - public void CreateUser_MissingEmailValue_DomainExceptionRequiredEmail() - { - Action action = () => new User("User Name", string.Empty, "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("email")); - } - - [Fact] - public void CreateUser_BigEmailValue_DomainExceptionBigEmail() - { - Action action = () => new User("User Name", "frttcgyxukstpasvqpbhqmsbjjvolqsrbfkaiptymddeegoedgodnxtlotplntqitreugkiernzsjmganfdjxcyagoqrzmadqffbsvehnblaovkzclijojbbrustwczcilguchcmrfswjjwquyjbhwgdtnwysdxuymmaibjwvnpvemjxpdkirtjezwyifnrmngoodufstmndqcgawzlvqazxfhdhrtcditryoiczqabbpdhqgwqzukrenvvezlwiciwbprebrxuiytnumvupvoqtdfnbmoxrrgalrudecdugkfblogserwipsrbcqtmotleqarahfqxokfqmrsorjuofatcvsd", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.MaxLength("email", 300)); - } - - [Fact] - public void CreateUser_WithNullEmailValue_DomainExceptionRequiredEmail() - { - Action action = () => new User("User Name", null, "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("email")); - } - - [Fact] - public void CreateUser_WithInvalidEmailValue_DomainExceptionInvalidEmail() - { - Action action = () => new User("User Name", "aaaa-bbbb", "123456", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.InvalidEmail("email")); - } - #endregion - - #region Password Tests - [Fact] - public void CreateUser_ShortPasswordValue_DomainExceptionShortPassword() - { - Action action = () => new User("User Name", "username@gmail.com", "123", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.MinLength("password", 6)); - } - - [Fact] - public void CreateUser_BigPasswordValue_DomainExceptionBigPassword() - { - Action action = () => new User("User Name", "username@gmail.com", "frttcgyxukstpasvqpbhqmsbjjvolqsrbfkaiptymddeegoedgodnxtlotplntqitreugkiernzsjmganfdjxcyagoqrzmadqffbsvehnblaovkzclijojbbrustwczcilguchcmrfswjjwquyjbhwgdtnwysdxuymmaibjwvnpvemjxpdkirtjezwyifnrmngoodufstmndqcgawzlvqazxfhdhrtcditryoiczqabbpdhqgwqzukrenvvezlwiciwbprebrxuiytnumvupvoqtdfnbmoxrrgalrudecdugkfblogserwipsrbcqtmotleqarahfqxokfqmrsorjuofatcvsd", "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.MaxLength("password", 300)); - } - - [Fact] - public void CreateUser_MissingPasswordValue_DomainExceptionRequiredPassword() - { - Action action = () => new User("User Name", "username@gmail.com", string.Empty, "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("password")); - } - - [Fact] - public void CreateUser_WithNullPasswordValue_DomainExceptionInvalidPassword() - { - Action action = () => new User("User Name", "username@gmail.com", null, "15162901784", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("password")); - } - - [Fact] - public void UpdateUserPassword_WithValidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Password = "987654321"; - action.Should() - .NotThrow(); - } - - [Fact] - public void UpdateUserPassword_WithInvalidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Password = string.Empty; - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("password")); - } - #endregion - - #region CPF Tests - [Fact] - public void CreateUser_ShortCpfValue_DomainExceptionShortCpf() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", "1516290178", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.WithLength("cpf", 11)); - } - - [Fact] - public void CreateUser_BigCpfValue_DomainExceptionBigCpf() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", "151629017840", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.WithLength("cpf", 11)); - } - - [Fact] - public void CreateUser_MissingCpfValue_DomainExceptionRequiredCpf() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", string.Empty, ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("cpf")); - } - - [Fact] - public void CreateUser_WithNullCpfValue_DomainExceptionInvalidCpf() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", null, ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("cpf")); - } - - [Fact] - public void CreateUser_WithInvalidCpfValue_DomainExceptionInvalidCpf() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", "12345678911", ERole.ADMIN, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.InvalidCpf()); - } - - [Fact] - public void UpdateUserCpf_WithValidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.CPF = "15162901784"; - action.Should() - .NotThrow(); - } - - [Fact] - public void UpdateUserCpf_WithInvalidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.CPF = string.Empty; - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("cpf")); - } - #endregion - - #region Role Tests - [Fact] - public void CreateUser_WithNullRoleValue_DomainExceptionInvalidRole() - { - Action action = () => new User("User Name", "username@gmail.com", "123456", "15162901784", null, null); - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("role")); - } - - [Fact] - public void UpdateUserRole_WithValidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Role = ERole.STUDENT; - action.Should() - .NotThrow(); - } - - [Fact] - public void UpdateUserRole_WithInvalidParameters_ResultObjectValidState() - { - var model = new User("User Name", "username@gmail.com", "123456", "15162901784", ERole.ADMIN, null); - Action action = () => model.Role = null; - action.Should() - .Throw() - .WithMessage(ExceptionMessageFactory.Required("role")); + public class UserUnitTests + { + private static User MockValidUser() => new("John Doe", "john.doe@example.com", "strongpassword", "92114660087", ERole.ADMIN); + + [Fact] + public void SetName_ValidName_SetsName() + { + // Arrange + var user = MockValidUser(); + var name = "John Doe Updated"; + + // Act + user.Name = name; + + // Assert + Assert.Equal(name, user.Name); + } + + [Fact] + public void SetName_NullOrEmptyName_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Name = null); + Assert.Throws(() => user.Name = string.Empty); + } + + [Fact] + public void SetName_TooShortName_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Name = "AB"); + } + + [Fact] + public void SetName_TooLongName_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Name = new string('A', 500)); + } + + [Fact] + public void SetEmail_ValidEmail_SetsEmail() + { + // Arrange + var user = MockValidUser(); + var email = "john.doe.updated@example.com"; + + // Act + user.Email = email; + + // Assert + Assert.Equal(email, user.Email); + } + + [Fact] + public void SetEmail_NullOrEmptyEmail_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Email = null); + Assert.Throws(() => user.Email = string.Empty); + } + + [Fact] + public void SetEmail_InvalidEmail_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Email = "invalid-email"); + } + + [Fact] + public void SetEmail_TooLongEmail_ThrowsEntityExceptionValidation() + { + // Arrange + var user = MockValidUser(); + var longEmail = new string('a', 300) + "@example.com"; + + // Act & Assert + var ex = Assert.Throws(() => user.Email = longEmail); + // Assert.Equal("email", ex.PropertyName); // Ensure that the correct property triggered the exception + Assert.Contains("email", ex.Message); // Ensure that the exception message contains the property name + } + + [Fact] + public void SetPassword_ValidPassword_SetsPassword() + { + // Arrange + var user = MockValidUser(); + var password = "new-strong-password"; + + // Act + user.Password = password; + + // Assert + Assert.Equal(password, user.Password); + } + + [Fact] + public void SetPassword_NullOrEmptyPassword_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Password = null); + Assert.Throws(() => user.Password = string.Empty); + } + + [Fact] + public void SetPassword_TooShortPassword_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Password = "abc12"); + } + + [Fact] + public void SetPassword_TooLongPassword_ThrowsException() + { + // Arrange + var user = MockValidUser(); + var longPassword = new string('a', 400); + + // Act & Assert + Assert.Throws(() => user.Password = longPassword); + } + + [Fact] + public void SetCPF_ValidCPF_SetsCPF() + { + // Arrange + var user = MockValidUser(); + var cpf = "58247313065"; + + // Act + user.CPF = cpf; + + // Assert + Assert.Equal(cpf, user.CPF); + } + + [Fact] + public void SetCPF_NullOrEmptyCPF_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.CPF = null); + Assert.Throws(() => user.CPF = string.Empty); + } + + [Fact] + public void SetCPF_InvalidCPF_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.CPF = "123.456.789-00"); + } + + [Fact] + public void SetCPF_InvalidLengthCPF_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.CPF = "123456789012"); + } + + [Fact] + public void SetRole_ValidRole_SetsRole() + { + // Arrange + var user = MockValidUser(); + var role = ERole.ADMIN; + + // Act + user.Role = role; + + // Assert + Assert.Equal(role, user.Role); + } + + [Fact] + public void SetRole_NullRole_ThrowsException() + { + // Arrange + var user = MockValidUser(); + + // Act & Assert + Assert.Throws(() => user.Role = null); + } } - #endregion } \ No newline at end of file diff --git a/src/Domain.Tests/UseCases/Area/CreateAreaTests.cs b/src/Domain.Tests/UseCases/Area/CreateAreaTests.cs deleted file mode 100644 index 832e00b0..00000000 --- a/src/Domain.Tests/UseCases/Area/CreateAreaTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Area; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; -using Domain.UseCases; -using Domain.Validation; -using Moq; -using NUnit.Framework; -using System; -using System.Threading.Tasks; - -namespace Domain.Tests.UseCases.Area -{ - [TestFixture] - public class CreateAreaTests - { - private ICreateArea _createArea; - private Mock _areaRepositoryMock; - private Mock _mainAreaRepositoryMock; - private Mock _mapperMock; - - public Domain.Entities.Area MockValidArea() => new Domain.Entities.Area(Guid.NewGuid(), "ABC", "Area Name"); - private Domain.Entities.MainArea MockValidMainArea() => new Domain.Entities.MainArea(Guid.NewGuid(), "ABC", "Main Area Name"); - - [SetUp] - public void Setup() - { - _areaRepositoryMock = new Mock(); - _mainAreaRepositoryMock = new Mock(); - _mapperMock = new Mock(); - - _createArea = new CreateArea( - _areaRepositoryMock.Object, - _mainAreaRepositoryMock.Object, - _mapperMock.Object - ); - } - - [Test] - public async Task Execute_WithValidInput_ShouldCreateArea() - { - // Arrange - var input = new CreateAreaInput - { - Code = "areaCode0", - MainAreaId = Guid.NewGuid(), - }; - - var areaEntity = MockValidArea(); - var detailedOutput = new DetailedReadAreaOutput(); - - _areaRepositoryMock.Setup(r => r.GetByCode(input.Code)).ReturnsAsync((Domain.Entities.Area)null); - _mainAreaRepositoryMock.Setup(r => r.GetById(input.MainAreaId)).ReturnsAsync(MockValidMainArea()); - _areaRepositoryMock.Setup(r => r.Create(It.IsAny())).ReturnsAsync(areaEntity); - _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); - - // Act - var result = await _createArea.Execute(input); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(detailedOutput, result); - _areaRepositoryMock.Verify(r => r.GetByCode(input.Code), Times.Once); - _mainAreaRepositoryMock.Verify(r => r.GetById(input.MainAreaId), Times.Once); - _areaRepositoryMock.Verify(r => r.Create(It.IsAny()), Times.Once); - _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); - } - - [Test] - public void Execute_WithExistingCode_ShouldThrowException() - { - // Arrange - var input = new CreateAreaInput - { - Code = "existingCode2", - MainAreaId = Guid.NewGuid(), - }; - - var existingArea = MockValidArea(); - - _areaRepositoryMock.Setup(r => r.GetByCode(input.Code)).ReturnsAsync(existingArea); - - // Act & Assert - Assert.ThrowsAsync(async () => await _createArea.Execute(input)); - _areaRepositoryMock.Verify(r => r.GetByCode(input.Code), Times.Once); - _mainAreaRepositoryMock.Verify(r => r.GetById(It.IsAny()), Times.Never); - _areaRepositoryMock.Verify(r => r.Create(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - - [Test] - public void Execute_WithMissingMainAreaId_ShouldThrowException() - { - // Arrange - var input = new CreateAreaInput - { - Code = "areaCode1", - MainAreaId = null, - }; - - // Act & Assert - Assert.ThrowsAsync(async () => await _createArea.Execute(input)); - _areaRepositoryMock.Verify(r => r.GetByCode(It.IsAny()), Times.Once); - _mainAreaRepositoryMock.Verify(r => r.GetById(It.IsAny()), Times.Never); - _areaRepositoryMock.Verify(r => r.Create(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - - [Test] - public void Execute_WithInactiveMainArea_ShouldThrowException() - { - // Arrange - var input = new CreateAreaInput - { - Code = "areaCode2", - MainAreaId = Guid.NewGuid(), - }; - - var inactiveMainArea = MockValidMainArea(); - inactiveMainArea.DeactivateEntity(); - _mainAreaRepositoryMock.Setup(r => r.GetById(input.MainAreaId)).ReturnsAsync(inactiveMainArea); - - // Act & Assert - Assert.ThrowsAsync(async () => await _createArea.Execute(input)); - _areaRepositoryMock.Verify(r => r.GetByCode(input.Code), Times.Once); - _mainAreaRepositoryMock.Verify(r => r.GetById(input.MainAreaId), Times.Once); - _areaRepositoryMock.Verify(r => r.Create(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - } -} diff --git a/src/Domain.Tests/UseCases/Area/GetAreaByIdTests.cs b/src/Domain.Tests/UseCases/Area/GetAreaByIdTests.cs deleted file mode 100644 index 9860293e..00000000 --- a/src/Domain.Tests/UseCases/Area/GetAreaByIdTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Area; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; -using Domain.UseCases; -using Domain.Validation; -using Moq; -using NUnit.Framework; -using System; -using System.Threading.Tasks; - -namespace Domain.Tests.UseCases.Area -{ - [TestFixture] - public class GetAreaByIdTests - { - private IGetAreaById _getAreaById; - private Mock _areaRepositoryMock; - private Mock _mapperMock; - - public Domain.Entities.Area MockValidArea() => new Domain.Entities.Area(Guid.NewGuid(), "ABC", "Area Name"); - - [SetUp] - public void Setup() - { - _areaRepositoryMock = new Mock(); - _mapperMock = new Mock(); - - _getAreaById = new GetAreaById( - _areaRepositoryMock.Object, - _mapperMock.Object - ); - } - - [Test] - public async Task Execute_WithValidId_ShouldReturnDetailedReadAreaOutput() - { - // Arrange - var id = Guid.NewGuid(); - var areaEntity = MockValidArea(); - var detailedOutput = new DetailedReadAreaOutput(); - - _areaRepositoryMock.Setup(r => r.GetById(id)).ReturnsAsync(areaEntity); - _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); - - // Act - var result = await _getAreaById.Execute(id); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(detailedOutput, result); - _areaRepositoryMock.Verify(r => r.GetById(id), Times.Once); - _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); - } - - [Test] - public void Execute_WithNullId_ShouldThrowException() - { - // Arrange - Guid? id = null; - - // Act & Assert - Assert.ThrowsAsync(async () => await _getAreaById.Execute(id)); - _areaRepositoryMock.Verify(r => r.GetById(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - } -} diff --git a/src/Domain.Tests/UseCases/Area/UpdateAreaTests.cs b/src/Domain.Tests/UseCases/Area/UpdateAreaTests.cs deleted file mode 100644 index 68f1623d..00000000 --- a/src/Domain.Tests/UseCases/Area/UpdateAreaTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Area; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; -using Domain.UseCases; -using Domain.Validation; -using Moq; -using NUnit.Framework; -using System; -using System.Threading.Tasks; - -namespace Domain.Tests.UseCases.Area -{ - [TestFixture] - public class UpdateAreaTests - { - private IUpdateArea _updateArea; - private Mock _areaRepositoryMock; - private Mock _mapperMock; - - public Domain.Entities.Area MockValidArea() => new Domain.Entities.Area(Guid.NewGuid(), "ABC", "Area Name"); - - [SetUp] - public void Setup() - { - _areaRepositoryMock = new Mock(); - _mapperMock = new Mock(); - - _updateArea = new UpdateArea( - _areaRepositoryMock.Object, - _mapperMock.Object - ); - } - - [Test] - public async Task Execute_WithValidIdAndInput_ShouldReturnDetailedReadAreaOutput() - { - // Arrange - var id = Guid.NewGuid(); - var input = new UpdateAreaInput - { - Name = "New Area Name", - Code = "New Area Code", - MainAreaId = Guid.NewGuid() - }; - var areaEntity = MockValidArea(); - var detailedOutput = new DetailedReadAreaOutput(); - - _areaRepositoryMock.Setup(r => r.GetById(id)).ReturnsAsync(areaEntity); - _areaRepositoryMock.Setup(r => r.Update(It.IsAny())).ReturnsAsync(areaEntity); - _mapperMock.Setup(m => m.Map(areaEntity)).Returns(detailedOutput); - - // Act - var result = await _updateArea.Execute(id, input); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(detailedOutput, result); - _areaRepositoryMock.Verify(r => r.GetById(id), Times.Once); - _areaRepositoryMock.Verify(r => r.Update(areaEntity), Times.Once); - _mapperMock.Verify(m => m.Map(areaEntity), Times.Once); - } - - [Test] - public void Execute_WithNullId_ShouldThrowException() - { - // Arrange - Guid? id = null; - var input = new UpdateAreaInput - { - Name = "New Area Name", - Code = "New Area Code", - MainAreaId = Guid.NewGuid() - }; - - // Act & Assert - Assert.ThrowsAsync(async () => await _updateArea.Execute(id, input)); - _areaRepositoryMock.Verify(r => r.GetById(It.IsAny()), Times.Never); - _areaRepositoryMock.Verify(r => r.Update(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - - [Test] - public void Execute_WithNonExistingId_ShouldThrowException() - { - // Arrange - var id = Guid.NewGuid(); - var input = new UpdateAreaInput - { - Name = "New Area Name", - Code = "New Area Code", - MainAreaId = Guid.NewGuid() - }; - - _areaRepositoryMock.Setup(r => r.GetById(id)).ReturnsAsync((Domain.Entities.Area)null); - - // Act & Assert - Assert.ThrowsAsync(async () => await _updateArea.Execute(id, input)); - _areaRepositoryMock.Verify(r => r.GetById(id), Times.Once); - _areaRepositoryMock.Verify(r => r.Update(It.IsAny()), Times.Never); - _mapperMock.Verify(m => m.Map(It.IsAny()), Times.Never); - } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Area/UpdateAreaInput.cs b/src/Domain/Contracts/Area/UpdateAreaInput.cs deleted file mode 100644 index 2b16414f..00000000 --- a/src/Domain/Contracts/Area/UpdateAreaInput.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.Area -{ - public class UpdateAreaInput : BaseAreaContract - { - [Required] - public Guid? MainAreaId { get; set; } - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Auth/UserLoginInput.cs b/src/Domain/Contracts/Auth/UserLoginInput.cs deleted file mode 100644 index a64a2ff4..00000000 --- a/src/Domain/Contracts/Auth/UserLoginInput.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.Auth -{ - public class UserLoginInput - { - [Required] - public string? Email { get; set; } - [Required] - public string? Password { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Auth/UserLoginOutput.cs b/src/Domain/Contracts/Auth/UserLoginOutput.cs deleted file mode 100644 index 4199540a..00000000 --- a/src/Domain/Contracts/Auth/UserLoginOutput.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Domain.Contracts.Auth -{ - public class UserLoginOutput - { - public string? Token { get; set; } - public DateTime Expiration { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Campus/CreateCampusInput.cs b/src/Domain/Contracts/Campus/CreateCampusInput.cs deleted file mode 100644 index 5fa878bd..00000000 --- a/src/Domain/Contracts/Campus/CreateCampusInput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.Campus -{ - public class CreateCampusInput : BaseCampusContract - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Campus/UpdateCampusInput.cs b/src/Domain/Contracts/Campus/UpdateCampusInput.cs deleted file mode 100644 index d89a83e1..00000000 --- a/src/Domain/Contracts/Campus/UpdateCampusInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.Campus -{ - public class UpdateCampusInput : BaseCampusContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Course/CreateCourseInput.cs b/src/Domain/Contracts/Course/CreateCourseInput.cs deleted file mode 100644 index 479cae2b..00000000 --- a/src/Domain/Contracts/Course/CreateCourseInput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.Course -{ - public class CreateCourseInput : BaseCourseContract - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Course/UpdateCourseInput.cs b/src/Domain/Contracts/Course/UpdateCourseInput.cs deleted file mode 100644 index 51cb6cf9..00000000 --- a/src/Domain/Contracts/Course/UpdateCourseInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.Course -{ - public class UpdateCourseInput : BaseCourseContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/MainArea/CreateMainAreaInput.cs b/src/Domain/Contracts/MainArea/CreateMainAreaInput.cs deleted file mode 100644 index 2eb21af9..00000000 --- a/src/Domain/Contracts/MainArea/CreateMainAreaInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.MainArea -{ - public class CreateMainAreaInput : BaseMainAreaContract { } -} \ No newline at end of file diff --git a/src/Domain/Contracts/MainArea/UpdateMainAreaInput.cs b/src/Domain/Contracts/MainArea/UpdateMainAreaInput.cs deleted file mode 100644 index df846f06..00000000 --- a/src/Domain/Contracts/MainArea/UpdateMainAreaInput.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.MainArea -{ - public class UpdateMainAreaInput : BaseMainAreaContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Notice/BaseNoticeContract.cs b/src/Domain/Contracts/Notice/BaseNoticeContract.cs deleted file mode 100644 index b49c0a1b..00000000 --- a/src/Domain/Contracts/Notice/BaseNoticeContract.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.Notice -{ - public abstract class BaseNoticeContract - { - [Required] - public DateTime? StartDate { get; set; } - [Required] - public DateTime? FinalDate { get; set; } - [Required] - public DateTime? AppealStartDate { get; set; } - [Required] - public DateTime? AppealFinalDate { get; set; } - [Required] - public int? SuspensionYears { get; set; } - [Required] - public int? SendingDocumentationDeadline { get; set; } - - public string? Description { get; set; } - public string? DocUrl { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Notice/CreateNoticeInput.cs b/src/Domain/Contracts/Notice/CreateNoticeInput.cs deleted file mode 100644 index 7b586a03..00000000 --- a/src/Domain/Contracts/Notice/CreateNoticeInput.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Domain.Contracts.Notice -{ - public class CreateNoticeInput : BaseNoticeContract - { - public IFormFile? File { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Notice/DetailedReadNoticeOutput.cs b/src/Domain/Contracts/Notice/DetailedReadNoticeOutput.cs deleted file mode 100644 index 8fbc2bc5..00000000 --- a/src/Domain/Contracts/Notice/DetailedReadNoticeOutput.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Domain.Contracts.Notice -{ - public class DetailedReadNoticeOutput : BaseNoticeContract - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Notice/ResumedReadNoticeOutput.cs b/src/Domain/Contracts/Notice/ResumedReadNoticeOutput.cs deleted file mode 100644 index 2fa55889..00000000 --- a/src/Domain/Contracts/Notice/ResumedReadNoticeOutput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.Notice -{ - public class ResumedReadNoticeOutput : BaseNoticeContract - { - public Guid? Id { get; set; } - } -} diff --git a/src/Domain/Contracts/Notice/UpdateNoticeInput.cs b/src/Domain/Contracts/Notice/UpdateNoticeInput.cs deleted file mode 100644 index a916a6e0..00000000 --- a/src/Domain/Contracts/Notice/UpdateNoticeInput.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Domain.Contracts.Notice -{ - public class UpdateNoticeInput : BaseNoticeContract - { - public Guid? Id { get; set; } - public IFormFile? File { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Professor/DetailedReadStudentOutput.cs b/src/Domain/Contracts/Professor/DetailedReadStudentOutput.cs deleted file mode 100644 index 9d3c02ca..00000000 --- a/src/Domain/Contracts/Professor/DetailedReadStudentOutput.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Contracts.Professor -{ - public class DetailedReadProfessorOutput : BaseProfessorContract - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - public UserReadOutput? User { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Professor/ResumedReadStudentOutput.cs b/src/Domain/Contracts/Professor/ResumedReadStudentOutput.cs deleted file mode 100644 index 536d57f1..00000000 --- a/src/Domain/Contracts/Professor/ResumedReadStudentOutput.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Domain.Contracts.Professor -{ - public class ResumedReadProfessorOutput : BaseProfessorContract - { - public Guid Id { get; set; } - public string? Name { get; set; } - public string? Email { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Professor/UpdateStudentInput.cs b/src/Domain/Contracts/Professor/UpdateStudentInput.cs deleted file mode 100644 index cd989d06..00000000 --- a/src/Domain/Contracts/Professor/UpdateStudentInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.Professor -{ - public class UpdateProfessorInput : BaseProfessorContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/ProgramType/CreateProgramTypeInput.cs b/src/Domain/Contracts/ProgramType/CreateProgramTypeInput.cs deleted file mode 100644 index fe9ddcd2..00000000 --- a/src/Domain/Contracts/ProgramType/CreateProgramTypeInput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.ProgramType -{ - public class CreateProgramTypeInput : BaseProgramTypeContract - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/ProgramType/ResumedReadProgramTypeOutput.cs b/src/Domain/Contracts/ProgramType/ResumedReadProgramTypeOutput.cs deleted file mode 100644 index 2a47c4e7..00000000 --- a/src/Domain/Contracts/ProgramType/ResumedReadProgramTypeOutput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.ProgramType -{ - public class ResumedReadProgramTypeOutput : BaseProgramTypeContract - { - public Guid? Id { get; set; } - } -} diff --git a/src/Domain/Contracts/ProgramType/UpdateProgramTypeInput.cs b/src/Domain/Contracts/ProgramType/UpdateProgramTypeInput.cs deleted file mode 100644 index 96844666..00000000 --- a/src/Domain/Contracts/ProgramType/UpdateProgramTypeInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.ProgramType -{ - public class UpdateProgramTypeInput : BaseProgramTypeContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/BaseProjectContract.cs b/src/Domain/Contracts/Project/BaseProjectContract.cs deleted file mode 100644 index fde77263..00000000 --- a/src/Domain/Contracts/Project/BaseProjectContract.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace Domain.Contracts.Project -{ - public abstract class BaseProjectContract - { - #region Informações do Projeto - public string? Title { get; set; } - public string? KeyWord1 { get; set; } - public string? KeyWord2 { get; set; } - public string? KeyWord3 { get; set; } - public bool IsScholarshipCandidate { get; set; } - public bool IsProductivityFellow { get; set; } - public string? Objective { get; set; } - public string? Methodology { get; set; } - public string? ExpectedResults { get; set; } - public string? ActivitiesExecutionSchedule { get; set; } - #endregion - - #region Produção Científica - Trabalhos Publicados - public int? WorkType1 { get; set; } - public int? WorkType2 { get; set; } - public int? IndexedConferenceProceedings { get; set; } - public int? NotIndexedConferenceProceedings { get; set; } - public int? CompletedBook { get; set; } - public int? OrganizedBook { get; set; } - public int? BookChapters { get; set; } - public int? BookTranslations { get; set; } - public int? ParticipationEditorialCommittees { get; set; } - #endregion - - #region Produção Artístca e Cultural - Produção Apresentada - public int? FullComposerSoloOrchestraAllTracks { get; set; } - public int? FullComposerSoloOrchestraCompilation { get; set; } - public int? ChamberOrchestraInterpretation { get; set; } - public int? IndividualAndCollectiveArtPerformances { get; set; } - public int? ScientificCulturalArtisticCollectionsCuratorship { get; set; } - #endregion - - #region Produção Técnica - Produtos Registrados - public int? PatentLetter { get; set; } - public int? PatentDeposit { get; set; } - public int? SoftwareRegistration { get; set; } - #endregion - - #region Critérios de Avaliação - public int? APIndex { get; set; } - public int? Qualification { get; set; } - public int? ProjectProposalObjectives { get; set; } - public int? AcademicScientificProductionCoherence { get; set; } - public int? ProposalMethodologyAdaptation { get; set; } - public int? EffectiveContributionToResearch { get; set; } - #endregion - - #region Resultados da Avaliação - public int? Status { get; set; } - public string? StatusDescription { get; set; } - public string? EvaluatorObservation { get; set; } - public string? AppealDescription { get; set; } - public string? AppealEvaluatorObservation { get; set; } - #endregion - - #region Relacionamentos - public Guid? ProgramTypeId { get; set; } - public Guid? ProfessorId { get; set; } - public Guid? StudentId { get; set; } - public Guid? SubAreaId { get; set; } - public Guid? NoticeId { get; set; } - #endregion - - #region Informações de Controle - public DateTime? SubmitionDate { get; set; } - public DateTime? RessubmitionDate { get; set; } - public DateTime? CancellationDate { get; set; } - public string? CancellationReason { get; set; } - #endregion - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/OpenProjectInput.cs b/src/Domain/Contracts/Project/OpenProjectInput.cs deleted file mode 100644 index 05d0565d..00000000 --- a/src/Domain/Contracts/Project/OpenProjectInput.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Domain.Contracts.Project -{ - public class OpenProjectInput - { - #region Informações do Projeto - public string? Title { get; set; } - public string? KeyWord1 { get; set; } - public string? KeyWord2 { get; set; } - public string? KeyWord3 { get; set; } - public bool IsScholarshipCandidate { get; set; } - public bool IsProductivityFellow { get; set; } - public string? Objective { get; set; } - public string? Methodology { get; set; } - public string? ExpectedResults { get; set; } - public string? ActivitiesExecutionSchedule { get; set; } - #endregion - - #region Produção Científica - Trabalhos Publicados - public int? WorkType1 { get; set; } - public int? WorkType2 { get; set; } - public int? IndexedConferenceProceedings { get; set; } - public int? NotIndexedConferenceProceedings { get; set; } - public int? CompletedBook { get; set; } - public int? OrganizedBook { get; set; } - public int? BookChapters { get; set; } - public int? BookTranslations { get; set; } - public int? ParticipationEditorialCommittees { get; set; } - #endregion - - #region Produção Artístca e Cultural - Produção Apresentada - public int? FullComposerSoloOrchestraAllTracks { get; set; } - public int? FullComposerSoloOrchestraCompilation { get; set; } - public int? ChamberOrchestraInterpretation { get; set; } - public int? IndividualAndCollectiveArtPerformances { get; set; } - public int? ScientificCulturalArtisticCollectionsCuratorship { get; set; } - #endregion - - #region Produção Técnica - Produtos Registrados - public int? PatentLetter { get; set; } - public int? PatentDeposit { get; set; } - public int? SoftwareRegistration { get; set; } - #endregion - - #region Relacionamentos - public Guid? ProgramTypeId { get; set; } - public Guid? ProfessorId { get; set; } - public Guid? StudentId { get; set; } - public Guid? SubAreaId { get; set; } - public Guid? NoticeId { get; set; } - #endregion - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/ProjectReportInput.cs b/src/Domain/Contracts/Project/ProjectReportInput.cs deleted file mode 100644 index cf822296..00000000 --- a/src/Domain/Contracts/Project/ProjectReportInput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.Project -{ - public class ProjectReportInput - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/ProjectReportOutput.cs b/src/Domain/Contracts/Project/ProjectReportOutput.cs deleted file mode 100644 index 496c85f0..00000000 --- a/src/Domain/Contracts/Project/ProjectReportOutput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.Project -{ - public class ProjectReportOutput - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/ResumedReadProjectOutput.cs b/src/Domain/Contracts/Project/ResumedReadProjectOutput.cs deleted file mode 100644 index 21e050bc..00000000 --- a/src/Domain/Contracts/Project/ResumedReadProjectOutput.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Domain.Contracts.Project; -public class ResumedReadProjectOutput : BaseProjectContract -{ - public Guid? Id { get; set; } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Project/UpdateProjectInput.cs b/src/Domain/Contracts/Project/UpdateProjectInput.cs deleted file mode 100644 index fe5fed83..00000000 --- a/src/Domain/Contracts/Project/UpdateProjectInput.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Domain.Contracts.Project -{ - public class UpdateProjectInput - { - #region Informações do Projeto - public string? Title { get; set; } - public string? KeyWord1 { get; set; } - public string? KeyWord2 { get; set; } - public string? KeyWord3 { get; set; } - public bool IsScholarshipCandidate { get; set; } - public bool IsProductivityFellow { get; set; } - public string? Objective { get; set; } - public string? Methodology { get; set; } - public string? ExpectedResults { get; set; } - public string? ActivitiesExecutionSchedule { get; set; } - #endregion - - #region Produção Científica - Trabalhos Publicados - public int? WorkType1 { get; set; } - public int? WorkType2 { get; set; } - public int? IndexedConferenceProceedings { get; set; } - public int? NotIndexedConferenceProceedings { get; set; } - public int? CompletedBook { get; set; } - public int? OrganizedBook { get; set; } - public int? BookChapters { get; set; } - public int? BookTranslations { get; set; } - public int? ParticipationEditorialCommittees { get; set; } - #endregion - - #region Produção Artístca e Cultural - Produção Apresentada - public int? FullComposerSoloOrchestraAllTracks { get; set; } - public int? FullComposerSoloOrchestraCompilation { get; set; } - public int? ChamberOrchestraInterpretation { get; set; } - public int? IndividualAndCollectiveArtPerformances { get; set; } - public int? ScientificCulturalArtisticCollectionsCuratorship { get; set; } - #endregion - - #region Produção Técnica - Produtos Registrados - public int? PatentLetter { get; set; } - public int? PatentDeposit { get; set; } - public int? SoftwareRegistration { get; set; } - #endregion - - #region Relacionamentos - public Guid? ProgramTypeId { get; set; } - public Guid? StudentId { get; set; } - public Guid? SubAreaId { get; set; } - #endregion - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs b/src/Domain/Contracts/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs deleted file mode 100644 index 1ccef820..00000000 --- a/src/Domain/Contracts/ProjectEvaluation/DetailedReadProjectEvaluationOutput.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Domain.Contracts.ProjectEvaluation; -public class DetailedReadProjectEvaluationOutput -{ - #region Informações Gerais da Avaliação - public Guid? ProjectId { get; set; } - public bool IsProductivityFellow { get; set; } - public Guid? SubmissionEvaluatorId { get; set; } - public int? SubmissionEvaluationStatus { get; set; } - public DateTime? SubmissionEvaluationDate { get; set; } - public string? SubmissionEvaluationDescription { get; set; } - public Guid? AppealEvaluatorId { get; set; } - public int? AppealEvaluationStatus { get; set; } - public DateTime? AppealEvaluationDate { get; set; } - public string? AppealEvaluationDescription { get; set; } - #endregion - - #region (Resultados) Produção Científica - Trabalhos Publicados - public int? FoundWorkType1 { get; set; } - public int? FoundWorkType2 { get; set; } - public int? FoundIndexedConferenceProceedings { get; set; } - public int? FoundNotIndexedConferenceProceedings { get; set; } - public int? FoundCompletedBook { get; set; } - public int? FoundOrganizedBook { get; set; } - public int? FoundBookChapters { get; set; } - public int? FoundBookTranslations { get; set; } - public int? FoundParticipationEditorialCommittees { get; set; } - #endregion - - #region (Resultados) Produção Artístca e Cultural - Produção Apresentada - public int? FoundFullComposerSoloOrchestraAllTracks { get; set; } - public int? FoundFullComposerSoloOrchestraCompilation { get; set; } - public int? FoundChamberOrchestraInterpretation { get; set; } - public int? FoundIndividualAndCollectiveArtPerformances { get; set; } - public int? FoundScientificCulturalArtisticCollectionsCuratorship { get; set; } - #endregion - - #region (Resultados) Produção Técnica - Produtos Registrados - public int? FoundPatentLetter { get; set; } - public int? FoundPatentDeposit { get; set; } - public int? FoundSoftwareRegistration { get; set; } - #endregion - - #region Critérios de Avaliação - public int? APIndex { get; set; } - public int? Qualification { get; set; } - public int? ProjectProposalObjectives { get; set; } - public int? AcademicScientificProductionCoherence { get; set; } - public int? ProposalMethodologyAdaptation { get; set; } - public int? EffectiveContributionToResearch { get; set; } - #endregion -} \ No newline at end of file diff --git a/src/Domain/Contracts/ProjectEvaluation/EvaluateAppealProjectInput.cs b/src/Domain/Contracts/ProjectEvaluation/EvaluateAppealProjectInput.cs deleted file mode 100644 index 74e4158b..00000000 --- a/src/Domain/Contracts/ProjectEvaluation/EvaluateAppealProjectInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.ProjectEvaluation; -public class EvaluateAppealProjectInput -{ - public Guid? ProjectId { get; set; } - public int? AppealEvaluationStatus { get; set; } - public string? AppealEvaluationDescription { get; set; } -} \ No newline at end of file diff --git a/src/Domain/Contracts/ProjectEvaluation/EvaluateSubmissionProjectInput.cs b/src/Domain/Contracts/ProjectEvaluation/EvaluateSubmissionProjectInput.cs deleted file mode 100644 index e79eb569..00000000 --- a/src/Domain/Contracts/ProjectEvaluation/EvaluateSubmissionProjectInput.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Domain.Contracts.ProjectEvaluation; -public class EvaluateSubmissionProjectInput -{ - #region Informações Gerais da Avaliação - public Guid? ProjectId { get; set; } - public bool IsProductivityFellow { get; set; } - public Guid? SubmissionEvaluatorId { get; set; } - public int? SubmissionEvaluationStatus { get; set; } - public DateTime? SubmissionEvaluationDate { get; set; } - public string? SubmissionEvaluationDescription { get; set; } - #endregion - - #region (Resultados) Produção Científica - Trabalhos Publicados - public int? FoundWorkType1 { get; set; } - public int? FoundWorkType2 { get; set; } - public int? FoundIndexedConferenceProceedings { get; set; } - public int? FoundNotIndexedConferenceProceedings { get; set; } - public int? FoundCompletedBook { get; set; } - public int? FoundOrganizedBook { get; set; } - public int? FoundBookChapters { get; set; } - public int? FoundBookTranslations { get; set; } - public int? FoundParticipationEditorialCommittees { get; set; } - #endregion - - #region (Resultados) Produção Artístca e Cultural - Produção Apresentada - public int? FoundFullComposerSoloOrchestraAllTracks { get; set; } - public int? FoundFullComposerSoloOrchestraCompilation { get; set; } - public int? FoundChamberOrchestraInterpretation { get; set; } - public int? FoundIndividualAndCollectiveArtPerformances { get; set; } - public int? FoundScientificCulturalArtisticCollectionsCuratorship { get; set; } - #endregion - - #region (Resultados) Produção Técnica - Produtos Registrados - public int? FoundPatentLetter { get; set; } - public int? FoundPatentDeposit { get; set; } - public int? FoundSoftwareRegistration { get; set; } - #endregion - - #region Critérios de Avaliação - public int? APIndex { get; set; } - public int? Qualification { get; set; } - public int? ProjectProposalObjectives { get; set; } - public int? AcademicScientificProductionCoherence { get; set; } - public int? ProposalMethodologyAdaptation { get; set; } - public int? EffectiveContributionToResearch { get; set; } - #endregion -} \ No newline at end of file diff --git a/src/Domain/Contracts/Student/DetailedReadStudentOutput.cs b/src/Domain/Contracts/Student/DetailedReadStudentOutput.cs deleted file mode 100644 index b1e8b63b..00000000 --- a/src/Domain/Contracts/Student/DetailedReadStudentOutput.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Contracts.Course; -using Domain.Contracts.User; - -namespace Domain.Contracts.Student -{ - public class DetailedReadStudentOutput : BaseStudentContract - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - public UserReadOutput? User { get; set; } - public DetailedReadCourseOutput? Course { get; set; } - public DetailedReadCampusOutput? Campus { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Student/ResumedReadStudentOutput.cs b/src/Domain/Contracts/Student/ResumedReadStudentOutput.cs deleted file mode 100644 index b2552b86..00000000 --- a/src/Domain/Contracts/Student/ResumedReadStudentOutput.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Domain.Contracts.Student -{ - public class ResumedReadStudentOutput : BaseStudentContract - { - public Guid Id { get; set; } - public string? Name { get; set; } - public string? Email { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/Student/UpdateStudentInput.cs b/src/Domain/Contracts/Student/UpdateStudentInput.cs deleted file mode 100644 index 28a06c25..00000000 --- a/src/Domain/Contracts/Student/UpdateStudentInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.Student -{ - public class UpdateStudentInput : BaseStudentContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/StudentDocuments/CreateStudentDocumentsInput.cs b/src/Domain/Contracts/StudentDocuments/CreateStudentDocumentsInput.cs deleted file mode 100644 index b47402aa..00000000 --- a/src/Domain/Contracts/StudentDocuments/CreateStudentDocumentsInput.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; - -namespace Domain.Contracts.StudentDocuments; -public class CreateStudentDocumentsInput -{ - [Required] - public Guid? ProjectId { get; set; } - - #region Documents - /// - /// Cópia do documento de identidade com foto. - /// - [Required] - public IFormFile? IdentityDocument { get; set; } - - /// - /// Cópia do CPF - /// - [Required] - public IFormFile? CPF { get; set; } - - /// - /// Foto 3x4 - /// - [Required] - public IFormFile? Photo3x4 { get; set; } - - /// - /// Cópia atualizada do Histórico Escolar - /// - [Required] - public IFormFile? SchoolHistory { get; set; } - - /// - /// Termo de Compromisso do Bolsista assinado (Anexo II ou disponível na página do PIBIC) no caso de bolsista do CEFET/RJ - /// - [Required] - public IFormFile? ScholarCommitmentAgreement { get; set; } - - /// - /// Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET) - /// - public IFormFile? ParentalAuthorization { get; set; } - #endregion - - #region BankData - /// - /// Número da Agência - /// - public string? AgencyNumber { get; set; } - /// - /// Número da Conta Corrente - /// - public string? AccountNumber { get; set; } - - /// - /// Comprovante de Abertura de Conta - /// - [Required] - public IFormFile? AccountOpeningProof { get; set; } - #endregion -} \ No newline at end of file diff --git a/src/Domain/Contracts/StudentDocuments/DetailedReadStudentDocumentsOutput.cs b/src/Domain/Contracts/StudentDocuments/DetailedReadStudentDocumentsOutput.cs deleted file mode 100644 index ce47fa2c..00000000 --- a/src/Domain/Contracts/StudentDocuments/DetailedReadStudentDocumentsOutput.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Domain.Contracts.StudentDocuments; -public class DetailedReadStudentDocumentsOutput : BaseStudentDocumentsOutput -{ - public DateTime? DeletedAt { get; set; } -} \ No newline at end of file diff --git a/src/Domain/Contracts/StudentDocuments/ResumedReadStudentDocumentsOutput.cs b/src/Domain/Contracts/StudentDocuments/ResumedReadStudentDocumentsOutput.cs deleted file mode 100644 index 132f0af2..00000000 --- a/src/Domain/Contracts/StudentDocuments/ResumedReadStudentDocumentsOutput.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Domain.Contracts.StudentDocuments; -public class ResumedReadStudentDocumentsOutput : BaseStudentDocumentsOutput { } diff --git a/src/Domain/Contracts/StudentDocuments/UpdateStudentDocumentsInput.cs b/src/Domain/Contracts/StudentDocuments/UpdateStudentDocumentsInput.cs deleted file mode 100644 index 6c40bc1e..00000000 --- a/src/Domain/Contracts/StudentDocuments/UpdateStudentDocumentsInput.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Domain.Contracts.StudentDocuments; -public class UpdateStudentDocumentsInput -{ - /// - /// Cópia do documento de identidade com foto. - /// - public IFormFile? IdentityDocument { get; set; } - - /// - /// Cópia do CPF - /// - public IFormFile? CPF { get; set; } - - /// - /// Foto 3x4 - /// - public IFormFile? Photo3x4 { get; set; } - - /// - /// Cópia atualizada do Histórico Escolar - /// - public IFormFile? SchoolHistory { get; set; } - - /// - /// Termo de Compromisso do Bolsista assinado (Anexo II ou disponível na página do PIBIC) no caso de bolsista do CEFET/RJ - /// - public IFormFile? ScholarCommitmentAgreement { get; set; } - - /// - /// Autorização dos pais ou responsáveis legais, em caso de aluno menor de 18 anos (Anexo 3 do Edital PIBIC ou modelo disponível na página da COPET) - /// - public IFormFile? ParentalAuthorization { get; set; } - - /// - /// Número da Agência - /// - public string? AgencyNumber { get; set; } - /// - /// Número da Conta Corrente - /// - public string? AccountNumber { get; set; } - - /// - /// Comprovante de Abertura de Conta - /// - public IFormFile? AccountOpeningProof { get; set; } -} \ No newline at end of file diff --git a/src/Domain/Contracts/SubArea/CreateSubAreaInput.cs b/src/Domain/Contracts/SubArea/CreateSubAreaInput.cs deleted file mode 100644 index a920e4ca..00000000 --- a/src/Domain/Contracts/SubArea/CreateSubAreaInput.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.SubArea -{ - public class CreateSubAreaInput : BaseSubAreaContract - { - [Required] - public Guid? AreaId { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/TypeAssistance/BaseTypeAssistanceContract.cs b/src/Domain/Contracts/TypeAssistance/BaseTypeAssistanceContract.cs deleted file mode 100644 index 55a7957a..00000000 --- a/src/Domain/Contracts/TypeAssistance/BaseTypeAssistanceContract.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Domain.Contracts.TypeAssistance -{ - public abstract class BaseTypeAssistanceContract - { - [Required] - public string? Name { get; set; } - public string? Description { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/TypeAssistance/CreateTypeAssistanceInput.cs b/src/Domain/Contracts/TypeAssistance/CreateTypeAssistanceInput.cs deleted file mode 100644 index 3da6b10d..00000000 --- a/src/Domain/Contracts/TypeAssistance/CreateTypeAssistanceInput.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Domain.Contracts.TypeAssistance -{ - public class CreateTypeAssistanceInput : BaseTypeAssistanceContract - { - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/TypeAssistance/DetailedReadTypeAssistanceOutput.cs b/src/Domain/Contracts/TypeAssistance/DetailedReadTypeAssistanceOutput.cs deleted file mode 100644 index 52c91549..00000000 --- a/src/Domain/Contracts/TypeAssistance/DetailedReadTypeAssistanceOutput.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Domain.Contracts.TypeAssistance -{ - public class DetailedReadTypeAssistanceOutput : BaseTypeAssistanceContract - { - public Guid? Id { get; set; } - public DateTime? DeletedAt { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Contracts/TypeAssistance/ResumedReadTypeAssistanceOutput.cs b/src/Domain/Contracts/TypeAssistance/ResumedReadTypeAssistanceOutput.cs deleted file mode 100644 index 8dcffc0c..00000000 --- a/src/Domain/Contracts/TypeAssistance/ResumedReadTypeAssistanceOutput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.TypeAssistance -{ - public class ResumedReadTypeAssistanceOutput : BaseTypeAssistanceContract - { - public Guid? Id { get; set; } - } -} diff --git a/src/Domain/Contracts/TypeAssistance/UpdateTypeAssistanceInput.cs b/src/Domain/Contracts/TypeAssistance/UpdateTypeAssistanceInput.cs deleted file mode 100644 index 224985e9..00000000 --- a/src/Domain/Contracts/TypeAssistance/UpdateTypeAssistanceInput.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Domain.Contracts.TypeAssistance -{ - public class UpdateTypeAssistanceInput : BaseTypeAssistanceContract - { - public Guid? Id { get; set; } - } -} \ No newline at end of file diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj index ba9ab662..9d07fa04 100644 --- a/src/Domain/Domain.csproj +++ b/src/Domain/Domain.csproj @@ -1,24 +1,29 @@ + net7.0 enable enable - 0.0.1 + 0.1.0 + + + + \ No newline at end of file diff --git a/src/Domain/Entities/Activity.cs b/src/Domain/Entities/Activity.cs new file mode 100644 index 00000000..37051c63 --- /dev/null +++ b/src/Domain/Entities/Activity.cs @@ -0,0 +1,97 @@ +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + public class Activity : Entity + { + #region Properties + private string? _name; + /// + /// Nome da atividade + /// + public string? Name + { + get => _name; + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(Name))); + EntityExceptionValidation.When(value?.Length < 3, + ExceptionMessageFactory.MinLength(nameof(Name), 3)); + EntityExceptionValidation.When(value?.Length > 300, + ExceptionMessageFactory.MaxLength(nameof(Name), 300)); + _name = value; + } + } + + private double? _points; + /// + /// Pontuação da atividade + /// + public double? Points + { + get => _points; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(Points))); + _points = value; + } + } + + private double? _limits; + /// + /// Limite de pontuação da atividade + /// + public double? Limits + { + get => _limits; + set => _limits = value ?? double.MaxValue; + } + + private Guid? _activityTypeId; + /// + /// Id do tipo de atividade + /// + public Guid? ActivityTypeId + { + get => _activityTypeId; + private set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(ActivityTypeId))); + _activityTypeId = value; + } + } + } + + public virtual ActivityType? ActivityType { get; } + #endregion + + #region Constructors + public Activity(string? name, double? points, double? limits, Guid? activityTypeId) + { + Name = name; + Points = points; + Limits = limits; + ActivityTypeId = activityTypeId; + } + + public Activity(Guid? id, string? name, double? points, double? limits, Guid? activityTypeId) + { + Id = id; + Name = name; + Points = points; + Limits = limits; + ActivityTypeId = activityTypeId; + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected Activity() { } + #endregion + } +} \ No newline at end of file diff --git a/src/Domain/Entities/ActivityType.cs b/src/Domain/Entities/ActivityType.cs new file mode 100644 index 00000000..5a796561 --- /dev/null +++ b/src/Domain/Entities/ActivityType.cs @@ -0,0 +1,83 @@ +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + public class ActivityType : Entity + { + #region Properties + private string? _name; + public string? Name + { + get { return _name; } + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(Name))); + EntityExceptionValidation.When(value?.Length < 3, + ExceptionMessageFactory.MinLength(nameof(Name), 3)); + EntityExceptionValidation.When(value?.Length > 300, + ExceptionMessageFactory.MaxLength(nameof(Name), 300)); + _name = value; + } + } + + private string? _unity; + public string? Unity + { + get { return _unity; } + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(Unity))); + EntityExceptionValidation.When(value?.Length < 3, + ExceptionMessageFactory.MinLength(nameof(Unity), 3)); + EntityExceptionValidation.When(value?.Length > 300, + ExceptionMessageFactory.MaxLength(nameof(Unity), 300)); + _unity = value; + } + } + + private Guid? _noticeId; + public Guid? NoticeId + { + get => _noticeId; + private set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required("Id do Edital")); + _noticeId = value; + } + } + } + + public virtual Notice? Notice { get; } + public virtual IList? Activities { get; } + #endregion + + #region Constructors + public ActivityType(string? name, string? unity, Guid? noticeId) + { + Name = name; + Unity = unity; + NoticeId = noticeId; + Activities = new List(); + } + + public ActivityType(Guid? id, string? name, string? unity, Guid? noticeId) + { + Id = id; + Name = name; + Unity = unity; + NoticeId = noticeId; + Activities = new List(); + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected ActivityType() { } + #endregion + } +} \ No newline at end of file diff --git a/src/Domain/Entities/Area.cs b/src/Domain/Entities/Area.cs index 2b59a7a6..024ce7a1 100644 --- a/src/Domain/Entities/Area.cs +++ b/src/Domain/Entities/Area.cs @@ -16,7 +16,7 @@ public Guid? MainAreaId set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Invalid("mainAreaId")); + ExceptionMessageFactory.Invalid("Id da Área Principal")); _mainAreaId = value; } } @@ -27,11 +27,11 @@ public string? Code set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required("code")); + ExceptionMessageFactory.Required("Código")); EntityExceptionValidation.When(value?.Length < 3, - ExceptionMessageFactory.MinLength("code", 3)); + ExceptionMessageFactory.MinLength("Código", 3)); EntityExceptionValidation.When(value?.Length > 100, - ExceptionMessageFactory.MaxLength("code", 100)); + ExceptionMessageFactory.MaxLength("Código", 100)); _code = value; } } @@ -42,11 +42,11 @@ public string? Name set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required("name")); + ExceptionMessageFactory.Required("Nome")); EntityExceptionValidation.When(value?.Length < 3, - ExceptionMessageFactory.MinLength("name", 3)); + ExceptionMessageFactory.MinLength("Nome", 3)); EntityExceptionValidation.When(value?.Length > 300, - ExceptionMessageFactory.MaxLength("name", 300)); + ExceptionMessageFactory.MaxLength("Nome", 300)); _name = value; } } diff --git a/src/Domain/Entities/AssistanceType.cs b/src/Domain/Entities/AssistanceType.cs new file mode 100644 index 00000000..1440e0d1 --- /dev/null +++ b/src/Domain/Entities/AssistanceType.cs @@ -0,0 +1,61 @@ +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + /// + /// Tipo de Bolsa de Assistência Estudantil + /// + public class AssistanceType : Entity + { + private string? _name; + public string? Name + { + get { return _name; } + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required("nome")); + EntityExceptionValidation.When(value?.Length < 3, + ExceptionMessageFactory.MinLength("nome", 3)); + EntityExceptionValidation.When(value?.Length > 100, + ExceptionMessageFactory.MaxLength("nome", 100)); + _name = value; + } + } + + private string? _description; + public string? Description + { + get { return _description; } + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required("descrição")); + EntityExceptionValidation.When(value?.Length < 3, + ExceptionMessageFactory.MinLength("descrição", 3)); + EntityExceptionValidation.When(value?.Length > 1500, + ExceptionMessageFactory.MaxLength("descrição", 1500)); + _description = value; + } + } + + public AssistanceType(string? name, string? description) + { + Name = name; + Description = description; + } + + public AssistanceType(Guid? id, string? name, string? description) + { + Id = id; + Name = name; + Description = description; + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected AssistanceType() { } + } +} \ No newline at end of file diff --git a/src/Domain/Entities/Campus.cs b/src/Domain/Entities/Campus.cs index ff4d5d28..0744d471 100644 --- a/src/Domain/Entities/Campus.cs +++ b/src/Domain/Entities/Campus.cs @@ -38,6 +38,6 @@ public Campus(Guid? id, string? name) /// /// Constructor to dbcontext EF instancing. /// - public Campus() { } + protected Campus() { } } } \ No newline at end of file diff --git a/src/Domain/Entities/Course.cs b/src/Domain/Entities/Course.cs index d11e3f2c..75395da8 100644 --- a/src/Domain/Entities/Course.cs +++ b/src/Domain/Entities/Course.cs @@ -38,6 +38,6 @@ public Course(Guid? id, string? name) /// /// Constructor to dbcontext EF instancing. /// - public Course() { } + protected Course() { } } } \ No newline at end of file diff --git a/src/Domain/Entities/Enums/EProjectStatus.cs b/src/Domain/Entities/Enums/EProjectStatus.cs index 52a7178e..e6700328 100644 --- a/src/Domain/Entities/Enums/EProjectStatus.cs +++ b/src/Domain/Entities/Enums/EProjectStatus.cs @@ -31,7 +31,7 @@ public enum EProjectStatus [Description("Cancelado: Projeto cancelado pelo professor ou administrador.")] Canceled, - [Description("Encerrado: Projeto concluído, o Orientador possui 30 dias para entregar o Relatório Final, do contrário sua conta será suspensa por XX anos.")] + [Description("Encerrado: Projeto concluído, o Orientador deve entregar o Relatório Final dentro do período estipulado no Edital, do contrário sua conta será suspensa.")] Closed } } \ No newline at end of file diff --git a/src/Domain/Entities/Enums/EScholarPerformance.cs b/src/Domain/Entities/Enums/EScholarPerformance.cs new file mode 100644 index 00000000..36f190ce --- /dev/null +++ b/src/Domain/Entities/Enums/EScholarPerformance.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Domain.Entities.Enums +{ + public enum EScholarPerformance + { + [Description("Ruim")] + Bad, + + [Description("Regular")] + Regular, + + [Description("Bom")] + Good, + + [Description("Muito Bom")] + VeryGood, + + [Description("Excelente")] + Excellent + } +} \ No newline at end of file diff --git a/src/Domain/Entities/Enums/EnumExtensions.cs b/src/Domain/Entities/Enums/EnumExtensions.cs index 547eef88..91796a02 100644 --- a/src/Domain/Entities/Enums/EnumExtensions.cs +++ b/src/Domain/Entities/Enums/EnumExtensions.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Reflection; +using Domain.Validation; namespace Domain.Entities.Enums; public static class EnumExtensions @@ -26,4 +27,31 @@ public static string GetDescription(this Enum value) return value.ToString(); } + + /// + /// Tenta converter um objeto para um tipo Enum. + /// + /// Valor a ser convertido. + /// Tipo para o qual ser convertido. + /// Objeto com tipo convertido. + public static T TryCastEnum(object? value) + { + try + { + EntityExceptionValidation.When(value is null, $"Valor não informado para o tipo Enum {typeof(T).ToString()}."); + foreach (T enumValue in Enum.GetValues(typeof(T))) + { + if (enumValue.GetHashCode().Equals(value)) + { + return (T)Enum.Parse(typeof(T), value?.ToString()!); + } + } + } + catch (Exception) + { + throw new EntityExceptionValidation($"Não foi possível converter o valor {value} para o tipo {typeof(T)}."); + } + + throw new EntityExceptionValidation($"Valor {value} fora do intervalo permitido para o tipo {typeof(T)}."); + } } \ No newline at end of file diff --git a/src/Domain/Entities/Notice.cs b/src/Domain/Entities/Notice.cs index 4f6f4659..402b0b70 100644 --- a/src/Domain/Entities/Notice.cs +++ b/src/Domain/Entities/Notice.cs @@ -9,36 +9,71 @@ namespace Domain.Entities public class Notice : Entity { #region Properties - private DateTime? _startDate; + #region Registration Dates + private DateTime? _registrationStartDate; /// - /// Data de início do edital. + /// Data de início das inscrições dos projetos. /// - public DateTime? StartDate + public DateTime? RegistrationStartDate { - get => _startDate; + get => _registrationStartDate; set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Invalid(nameof(StartDate))); - _startDate = value.HasValue ? value.Value.ToUniversalTime() : null; + ExceptionMessageFactory.Invalid("Data de início das inscrições dos projetos")); + _registrationStartDate = value; } } - private DateTime? _finalDate; + private DateTime? _registrationEndDate; /// - /// Data de término do edital. + /// Data de término das inscrições dos projetos. /// - public DateTime? FinalDate + public DateTime? RegistrationEndDate { - get => _finalDate; + get => _registrationEndDate; set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Invalid(nameof(FinalDate))); - _finalDate = value.HasValue ? value.Value.ToUniversalTime() : null; + ExceptionMessageFactory.Invalid("Data de término das inscrições dos projetos")); + _registrationEndDate = value; + } + } + #endregion + + #region Evaluation Dates + /// + /// Data de início das avaliações dos projetos. + /// + private DateTime? _evaluationStartDate; + public DateTime? EvaluationStartDate + { + get => _evaluationStartDate; + set + { + EntityExceptionValidation.When(!value.HasValue, + ExceptionMessageFactory.Invalid("Data de início das avaliações dos projetos")); + _evaluationStartDate = value; } } + /// + /// Data de término das avaliações dos projetos. + /// + private DateTime? _evaluationEndDate; + public DateTime? EvaluationEndDate + { + get => _evaluationEndDate; + set + { + EntityExceptionValidation.When(!value.HasValue, + ExceptionMessageFactory.Invalid("Data de término das avaliações dos projetos")); + _evaluationEndDate = value; + } + } + #endregion + + #region Appeal Dates private DateTime? _appealStartDate; /// /// Data de início do período de recurso. @@ -49,76 +84,188 @@ public DateTime? AppealStartDate set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Invalid(nameof(AppealStartDate))); - _appealStartDate = value.HasValue ? value.Value.ToUniversalTime() : null; + ExceptionMessageFactory.Invalid("Data de início do período de recurso")); + _appealStartDate = value; } } - private DateTime? _appealFinalDate; + private DateTime? _appealEndDate; /// /// Data de término do período de recurso. /// - public DateTime? AppealFinalDate + public DateTime? AppealEndDate { - get => _appealFinalDate; + get => _appealEndDate; set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Invalid(nameof(AppealFinalDate))); - _appealFinalDate = value.HasValue ? value.Value.ToUniversalTime() : null; + ExceptionMessageFactory.Invalid("Data de término do período de recurso")); + _appealEndDate = value; } } + #endregion - private int? _suspensionYears; + #region Sending Documentation Dates /// - /// Anos de suspensão do orientador em caso de não entrega do relatório final. + /// Data de início para entrega de documentação dos bolsistas. /// - public int? SuspensionYears + private DateTime? _sendingDocsStartDate; + public DateTime? SendingDocsStartDate { - get => _suspensionYears; + get => _sendingDocsStartDate; set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Required(nameof(SuspensionYears))); - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(SuspensionYears))); - _suspensionYears = value; + ExceptionMessageFactory.Invalid("Data de início para entrega de documentação dos bolsistas")); + _sendingDocsStartDate = value; + } + } + + /// + /// Data de término para entrega de documentação dos bolsistas. + /// + private DateTime? _sendingDocsEndDate; + public DateTime? SendingDocsEndDate + { + get => _sendingDocsEndDate; + set + { + EntityExceptionValidation.When(!value.HasValue, + ExceptionMessageFactory.Invalid("Data de término para entrega de documentação dos bolsistas")); + _sendingDocsEndDate = value; + } + } + #endregion + + #region Report Sending Dates + /// + /// Prazo de entrega do relatório parcial (Data fim). + /// + private DateTime? _partialReportDeadline; + public DateTime? PartialReportDeadline + { + get => _partialReportDeadline; + set + { + EntityExceptionValidation.When(!value.HasValue, + ExceptionMessageFactory.Invalid("Prazo de entrega do relatório parcial")); + _partialReportDeadline = value; } } - private int? _sendingDocumentationDeadline; /// - /// Prazo para envio da documentação. + /// Prazo de entrega do relatório final (Data fim). /// - public int? SendingDocumentationDeadline + private DateTime? _finalReportDeadline; + public DateTime? FinalReportDeadline { - get => _sendingDocumentationDeadline; + get => _finalReportDeadline; set { EntityExceptionValidation.When(!value.HasValue, - ExceptionMessageFactory.Required(nameof(SuspensionYears))); + ExceptionMessageFactory.Invalid("Prazo de entrega do relatório final")); + _finalReportDeadline = value; + } + } + #endregion + + private int? _suspensionYears; + /// + /// Anos de suspensão do orientador em caso de não entrega do relatório final. + /// + public int? SuspensionYears + { + get => _suspensionYears; + set + { + EntityExceptionValidation.When(!value.HasValue, + ExceptionMessageFactory.Required("Anos de suspensão do orientador")); EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(SuspensionYears))); - _sendingDocumentationDeadline = value; + ExceptionMessageFactory.Invalid("Anos de suspensão do orientador")); + _suspensionYears = value; } } + /// + /// URL do edital + /// + public string? DocUrl { get; set; } + /// /// Descrição do edital /// public string? Description { get; set; } /// - /// URL do edital + /// Data de criação do edital /// - public string? DocUrl { get; set; } + public DateTime? CreatedAt { get; protected set; } #endregion #region Constructors + public Notice(DateTime? registrationStartDate, + DateTime? registrationEndDate, + DateTime? evaluationStartDate, + DateTime? evaluationEndDate, + DateTime? appealStartDate, + DateTime? appealFinalDate, + DateTime? sendingDocsStartDate, + DateTime? sendingDocsEndDate, + DateTime? partialReportDeadline, + DateTime? finalReportDeadline, + string? description, + int? suspensionYears) + { + RegistrationStartDate = registrationStartDate; + RegistrationEndDate = registrationEndDate; + EvaluationStartDate = evaluationStartDate; + EvaluationEndDate = evaluationEndDate; + AppealStartDate = appealStartDate; + AppealEndDate = appealFinalDate; + SendingDocsStartDate = sendingDocsStartDate; + SendingDocsEndDate = sendingDocsEndDate; + SuspensionYears = suspensionYears; + PartialReportDeadline = partialReportDeadline; + FinalReportDeadline = finalReportDeadline; + Description = description; + CreatedAt = DateTime.UtcNow; + } + + public Notice( + Guid? id, + DateTime? registrationStartDate, + DateTime? registrationEndDate, + DateTime? evaluationStartDate, + DateTime? evaluationEndDate, + DateTime? appealStartDate, + DateTime? appealFinalDate, + DateTime? sendingDocsStartDate, + DateTime? sendingDocsEndDate, + DateTime? partialReportDeadline, + DateTime? finalReportDeadline, + string? description, + int? suspensionYears) + { + Id = id; + RegistrationStartDate = registrationStartDate; + RegistrationEndDate = registrationEndDate; + EvaluationStartDate = evaluationStartDate; + EvaluationEndDate = evaluationEndDate; + AppealStartDate = appealStartDate; + AppealEndDate = appealFinalDate; + SendingDocsStartDate = sendingDocsStartDate; + SendingDocsEndDate = sendingDocsEndDate; + SuspensionYears = suspensionYears; + PartialReportDeadline = partialReportDeadline; + FinalReportDeadline = finalReportDeadline; + Description = description; + CreatedAt = DateTime.UtcNow; + } + /// /// Constructor to dbcontext EF instancing. /// - public Notice() { } + protected Notice() { } #endregion } } \ No newline at end of file diff --git a/src/Domain/Entities/Primitives/Entity.cs b/src/Domain/Entities/Primitives/Entity.cs index 4ac7f0dd..e691e479 100644 --- a/src/Domain/Entities/Primitives/Entity.cs +++ b/src/Domain/Entities/Primitives/Entity.cs @@ -1,5 +1,4 @@ -using System; -namespace Domain.Entities.Primitives +namespace Domain.Entities.Primitives { public abstract class Entity { diff --git a/src/Domain/Entities/Professor.cs b/src/Domain/Entities/Professor.cs index c9b206dd..ac14d25c 100644 --- a/src/Domain/Entities/Professor.cs +++ b/src/Domain/Entities/Professor.cs @@ -59,21 +59,40 @@ public Guid? UserId } } - public virtual User? User { get; } + /// + /// Data de início da suspensão do professor + /// + private DateTime? _suspensionEndDate; + public DateTime? SuspensionEndDate + { + get => _suspensionEndDate; + set + { + _suspensionEndDate = value; + } + } + + public virtual User? User { get; set; } #endregion #region Constructors - public Professor(string? siapeEnrollment, long identifyLattes, Guid? userId) + public Professor(string? siapeEnrollment, long identifyLattes) + { + SIAPEEnrollment = siapeEnrollment; + IdentifyLattes = identifyLattes; + } + + public Professor(Guid? id, string? siapeEnrollment, long identifyLattes) { + Id = id; SIAPEEnrollment = siapeEnrollment; IdentifyLattes = identifyLattes; - UserId = userId; } /// /// Constructor to dbcontext EF instancing. /// - public Professor() { } + protected Professor() { } #endregion } } \ No newline at end of file diff --git a/src/Domain/Entities/ProgramType.cs b/src/Domain/Entities/ProgramType.cs index 466a4e27..07d7a428 100644 --- a/src/Domain/Entities/ProgramType.cs +++ b/src/Domain/Entities/ProgramType.cs @@ -57,6 +57,6 @@ public ProgramType(Guid? id, string name, string description) /// /// Constructor to dbcontext EF instancing. /// - public ProgramType() { } + protected ProgramType() { } } } \ No newline at end of file diff --git a/src/Domain/Entities/Project.cs b/src/Domain/Entities/Project.cs index 522bd3fa..de7e23fa 100644 --- a/src/Domain/Entities/Project.cs +++ b/src/Domain/Entities/Project.cs @@ -21,7 +21,7 @@ public string? Title set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Title))); _title = value; } } @@ -33,9 +33,9 @@ public string? KeyWord1 set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(KeyWord1))); EntityExceptionValidation.When(value!.Length > 100, - ExceptionMessageFactory.MaxLength(nameof(value), 100)); + ExceptionMessageFactory.MaxLength(nameof(KeyWord1), 100)); _keyWord1 = value; } } @@ -47,9 +47,9 @@ public string? KeyWord2 set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(KeyWord2))); EntityExceptionValidation.When(value!.Length > 100, - ExceptionMessageFactory.MaxLength(nameof(value), 100)); + ExceptionMessageFactory.MaxLength(nameof(KeyWord2), 100)); _keyWord2 = value; } } @@ -61,9 +61,9 @@ public string? KeyWord3 set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(KeyWord3))); EntityExceptionValidation.When(value!.Length > 100, - ExceptionMessageFactory.MaxLength(nameof(value), 100)); + ExceptionMessageFactory.MaxLength(nameof(KeyWord3), 100)); _keyWord3 = value; } } @@ -83,9 +83,9 @@ public string? Objective set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Objective))); EntityExceptionValidation.When(value?.Length > 1500, - ExceptionMessageFactory.MaxLength(nameof(value), 1500)); + ExceptionMessageFactory.MaxLength(nameof(Objective), 1500)); _objective = value; } } @@ -100,9 +100,9 @@ public string? Methodology set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Methodology))); EntityExceptionValidation.When(value?.Length > 1500, - ExceptionMessageFactory.MaxLength(nameof(value), 1500)); + ExceptionMessageFactory.MaxLength(nameof(Methodology), 1500)); _methodology = value; } } @@ -117,9 +117,9 @@ public string? ExpectedResults set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(ExpectedResults))); EntityExceptionValidation.When(value?.Length > 1500, - ExceptionMessageFactory.MaxLength(nameof(value), 1500)); + ExceptionMessageFactory.MaxLength(nameof(ExpectedResults), 1500)); _expectedResults = value; } } @@ -134,290 +134,12 @@ public string? ActivitiesExecutionSchedule set { EntityExceptionValidation.When(string.IsNullOrWhiteSpace(value), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(ActivitiesExecutionSchedule))); _activitiesExecutionSchedule = value; } } #endregion - #region Produção Científica - Trabalhos Publicados - private int? _workType1; - /// - /// Periódicos indexados nas bases do tipo 1 ou constantes na base QUALIS do estrato superior (A1, A2 e B1) (1). - /// - public int? WorkType1 - { - get => _workType1; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _workType1 = value; - } - } - - private int? _workType2; - /// - /// Periódicos indexados nas bases do tipo 2 ou constantes na base QUALIS do estrato inferior (B2, B3, B4, B5) (2). - /// - public int? WorkType2 - { - get => _workType2; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _workType2 = value; - } - } - - private int? _indexedConferenceProceedings; - /// - /// Anais de Congressos indexados (3a). - /// - public int? IndexedConferenceProceedings - { - get => _indexedConferenceProceedings; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _indexedConferenceProceedings = value; - } - } - - private int? _notIndexedConferenceProceedings; - /// - /// Anais de Congressos não indexados (3b). - /// - public int? NotIndexedConferenceProceedings - { - get => _notIndexedConferenceProceedings; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _notIndexedConferenceProceedings = value; - } - } - - private int? _completedBook; - /// - /// Livros - Completos - /// - public int? CompletedBook - { - get => _completedBook; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _completedBook = value; - } - } - - private int? _organizedBook; - /// - /// Livros - Organizados - /// - public int? OrganizedBook - { - get => _organizedBook; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _organizedBook = value; - } - } - - private int? _bookChapters; - /// - /// Livros - Capítulos - /// - public int? BookChapters - { - get => _bookChapters; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _bookChapters = value; - } - } - - private int? _bookTranslations; - /// - /// Livros - Tradução - /// - public int? BookTranslations - { - get => _bookTranslations; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _bookTranslations = value; - } - } - - private int? _participationEditorialCommittees; - /// - /// Participação em comissão editorial de editoras e instituições acadêmicas. - /// - public int? ParticipationEditorialCommittees - { - get => _participationEditorialCommittees; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _participationEditorialCommittees = value; - } - } - #endregion - - #region Produção Artístca e Cultural - Produção Apresentada - private int? _fullComposerSoloOrchestraAllTracks; - /// - /// Autoria ou coautoria de CD ou DVD publicado como compositor ou intérprete principal (solo, duo ou regência) em todas as faixas. - /// - public int? FullComposerSoloOrchestraAllTracks - { - get => _fullComposerSoloOrchestraAllTracks; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _fullComposerSoloOrchestraAllTracks = value; - } - } - - private int? _fullComposerSoloOrchestraCompilation; - /// - /// Autoria ou coautoria de CD ou DVD publicado como compositor ou intérprete principal (solo, duo ou regência) em coletânea (sem participação em todas as faixas). - /// - public int? FullComposerSoloOrchestraCompilation - { - get => _fullComposerSoloOrchestraCompilation; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _fullComposerSoloOrchestraCompilation = value; - } - } - - private int? _chamberOrchestraInterpretation; - /// - /// Participação em CD ou DVD como intérprete em grupo de câmara ou orquestra. - /// - public int? ChamberOrchestraInterpretation - { - get => _chamberOrchestraInterpretation; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _chamberOrchestraInterpretation = value; - } - } - - private int? _individualAndCollectiveArtPerformances; - /// - /// Apresentações individuais e coletivas no campo das artes. - /// - public int? IndividualAndCollectiveArtPerformances - { - get => _individualAndCollectiveArtPerformances; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _individualAndCollectiveArtPerformances = value; - } - } - - private int? _scientificCulturalArtisticCollectionsCuratorship; - /// - /// Curadoria de coleções ou exposições científicas, culturais e artísticas. - /// - public int? ScientificCulturalArtisticCollectionsCuratorship - { - get => _scientificCulturalArtisticCollectionsCuratorship; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _scientificCulturalArtisticCollectionsCuratorship = value; - } - } - #endregion - - #region Produção Técnica - Produtos Registrados - private int? _patentLetter; - /// - /// Carta patente com titularidade do CEFET/RJ. - /// - public int? PatentLetter - { - get => _patentLetter; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _patentLetter = value; - } - } - - private int? _patentDeposit; - /// - /// Depósito de patente com titularidade do CEFET/RJ. - /// - public int? PatentDeposit - { - get => _patentDeposit; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _patentDeposit = value; - } - } - - private int? _softwareRegistration; - /// - /// Registro de Software. - /// - public int? SoftwareRegistration - { - get => _softwareRegistration; - set - { - value ??= 0; - EntityExceptionValidation.When(value < 0, - ExceptionMessageFactory.Invalid(nameof(value))); - _softwareRegistration = value; - } - } - #endregion - #region Relacionamentos public Guid? StudentId; @@ -429,7 +151,7 @@ public Guid? ProgramTypeId { { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(ProgramTypeId))); _programTypeId = value; } } @@ -439,11 +161,11 @@ public Guid? ProgramTypeId public Guid? ProfessorId { get => _professorId; - private set + set { { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(ProfessorId))); _professorId = value; } } @@ -457,7 +179,7 @@ public Guid? SubAreaId { { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(SubAreaId))); _subAreaId = value; } } @@ -467,21 +189,21 @@ public Guid? SubAreaId public Guid? NoticeId { get => _noticeId; - private set + set { { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(NoticeId))); _noticeId = value; } } } - public virtual ProgramType? ProgramType { get; } - public virtual Professor? Professor { get; } - public virtual Student? Student { get; } - public virtual SubArea? SubArea { get; } - public virtual Notice? Notice { get; } + public virtual ProgramType? ProgramType { get; set; } + public virtual Professor? Professor { get; set; } + public virtual Student? Student { get; set; } + public virtual SubArea? SubArea { get; set; } + public virtual Notice? Notice { get; set; } #endregion #region Informações de Controle @@ -503,40 +225,84 @@ private set /// /// Data de submissão do projeto na plataforma. /// - public DateTime? SubmissionDate { get; set; } + private DateTime? _submissionDate; + public DateTime? SubmissionDate + { + get { return _submissionDate; } + set { _submissionDate = value; } + } /// /// Data de ressubmissão do projeto na plataforma. /// - public DateTime? ResubmissionDate { get; set; } + private DateTime? _appealDate; + public DateTime? AppealDate + { + get { return _appealDate; } + set { _appealDate = value; } + } /// /// Data de cancelamento do projeto. /// - public DateTime? CancellationDate { get; set; } + private DateTime? _cancellationDate; + public DateTime? CancellationDate + { + get { return _cancellationDate; } + set { _cancellationDate = value; } + } /// /// Razão de cancelamento do projeto, preenchido pelo professor. /// public string? CancellationReason { get; set; } + + /// + /// URL do certificado do projeto. + /// + public string? CertificateUrl { get; set; } #endregion #endregion #region Constructors - public Project(string title, string keyWord1, string keyWord2, string keyWord3, bool isScholarshipCandidate, - string objective, string methodology, string expectedResults, string activitiesExecutionSchedule, - int? workType1, int? workType2, int? indexedConferenceProceedings, int? notIndexedConferenceProceedings, - int? completedBook, int? organizedBook, int? bookChapters, int? bookTranslations, - int? participationEditorialCommittees, int? fullComposerSoloOrchestraAllTracks, - int? fullComposerSoloOrchestraCompilation, int? chamberOrchestraInterpretation, - int? individualAndCollectiveArtPerformances, int? scientificCulturalArtisticCollectionsCuratorship, - int? patentLetter, int? patentDeposit, int? softwareRegistration, - Guid studentId, Guid programTypeId, Guid professorId, Guid subAreaId, Guid noticeId, - EProjectStatus? status, string statusDescription, string appealDescription, + public Project(string? title, string? keyWord1, string? keyWord2, string? keyWord3, bool isScholarshipCandidate, + string? objective, string? methodology, string? expectedResults, string? activitiesExecutionSchedule, + Guid? studentId, Guid? programTypeId, Guid? professorId, Guid? subAreaId, Guid? noticeId, + EProjectStatus? status, string? statusDescription, string? appealDescription, + DateTime? submitionDate, DateTime? ressubmissionDate, DateTime? cancellationDate, + string? cancellationReason) + { + Title = title; + KeyWord1 = keyWord1; + KeyWord2 = keyWord2; + KeyWord3 = keyWord3; + IsScholarshipCandidate = isScholarshipCandidate; + Objective = objective; + Methodology = methodology; + ExpectedResults = expectedResults; + ActivitiesExecutionSchedule = activitiesExecutionSchedule; + StudentId = studentId; + ProgramTypeId = programTypeId; + ProfessorId = professorId; + SubAreaId = subAreaId; + NoticeId = noticeId; + Status = status; + StatusDescription = statusDescription; + AppealObservation = appealDescription; + SubmissionDate = submitionDate; + AppealDate = ressubmissionDate; + CancellationDate = cancellationDate; + CancellationReason = cancellationReason; + } + + public Project(Guid? id, string? title, string? keyWord1, string? keyWord2, string? keyWord3, bool isScholarshipCandidate, + string? objective, string? methodology, string? expectedResults, string? activitiesExecutionSchedule, + Guid? studentId, Guid? programTypeId, Guid? professorId, Guid? subAreaId, Guid? noticeId, + EProjectStatus? status, string? statusDescription, string? appealDescription, DateTime? submitionDate, DateTime? ressubmissionDate, DateTime? cancellationDate, - string cancellationReason) + string? cancellationReason) { - // Inicializar as propriedades + Id = id; Title = title; KeyWord1 = keyWord1; KeyWord2 = keyWord2; @@ -546,23 +312,6 @@ public Project(string title, string keyWord1, string keyWord2, string keyWord3, Methodology = methodology; ExpectedResults = expectedResults; ActivitiesExecutionSchedule = activitiesExecutionSchedule; - WorkType1 = workType1; - WorkType2 = workType2; - IndexedConferenceProceedings = indexedConferenceProceedings; - NotIndexedConferenceProceedings = notIndexedConferenceProceedings; - CompletedBook = completedBook; - OrganizedBook = organizedBook; - BookChapters = bookChapters; - BookTranslations = bookTranslations; - ParticipationEditorialCommittees = participationEditorialCommittees; - FullComposerSoloOrchestraAllTracks = fullComposerSoloOrchestraAllTracks; - FullComposerSoloOrchestraCompilation = fullComposerSoloOrchestraCompilation; - ChamberOrchestraInterpretation = chamberOrchestraInterpretation; - IndividualAndCollectiveArtPerformances = individualAndCollectiveArtPerformances; - ScientificCulturalArtisticCollectionsCuratorship = scientificCulturalArtisticCollectionsCuratorship; - PatentLetter = patentLetter; - PatentDeposit = patentDeposit; - SoftwareRegistration = softwareRegistration; StudentId = studentId; ProgramTypeId = programTypeId; ProfessorId = professorId; @@ -572,7 +321,7 @@ public Project(string title, string keyWord1, string keyWord2, string keyWord3, StatusDescription = statusDescription; AppealObservation = appealDescription; SubmissionDate = submitionDate; - ResubmissionDate = ressubmissionDate; + AppealDate = ressubmissionDate; CancellationDate = cancellationDate; CancellationReason = cancellationReason; } @@ -580,7 +329,7 @@ public Project(string title, string keyWord1, string keyWord2, string keyWord3, /// /// Constructor to dbcontext EF instancing. /// - public Project() { } + protected Project() { } #endregion } } \ No newline at end of file diff --git a/src/Domain/Entities/ProjectActivity.cs b/src/Domain/Entities/ProjectActivity.cs new file mode 100644 index 00000000..d43b0f71 --- /dev/null +++ b/src/Domain/Entities/ProjectActivity.cs @@ -0,0 +1,105 @@ +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + public class ProjectActivity : Entity + { + #region Properties + private int? _informedActivities; + public int? InformedActivities + { + get => _informedActivities; + set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(InformedActivities))); + _informedActivities = value; + } + } + } + private int? _foundActivities; + public int? FoundActivities + { + get => _foundActivities; + set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(FoundActivities))); + _foundActivities = value; + } + } + } + + private Guid? _projectId; + public Guid? ProjectId + { + get => _projectId; + set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(ProjectId))); + _projectId = value; + } + } + } + + private Guid? _activityId; + public Guid? ActivityId + { + get => _activityId; + set + { + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(ActivityId))); + _activityId = value; + } + } + } + + public virtual Project? Project { get; set; } + public virtual Activity? Activity { get; set; } + #endregion + + #region Constructors + public ProjectActivity(Guid? projectId, Guid? activityId, int? informedActivities, int? foundActivities) + { + ProjectId = projectId; + ActivityId = activityId; + InformedActivities = informedActivities; + FoundActivities = foundActivities; + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected ProjectActivity() { } + #endregion + + #region Methods + /// + /// Calcula a pontuação da Atividade. + /// + /// Total obtido com o cálculo. + public double CalculatePoints() + { + // Verifica se a Atividade está preenchida para calcular a pontuação. + EntityExceptionValidation.When(Activity is null, + ExceptionMessageFactory.BusinessRuleViolation("Atividade não informada, não é possível calcular a pontuação.")); + + // A pontuação é calculada multiplicando a quantidade de atividades encontradas pela pontuação da atividade. + double points = Activity!.Points!.Value * FoundActivities!.Value; + + // Verifica se limite foi informado, caso não tenha sido, utiliza o valor máximo do tipo. + double limits = Activity!.Limits.HasValue ? (double)Activity.Limits.Value : double.MaxValue; + + // Se a pontuação calculada for maior que o limite da atividade, retorna o limite da atividade, caso contrário, retorna a pontuação calculada. + return limits < points ? limits : points; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Domain/Entities/ProjectEvaluation.cs b/src/Domain/Entities/ProjectEvaluation.cs index a9ef4182..a1e1f0db 100644 --- a/src/Domain/Entities/ProjectEvaluation.cs +++ b/src/Domain/Entities/ProjectEvaluation.cs @@ -1,12 +1,11 @@ using Domain.Entities.Enums; +using Domain.Entities.Primitives; using Domain.Validation; namespace Domain.Entities { - public class ProjectEvaluation + public class ProjectEvaluation : Entity { - public Guid? Id { get; protected set; } - #region Properties #region Informações Gerais da Avaliação private Guid? _projectId; @@ -16,7 +15,7 @@ public Guid? ProjectId set { EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(IsProductivityFellow))); + ExceptionMessageFactory.Required(nameof(ProjectId))); _projectId = value; } } @@ -90,7 +89,7 @@ public string? SubmissionEvaluationDescription get => _submissionEvaluationDescription; set { - EntityExceptionValidation.When(value is null, + EntityExceptionValidation.When(string.IsNullOrEmpty(value), ExceptionMessageFactory.Required(nameof(SubmissionEvaluationDescription))); _submissionEvaluationDescription = value; } @@ -104,7 +103,12 @@ public string? SubmissionEvaluationDescription /// /// Data da avaliação do recurso. /// - public DateTime? AppealEvaluationDate { get; set; } + private DateTime? _appealEvaluationDate; + public DateTime? AppealEvaluationDate + { + get { return _appealEvaluationDate; } + set { _appealEvaluationDate = value; } + } /// /// Status da avaliação do recurso. @@ -124,208 +128,113 @@ public string? SubmissionEvaluationDescription /// /// Data da avaliação da documentação do projeto. /// - public DateTime? DocumentsEvaluationDate { get; set; } + private DateTime? _documentsEvaluationDate; + public DateTime? DocumentsEvaluationDate + { + get { return _documentsEvaluationDate; } + set { _documentsEvaluationDate = value; } + } /// /// Nota da avaliação da documentação do projeto. /// public string? DocumentsEvaluationDescription { get; set; } - public virtual Project? Project { get; } + public virtual Project? Project { get; set; } public virtual User? SubmissionEvaluator { get; } public virtual User? AppealEvaluator { get; } public virtual User? DocumentsEvaluator { get; } #endregion Informações Gerais da Avaliação - #region (Resultados) Produção Científica - Trabalhos Publicados - public int? FoundWorkType1 - { - get => FoundWorkType1; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundWorkType1))); - } - - public int? FoundWorkType2 - { - get => FoundWorkType2; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundWorkType2))); - } - - public int? FoundIndexedConferenceProceedings - { - get => FoundIndexedConferenceProceedings; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundIndexedConferenceProceedings))); - } - - public int? FoundNotIndexedConferenceProceedings - { - get => FoundNotIndexedConferenceProceedings; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundNotIndexedConferenceProceedings))); - } - - public int? FoundCompletedBook - { - get => FoundCompletedBook; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundCompletedBook))); - } - - public int? FoundOrganizedBook - { - get => FoundOrganizedBook; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundOrganizedBook))); - } - - public int? FoundBookChapters - { - get => FoundBookChapters; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundBookChapters))); - } - - public int? FoundBookTranslations - { - get => FoundBookTranslations; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundBookTranslations))); - } - - public int? FoundParticipationEditorialCommittees - { - get => FoundParticipationEditorialCommittees; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundParticipationEditorialCommittees))); - } - #endregion - - #region (Resultados) Produção Artístca e Cultural - Produção Apresentada - public int? FoundFullComposerSoloOrchestraAllTracks - { - get => FoundFullComposerSoloOrchestraAllTracks; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundFullComposerSoloOrchestraAllTracks))); - } - - public int? FoundFullComposerSoloOrchestraCompilation - { - get => FoundFullComposerSoloOrchestraCompilation; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundFullComposerSoloOrchestraCompilation))); - } - - public int? FoundChamberOrchestraInterpretation - { - get => FoundChamberOrchestraInterpretation; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundChamberOrchestraInterpretation))); - } - - public int? FoundIndividualAndCollectiveArtPerformances - { - get => FoundIndividualAndCollectiveArtPerformances; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundIndividualAndCollectiveArtPerformances))); - } - - public int? FoundScientificCulturalArtisticCollectionsCuratorship - { - get => FoundScientificCulturalArtisticCollectionsCuratorship; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundScientificCulturalArtisticCollectionsCuratorship))); - } - #endregion - - #region (Resultados) Produção Técnica - Produtos Registrados - public int? FoundPatentLetter - { - get => FoundPatentLetter; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundPatentLetter))); - } - - public int? FoundPatentDeposit - { - get => FoundPatentDeposit; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundPatentDeposit))); - } - - public int? FoundSoftwareRegistration - { - get => FoundSoftwareRegistration; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(FoundSoftwareRegistration))); - } - #endregion - #region Critérios de Avaliação /// /// Pontuação Total (Índice AP). /// - public int? APIndex - { - get => APIndex; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(APIndex))); - } + public double APIndex { get; set; } + + /// + /// Pontuação Total Final. + /// + public double FinalScore { get; set; } /// /// Titulação do Orientador. /// Doutor (2); Mestre (1). /// + private EQualification? _qualification; public EQualification? Qualification { - get => Qualification; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(Qualification))); + get => _qualification; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(Qualification))); + _qualification = value; + } } /// /// Foco e clareza quanto aos objetivos da proposta de projeto a ser desenvolvido pelo aluno. /// Excelente (4); Bom (3); Regular (2); Fraco (1). /// + private EScore? _projectProposalObjectives; public EScore? ProjectProposalObjectives { - get => ProjectProposalObjectives; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(ProjectProposalObjectives))); + get => _projectProposalObjectives; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(ProjectProposalObjectives))); + _projectProposalObjectives = value; + } } /// /// Coerência entre a produção acadêmico-científica do orientador e a proposta de projeto. /// Excelente (4); Bom (3); Regular (2); Fraco (1). /// + private EScore? _academicScientificProductionCoherence; public EScore? AcademicScientificProductionCoherence { - get => AcademicScientificProductionCoherence; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(AcademicScientificProductionCoherence))); + get => _academicScientificProductionCoherence; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(AcademicScientificProductionCoherence))); + _academicScientificProductionCoherence = value; + } } /// /// Adequação da metodologia da proposta aos objetivos e ao cronograma de execução. /// Excelente (4); Bom (3); Regular (2); Fraco (1). /// + private EScore? _proposalMethodologyAdaptation; public EScore? ProposalMethodologyAdaptation { - get => ProposalMethodologyAdaptation; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(ProposalMethodologyAdaptation))); + get => _proposalMethodologyAdaptation; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(ProposalMethodologyAdaptation))); + _proposalMethodologyAdaptation = value; + } } /// /// Contribuição efetiva da proposta de projeto para formação em pesquisa do aluno. /// Excelente (4); Bom (3); Regular (2); Fraco (1). /// + private EScore? _effectiveContributionToResearch; public EScore? EffectiveContributionToResearch { - get => EffectiveContributionToResearch; - set => EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(EffectiveContributionToResearch))); + get => _effectiveContributionToResearch; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(EffectiveContributionToResearch))); + _effectiveContributionToResearch = value; + } } #endregion #endregion @@ -334,7 +243,53 @@ public EScore? EffectiveContributionToResearch /// /// Constructor to dbcontext EF instancing. /// - public ProjectEvaluation() { } + protected ProjectEvaluation() { } + + public ProjectEvaluation(Guid? projectId, + bool? isProductivityFellow, + Guid? submissionEvaluatorId, + EProjectStatus? submissionEvaluationStatus, + DateTime? submissionEvaluationDate, + string? submissionEvaluationDescription, + EQualification? qualification, + EScore? projectProposalObjectives, + EScore? academicScientificProductionCoherence, + EScore? proposalMethodologyAdaptation, + EScore? effectiveContributionToResearch, + double apIndex) + { + ProjectId = projectId; + IsProductivityFellow = isProductivityFellow; + SubmissionEvaluatorId = submissionEvaluatorId; + SubmissionEvaluationStatus = submissionEvaluationStatus; + SubmissionEvaluationDate = submissionEvaluationDate; + Qualification = qualification; + ProjectProposalObjectives = projectProposalObjectives; + AcademicScientificProductionCoherence = academicScientificProductionCoherence; + ProposalMethodologyAdaptation = proposalMethodologyAdaptation; + EffectiveContributionToResearch = effectiveContributionToResearch; + APIndex = apIndex; + + // Define a descrição da avaliação da submissão. + SubmissionEvaluationDescription = submissionEvaluationStatus == EProjectStatus.Accepted + ? EProjectStatus.Accepted.GetDescription() + : submissionEvaluationDescription; + } + #endregion + + #region Methods + /// + /// Calcula a pontuação final do projeto considerando todos os critérios. + /// + public void CalculateFinalScore() + { + FinalScore = (double)Qualification! + + (double)ProjectProposalObjectives! + + (double)AcademicScientificProductionCoherence! + + (double)ProposalMethodologyAdaptation! + + (double)EffectiveContributionToResearch! + + APIndex; + } #endregion } } \ No newline at end of file diff --git a/src/Domain/Entities/ProjectFinalReport.cs b/src/Domain/Entities/ProjectFinalReport.cs new file mode 100644 index 00000000..67966c16 --- /dev/null +++ b/src/Domain/Entities/ProjectFinalReport.cs @@ -0,0 +1,81 @@ +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + public class ProjectFinalReport : Entity + { + /// + /// URL do relatório. + /// + public string? ReportUrl { get; set; } + + private DateTime? _sendDate; + /// + /// Data de envio do relatório. + /// + public DateTime? SendDate + { + get => _sendDate; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(SendDate))); + _sendDate = value; + } + } + + private Guid? _projectId; + /// + /// Id do projeto. + /// + public Guid? ProjectId + { + get => _projectId; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(ProjectId))); + _projectId = value; + } + } + + private Guid? _userId; + /// + /// Id do usuário que fez o envio do relatório. + /// + public Guid? UserId + { + get => _userId; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(UserId))); + _userId = value; + } + } + + public virtual Project? Project { get; set; } + public virtual User? User { get; set; } + + public ProjectFinalReport(Guid? projectId, Guid? userId) + { + SendDate = DateTime.UtcNow; + ProjectId = projectId; + UserId = userId; + } + + public ProjectFinalReport(Guid? id, Guid? projectId, Guid? userId) + { + Id = id; + SendDate = DateTime.UtcNow; + ProjectId = projectId; + UserId = userId; + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected ProjectFinalReport() { } + } +} \ No newline at end of file diff --git a/src/Domain/Entities/ProjectPartialReport.cs b/src/Domain/Entities/ProjectPartialReport.cs new file mode 100644 index 00000000..164b1fec --- /dev/null +++ b/src/Domain/Entities/ProjectPartialReport.cs @@ -0,0 +1,95 @@ +using Domain.Entities.Enums; +using Domain.Entities.Primitives; +using Domain.Validation; + +namespace Domain.Entities +{ + public class ProjectPartialReport : Entity + { + private int _currentDevelopmentStage; + /// + /// Estágio atual de desenvolvimento do Projeto de IC. + /// + /// Valor entre 0 e 100. + public int CurrentDevelopmentStage + { + get { return _currentDevelopmentStage; } + set + { + EntityExceptionValidation.When(value <= 0, + ExceptionMessageFactory.MinValue(nameof(CurrentDevelopmentStage), 0)); + EntityExceptionValidation.When(value >= 100, + ExceptionMessageFactory.MinValue(nameof(CurrentDevelopmentStage), 100)); + _currentDevelopmentStage = value; + } + } + + private EScholarPerformance? _scholarPerformance; + /// + /// Desempenho do bolsista segundo o orientador. + /// + /// Valores possíveis: "Ruim", "Regular", "Bom", "Muito Bom", "Excelente". + public EScholarPerformance? ScholarPerformance + { + get { return _scholarPerformance; } + set + { + EntityExceptionValidation.When(value == null, + ExceptionMessageFactory.Required(nameof(ScholarPerformance))); + _scholarPerformance = value; + } + } + + /// + /// Informações adicionais sobre o projeto. + /// + public string? AdditionalInfo { get; set; } + + private Guid? _projectId; + /// + /// Id do projeto. + /// + public Guid? ProjectId + { + get => _projectId; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(ProjectId))); + _projectId = value; + } + } + + private Guid? _userId; + /// + /// Id do usuário que fez o envio do relatório. + /// + public Guid? UserId + { + get => _userId; + set + { + EntityExceptionValidation.When(value is null, + ExceptionMessageFactory.Required(nameof(UserId))); + _userId = value; + } + } + + public virtual Project? Project { get; set; } + public virtual User? User { get; set; } + + public ProjectPartialReport(Guid? projectId, int currentDevelopmentStage, EScholarPerformance? scholarPerformance, string? additionalInfo, Guid? userId) + { + ProjectId = projectId; + CurrentDevelopmentStage = currentDevelopmentStage; + ScholarPerformance = scholarPerformance; + AdditionalInfo = additionalInfo; + UserId = userId; + } + + /// + /// Constructor to dbcontext EF instancing. + /// + protected ProjectPartialReport() { } + } +} \ No newline at end of file diff --git a/src/Domain/Entities/Student.cs b/src/Domain/Entities/Student.cs index 92df1456..880f284f 100644 --- a/src/Domain/Entities/Student.cs +++ b/src/Domain/Entities/Student.cs @@ -10,28 +10,52 @@ namespace Domain.Entities public class Student : Entity { #region Properties + private string? _registrationCode; + /// + /// Código de Matrícula + /// + public string? RegistrationCode + { + get { return _registrationCode; } + set + { + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(RegistrationCode))); + EntityExceptionValidation.When(value?.Length > 20, + ExceptionMessageFactory.MaxLength(nameof(RegistrationCode), 20)); + _registrationCode = value?.ToUpper(); + } + } + + private DateTime _birthDate; + /// + /// Data de Nascimento + /// public DateTime BirthDate { get { return _birthDate; } set { EntityExceptionValidation.When(value == default, - ExceptionMessageFactory.Required(nameof(value))); - EntityExceptionValidation.When(value >= DateTime.Now, - ExceptionMessageFactory.LessThan(nameof(value), DateTime.Now.ToString("dd/MM/yyyy"))); - _birthDate = value; + ExceptionMessageFactory.Required(nameof(BirthDate))); + EntityExceptionValidation.When(value >= DateTime.UtcNow, + ExceptionMessageFactory.LessThan(nameof(BirthDate), DateTime.UtcNow.ToString("dd/MM/yyyy"))); + _birthDate = value.ToUniversalTime(); } } private long _rg; + /// + /// RG + /// public long RG { get { return _rg; } set { EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(RG))); _rg = value; } } @@ -46,9 +70,9 @@ public string? IssuingAgency set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(IssuingAgency))); EntityExceptionValidation.When(value?.Length > 50, - ExceptionMessageFactory.MaxLength(nameof(value), 50)); + ExceptionMessageFactory.MaxLength(nameof(IssuingAgency), 50)); _issuingAgency = value; } } @@ -63,26 +87,27 @@ public DateTime DispatchDate set { EntityExceptionValidation.When(value == default, - ExceptionMessageFactory.Required(nameof(value))); - EntityExceptionValidation.When(value >= DateTime.Now, - ExceptionMessageFactory.LessThan(nameof(value), DateTime.Now.ToString("dd/MM/yyyy"))); - _dispatchDate = value; + ExceptionMessageFactory.Required(nameof(DispatchDate))); + EntityExceptionValidation.When(value >= DateTime.UtcNow, + ExceptionMessageFactory.LessThan(nameof(DispatchDate), + DateTime.UtcNow.ToString("dd/MM/yyyy"))); + _dispatchDate = value.ToUniversalTime(); } } + private EGender? _gender; /// /// Sexo: Masculino e Feminino /// - private EGender? _gender; public EGender? Gender { get { return _gender; } set { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(Gender))); EntityExceptionValidation.When(!Enum.TryParse(value.ToString(), out var _), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Gender))); _gender = value; } } @@ -97,9 +122,9 @@ public ERace? Race set { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(Race))); EntityExceptionValidation.When(!Enum.TryParse(value.ToString(), out var _), - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Race))); _race = value; } } @@ -114,54 +139,66 @@ public string? HomeAddress set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(HomeAddress))); EntityExceptionValidation.When(value?.Length > 300, - ExceptionMessageFactory.MaxLength(nameof(value), 300)); + ExceptionMessageFactory.MaxLength(nameof(HomeAddress), 300)); _homeAddress = value; } } private string? _city; + /// + /// Cidade + /// public string? City { get { return _city; } set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(City))); EntityExceptionValidation.When(value?.Length > 50, - ExceptionMessageFactory.MaxLength(nameof(value), 50)); + ExceptionMessageFactory.MaxLength(nameof(City), 50)); _city = value; } } private string? _uf; + /// + /// Estado + /// public string? UF { get { return _uf; } set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(UF))); EntityExceptionValidation.When(value?.Length != 2, - ExceptionMessageFactory.WithLength(nameof(value), 2)); + ExceptionMessageFactory.WithLength(nameof(UF), 2)); _uf = value; } } private long _cep; + /// + /// CEP + /// public long CEP { get { return _cep; } set { EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(CEP))); _cep = value; } } private int? _phoneDDD; + /// + /// DDD Telefone + /// public int? PhoneDDD { get { return _phoneDDD; } @@ -169,12 +206,15 @@ public int? PhoneDDD { if (value == null) return; EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(PhoneDDD))); _phoneDDD = value; } } private long? _phone; + /// + /// Telefone + /// public long? Phone { get { return _phone; } @@ -182,12 +222,15 @@ public long? Phone { if (value == null) return; EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(Phone))); _phone = value; } } private int? _cellPhoneDDD; + /// + /// DDD Celular + /// public int? CellPhoneDDD { get { return _cellPhoneDDD; } @@ -195,12 +238,15 @@ public int? CellPhoneDDD { if (value == null) return; EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(CellPhoneDDD))); _cellPhoneDDD = value; } } private long? _cellPhone; + /// + /// Celular + /// public long? CellPhone { get { return _cellPhone; } @@ -208,31 +254,37 @@ public long? CellPhone { if (value == null) return; EntityExceptionValidation.When(value <= 0, - ExceptionMessageFactory.Invalid(nameof(value))); + ExceptionMessageFactory.Invalid(nameof(CellPhone))); _cellPhone = value; } } private Guid? _campusId; + /// + /// ID do Campus + /// public Guid? CampusId { get { return _campusId; } set { EntityExceptionValidation.When(value == null || value == Guid.Empty, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(CampusId))); _campusId = value; } } private Guid? _courseId; + /// + /// ID do Curso + /// public Guid? CourseId { get { return _courseId; } set { EntityExceptionValidation.When(value == null || value == Guid.Empty, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(CourseId))); _courseId = value; } } @@ -247,30 +299,30 @@ public string? StartYear set { EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(StartYear))); _startYear = value; } } + private Guid? _assistanceTypeId; /// /// Tipo de Bolsa de Assistência Estudantil do aluno /// - private Guid? _TypeAssistanceId; - public Guid? TypeAssistanceId + public Guid? AssistanceTypeId { - get { return _TypeAssistanceId; } + get { return _assistanceTypeId; } set { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); - _TypeAssistanceId = value; + ExceptionMessageFactory.Required(nameof(AssistanceTypeId))); + _assistanceTypeId = value; } } + private Guid? _userId; /// /// ID do usuário /// - private Guid? _userId; public Guid? UserId { get { return _userId; } @@ -278,7 +330,7 @@ public Guid? UserId { { EntityExceptionValidation.When(value == null, - ExceptionMessageFactory.Required(nameof(value))); + ExceptionMessageFactory.Required(nameof(UserId))); _userId = value; } } @@ -287,20 +339,63 @@ public Guid? UserId public virtual User? User { get; } public virtual Campus? Campus { get; } public virtual Course? Course { get; } - public virtual TypeAssistance? TypeAssistance { get; } + public virtual AssistanceType? AssistanceType { get; } #endregion #region Constructors public Student( DateTime birthDate, long rg, - string issuingAgency, + string? issuingAgency, + DateTime dispatchDate, + EGender? gender, + ERace? race, + string? homeAddress, + string? city, + string? uf, + long cep, + int? phoneDDD, + long? phone, + int? cellPhoneDDD, + long? cellPhone, + Guid? campusId, + Guid? courseId, + string? startYear, + Guid? studentAssistanceProgramId, + string? registrationCode) + { + BirthDate = birthDate; + RG = rg; + IssuingAgency = issuingAgency; + DispatchDate = dispatchDate; + Gender = gender; + Race = race; + HomeAddress = homeAddress; + City = city; + UF = uf; + CEP = cep; + PhoneDDD = phoneDDD; + Phone = phone; + CellPhoneDDD = cellPhoneDDD; + CellPhone = cellPhone; + CampusId = campusId; + CourseId = courseId; + StartYear = startYear; + AssistanceTypeId = studentAssistanceProgramId; + RegistrationCode = registrationCode; + } + + public Student( + Guid id, + DateTime birthDate, + long rg, + string? issuingAgency, DateTime dispatchDate, EGender? gender, ERace? race, - string homeAddress, - string city, - string uf, + string? homeAddress, + string? city, + string? uf, long cep, int? phoneDDD, long? phone, @@ -308,10 +403,11 @@ public Student( long? cellPhone, Guid? campusId, Guid? courseId, - string startYear, + string? startYear, Guid? studentAssistanceProgramId, - Guid? userId) + string? registrationCode) { + Id = id; BirthDate = birthDate; RG = rg; IssuingAgency = issuingAgency; @@ -329,14 +425,14 @@ public Student( CampusId = campusId; CourseId = courseId; StartYear = startYear; - TypeAssistanceId = studentAssistanceProgramId; - UserId = userId; + AssistanceTypeId = studentAssistanceProgramId; + RegistrationCode = registrationCode; } /// /// Constructor to dbcontext EF instancing. /// - public Student() { } + protected Student() { } #endregion } } \ No newline at end of file diff --git a/src/Domain/Entities/StudentDocuments.cs b/src/Domain/Entities/StudentDocuments.cs index c179dc93..c43aa200 100644 --- a/src/Domain/Entities/StudentDocuments.cs +++ b/src/Domain/Entities/StudentDocuments.cs @@ -13,7 +13,7 @@ public Guid? ProjectId set { EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_projectId))); + ExceptionMessageFactory.Required(nameof(ProjectId))); _projectId = value; } } @@ -28,8 +28,8 @@ public string? IdentityDocument get => _identityDocument; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_identityDocument))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(IdentityDocument))); _identityDocument = value; } } @@ -43,8 +43,8 @@ public string? CPF get => _cpf; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_cpf))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(CPF))); _cpf = value; } } @@ -58,8 +58,8 @@ public string? Photo3x4 get => _photo3x4; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_photo3x4))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(Photo3x4))); _photo3x4 = value; } } @@ -73,8 +73,8 @@ public string? SchoolHistory get => _schoolHistory; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_schoolHistory))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(SchoolHistory))); _schoolHistory = value; } } @@ -88,8 +88,8 @@ public string? ScholarCommitmentAgreement get => _scholarCommitmentAgreement; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_scholarCommitmentAgreement))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(ScholarCommitmentAgreement))); _scholarCommitmentAgreement = value; } } @@ -103,15 +103,15 @@ public string? ParentalAuthorization get => _parentalAuthorization; set { - if (Project?.Student?.BirthDate >= DateTime.Now.AddYears(-18)) + if (Project?.Student?.BirthDate >= DateTime.UtcNow.AddYears(-18)) { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_parentalAuthorization))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(ParentalAuthorization))); } _parentalAuthorization = value; } } - #endregion + #endregion Documents #region BankData private string? _agencyNumber; @@ -123,10 +123,10 @@ public string? AgencyNumber get => _agencyNumber; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_agencyNumber))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(AgencyNumber))); EntityExceptionValidation.When(long.TryParse(value, out long tmp) && tmp <= 0, - ExceptionMessageFactory.Invalid(ExceptionMessageFactory.Invalid(nameof(_agencyNumber)))); + ExceptionMessageFactory.Invalid(ExceptionMessageFactory.Invalid(nameof(AgencyNumber)))); _agencyNumber = value; } } @@ -140,10 +140,10 @@ public string? AccountNumber get => _accountNumber; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_accountNumber))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(AccountNumber))); EntityExceptionValidation.When(long.TryParse(value, out long tmp) && tmp <= 0, - ExceptionMessageFactory.Invalid(ExceptionMessageFactory.Invalid(nameof(_accountNumber)))); + ExceptionMessageFactory.Invalid(ExceptionMessageFactory.Invalid(nameof(AccountNumber)))); _accountNumber = value; } } @@ -157,15 +157,15 @@ public string? AccountOpeningProof get => _accountOpeningProof; set { - EntityExceptionValidation.When(value is null, - ExceptionMessageFactory.Required(nameof(_accountOpeningProof))); + EntityExceptionValidation.When(string.IsNullOrEmpty(value), + ExceptionMessageFactory.Required(nameof(AccountOpeningProof))); _accountOpeningProof = value; } } - #endregion + #endregion BankData - public virtual Project? Project { get; } - #endregion + public virtual Project? Project { get; set; } + #endregion Properties /// /// Constructor to dbcontext EF instancing. diff --git a/src/Domain/Entities/TypeAssistance.cs b/src/Domain/Entities/TypeAssistance.cs deleted file mode 100644 index 02bd908e..00000000 --- a/src/Domain/Entities/TypeAssistance.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Domain.Entities.Primitives; -using Domain.Validation; - -namespace Domain.Entities -{ - /// - /// Tipo de Bolsa de Assistência Estudantil - /// - public class TypeAssistance : Entity - { - private string? _name; - public string? Name - { - get { return _name; } - set - { - EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required("nome")); - EntityExceptionValidation.When(value?.Length < 3, - ExceptionMessageFactory.MinLength("nome", 3)); - EntityExceptionValidation.When(value?.Length > 100, - ExceptionMessageFactory.MaxLength("nome", 100)); - _name = value; - } - } - - private string? _description; - public string? Description - { - get { return _description; } - set - { - EntityExceptionValidation.When(string.IsNullOrEmpty(value), - ExceptionMessageFactory.Required("descrição")); - EntityExceptionValidation.When(value?.Length < 3, - ExceptionMessageFactory.MinLength("descrição", 3)); - EntityExceptionValidation.When(value?.Length > 1500, - ExceptionMessageFactory.MaxLength("descrição", 1500)); - _description = value; - } - } - - public TypeAssistance(string? name, string? description) - { - Name = name; - Description = description; - } - - public TypeAssistance(Guid? id, string? name, string? description) - { - Id = id; - Name = name; - Description = description; - } - - /// - /// Constructor to dbcontext EF instancing. - /// - public TypeAssistance() { } - } -} \ No newline at end of file diff --git a/src/Domain/Entities/User.cs b/src/Domain/Entities/User.cs index 274f05f3..2ee0c4d2 100644 --- a/src/Domain/Entities/User.cs +++ b/src/Domain/Entities/User.cs @@ -1,5 +1,7 @@ using System.Data; using System.Net.Mail; +using System.Security.Cryptography; +using System.Text; using Domain.Entities.Enums; using Domain.Entities.Primitives; using Domain.Validation; @@ -87,6 +89,7 @@ public ERole? Role _role = value; } } + public bool IsConfirmed { get; private set; } private string? _validationCode; @@ -120,11 +123,32 @@ private set _forgotPasswordToken = value; } } + + public bool IsCoordinator { get; set; } #endregion #region Constructors - internal User(string? name, string? email, string? password, string? cpf, ERole? role) + public User(Guid? id, string? name, string? role) + { + Id = id; + Name = name; + Role = Enum.Parse(role!); + } + + public User(string? name, string? email, string? password, string? cpf, ERole? role) + { + Name = name; + Email = email; + Password = password; + CPF = cpf; + Role = role; + + GenerateEmailValidationCode(); + } + + public User(Guid? id, string? name, string? email, string? password, string? cpf, ERole? role) { + Id = id; Name = name; Email = email; Password = password; @@ -147,7 +171,7 @@ internal void GenerateEmailValidationCode() ValidationCode = GenerateValidationCode(); } - internal void ConfirmUserEmail(string validationCode) + public void ConfirmUserEmail(string validationCode) { EntityExceptionValidation.When(IsConfirmed, "O e-mail do usuário já foi confirmado."); @@ -159,9 +183,9 @@ internal void ConfirmUserEmail(string validationCode) #endregion #region Reset Password - internal void GenerateResetPasswordToken() => ResetPasswordToken = GenerateValidationCode(6); + public void GenerateResetPasswordToken() => ResetPasswordToken = GenerateValidationCode(6); - internal bool UpdatePassword(string password, string token) + public bool UpdatePassword(string password, string token) { if (ResetPasswordToken?.Equals(token) == true) { @@ -227,10 +251,18 @@ private static bool ValidateCPF(string cpf) private static string GenerateValidationCode(int size = 6) { - var random = new Random(); - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - return new string(Enumerable.Repeat(chars, size) - .Select(s => s[random.Next(s.Length)]).ToArray()); + const string allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + using var rng = RandomNumberGenerator.Create(); + byte[] randomBytes = new byte[size]; + rng.GetBytes(randomBytes); + + StringBuilder code = new(size); + foreach (byte b in randomBytes) + { + code.Append(allowedCharacters[b % allowedCharacters.Length]); + } + + return code.ToString(); } #endregion } diff --git a/src/Domain/Interfaces/Repositories/Bases/IGenericCRUDRepository.cs b/src/Domain/Interfaces/Repositories/Bases/IGenericCRUDRepository.cs index 5fc9fc3e..94f3f533 100644 --- a/src/Domain/Interfaces/Repositories/Bases/IGenericCRUDRepository.cs +++ b/src/Domain/Interfaces/Repositories/Bases/IGenericCRUDRepository.cs @@ -8,33 +8,35 @@ public interface IGenericCRUDRepository /// /// Id da entidade. /// Entidade encontrada. - Task GetById(Guid? id); + Task GetByIdAsync(Guid? id); /// /// Busca todas as entidades ativas. /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Lista de entidades ativas. - Task> GetAll(int skip, int take); + Task> GetAllAsync(int skip, int take); /// /// Cria entidade conforme parâmetros fornecidos. /// /// Parâmetros de criação. /// Entidade criada. - Task Create(T model); + Task CreateAsync(T model); /// /// Remove entidade através do Id informado. /// /// Id da entidade a ser removida. /// Entidade removida. - Task Delete(Guid? id); + Task DeleteAsync(Guid? id); /// /// Atualiza entidade conforme parâmetros fornecidos. /// /// Parâmetros de atualização. /// Entidade atualizada. - Task Update(T model); + Task UpdateAsync(T model); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IActivityRepository.cs b/src/Domain/Interfaces/Repositories/IActivityRepository.cs new file mode 100644 index 00000000..c384860e --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IActivityRepository.cs @@ -0,0 +1,7 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IActivityRepository : IGenericCRUDRepository { } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IActivityTypeRepository.cs b/src/Domain/Interfaces/Repositories/IActivityTypeRepository.cs new file mode 100644 index 00000000..2100fb40 --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IActivityTypeRepository.cs @@ -0,0 +1,11 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IActivityTypeRepository : IGenericCRUDRepository + { + Task> GetByNoticeIdAsync(Guid? noticeId); + Task> GetLastNoticeActivitiesAsync(); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IAreaRepository.cs b/src/Domain/Interfaces/Repositories/IAreaRepository.cs index e162877d..f509ba18 100644 --- a/src/Domain/Interfaces/Repositories/IAreaRepository.cs +++ b/src/Domain/Interfaces/Repositories/IAreaRepository.cs @@ -5,7 +5,7 @@ namespace Domain.Interfaces.Repositories { public interface IAreaRepository : IGenericCRUDRepository { - Task GetByCode(string? code); - Task> GetAreasByMainArea(Guid? mainAreaId, int skip, int take); + Task GetByCodeAsync(string? code); + Task> GetAreasByMainAreaAsync(Guid? mainAreaId, int skip, int take); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IAssistanceTypeRepository.cs b/src/Domain/Interfaces/Repositories/IAssistanceTypeRepository.cs new file mode 100644 index 00000000..43ef0913 --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IAssistanceTypeRepository.cs @@ -0,0 +1,10 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IAssistanceTypeRepository : IGenericCRUDRepository + { + Task GetAssistanceTypeByNameAsync(string name); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/ICampusRepository.cs b/src/Domain/Interfaces/Repositories/ICampusRepository.cs index 6c0aa8e2..bd1a61ff 100644 --- a/src/Domain/Interfaces/Repositories/ICampusRepository.cs +++ b/src/Domain/Interfaces/Repositories/ICampusRepository.cs @@ -5,6 +5,6 @@ namespace Domain.Interfaces.Repositories { public interface ICampusRepository : IGenericCRUDRepository { - Task GetCampusByName(string name); + Task GetCampusByNameAsync(string name); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/ICourseRepository.cs b/src/Domain/Interfaces/Repositories/ICourseRepository.cs index 4874ac60..87e761da 100644 --- a/src/Domain/Interfaces/Repositories/ICourseRepository.cs +++ b/src/Domain/Interfaces/Repositories/ICourseRepository.cs @@ -5,6 +5,6 @@ namespace Domain.Interfaces.Repositories { public interface ICourseRepository : IGenericCRUDRepository { - Task GetCourseByName(string name); + Task GetCourseByNameAsync(string name); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IMainAreaRepository.cs b/src/Domain/Interfaces/Repositories/IMainAreaRepository.cs index b078f1d3..63ddb802 100644 --- a/src/Domain/Interfaces/Repositories/IMainAreaRepository.cs +++ b/src/Domain/Interfaces/Repositories/IMainAreaRepository.cs @@ -5,6 +5,6 @@ namespace Domain.Interfaces.Repositories { public interface IMainAreaRepository : IGenericCRUDRepository { - Task GetByCode(string? code); + Task GetByCodeAsync(string? code); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/INoticeRepository.cs b/src/Domain/Interfaces/Repositories/INoticeRepository.cs index 28908083..83d40f28 100644 --- a/src/Domain/Interfaces/Repositories/INoticeRepository.cs +++ b/src/Domain/Interfaces/Repositories/INoticeRepository.cs @@ -5,6 +5,12 @@ namespace Domain.Interfaces.Repositories { public interface INoticeRepository : IGenericCRUDRepository { - Task GetNoticeByPeriod(DateTime start, DateTime end); + Task GetNoticeByPeriodAsync(DateTime start, DateTime end); + + /// + /// Busca edital que possui data final de entrega de relatório para o dia anterior. + /// + /// Edital que possui data final de entrega de relatório para o dia anterior. + Task GetNoticeEndingAsync(); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProfessorRepository.cs b/src/Domain/Interfaces/Repositories/IProfessorRepository.cs index 47b37147..f24723a3 100644 --- a/src/Domain/Interfaces/Repositories/IProfessorRepository.cs +++ b/src/Domain/Interfaces/Repositories/IProfessorRepository.cs @@ -5,5 +5,18 @@ namespace Domain.Interfaces.Repositories { public interface IProfessorRepository : IGenericCRUDRepository { + /// + /// Obtém todos os professores ativos. + /// + /// Lista de professores ativos. + /// Ativo significa que o professor não foi removido. + Task> GetAllActiveProfessorsAsync(); + + /// + /// Obtém professor pelo Id do usuário informado. + /// + /// Id do usuário. + /// Professor encontrado. + Task GetByUserIdAsync(Guid? userId); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProgramTypeRepository.cs b/src/Domain/Interfaces/Repositories/IProgramTypeRepository.cs index 1f5cc774..3e22ebd3 100644 --- a/src/Domain/Interfaces/Repositories/IProgramTypeRepository.cs +++ b/src/Domain/Interfaces/Repositories/IProgramTypeRepository.cs @@ -5,6 +5,6 @@ namespace Domain.Interfaces.Repositories { public interface IProgramTypeRepository : IGenericCRUDRepository { - Task GetProgramTypeByName(string name); + Task GetProgramTypeByNameAsync(string name); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProjectActivityRepository.cs b/src/Domain/Interfaces/Repositories/IProjectActivityRepository.cs new file mode 100644 index 00000000..8de2dc9b --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IProjectActivityRepository.cs @@ -0,0 +1,10 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IProjectActivityRepository : IGenericCRUDRepository + { + Task> GetByProjectIdAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProjectEvaluationRepository.cs b/src/Domain/Interfaces/Repositories/IProjectEvaluationRepository.cs index 714e90fa..e05820c5 100644 --- a/src/Domain/Interfaces/Repositories/IProjectEvaluationRepository.cs +++ b/src/Domain/Interfaces/Repositories/IProjectEvaluationRepository.cs @@ -9,27 +9,27 @@ public interface IProjectEvaluationRepository /// /// Id da avaliação. /// Avaliação de projeto encontrado. - Task GetById(Guid? id); + Task GetByIdAsync(Guid? id); /// /// Busca uma avaliação de projeto pelo id do projeto. /// /// Id do projeto em avaliação. /// Avaliação de projeto encontrado. - Task GetByProjectId(Guid? projectId); + Task GetByProjectIdAsync(Guid? projectId); /// /// Cria uma avaliação de projeto. /// /// Modelo da avaliação de projeto. /// Avaliação de projeto criado. - Task Create(ProjectEvaluation model); + Task CreateAsync(ProjectEvaluation model); /// /// Atualiza uma avaliação de projeto. /// /// Modelo da avaliação de projeto. /// Avaliação de projeto atualizado. - Task Update(ProjectEvaluation model); + Task UpdateAsync(ProjectEvaluation model); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProjectFinalReportRepository.cs b/src/Domain/Interfaces/Repositories/IProjectFinalReportRepository.cs new file mode 100644 index 00000000..c8fcd2ba --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IProjectFinalReportRepository.cs @@ -0,0 +1,15 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IProjectFinalReportRepository : IGenericCRUDRepository + { + /// + /// Busca relatório de projeto pelo Id do projeto informado. + /// + /// Id do projeto. + /// Relatório de projeto encontrado. + Task GetByProjectIdAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProjectPartialReportRepository.cs b/src/Domain/Interfaces/Repositories/IProjectPartialReportRepository.cs new file mode 100644 index 00000000..1d7f2d30 --- /dev/null +++ b/src/Domain/Interfaces/Repositories/IProjectPartialReportRepository.cs @@ -0,0 +1,15 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories.Bases; + +namespace Domain.Interfaces.Repositories +{ + public interface IProjectPartialReportRepository : IGenericCRUDRepository + { + /// + /// Busca relatório parcial de projeto pelo Id do projeto informado. + /// + /// Id do projeto. + /// Relatório parcial de projeto encontrado. + Task GetByProjectIdAsync(Guid? projectId); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IProjectRepository.cs b/src/Domain/Interfaces/Repositories/IProjectRepository.cs index e816da69..96f2b14c 100644 --- a/src/Domain/Interfaces/Repositories/IProjectRepository.cs +++ b/src/Domain/Interfaces/Repositories/IProjectRepository.cs @@ -9,50 +9,94 @@ public interface IProjectRepository /// /// Id do projeto. /// Projeto encontrado. - Task GetById(Guid? id); + Task GetByIdAsync(Guid? id); /// - /// Permite a busca de todos os projetos abertos. + /// Permite a busca de todos os projetos (abertos ou fechados). /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Filtra por projetos encerrados. /// Retorna todos os projetos. - Task> GetProjects(int skip, int take, bool isClosed = false); + Task> GetProjectsAsync(int skip, int take, bool isClosed = false); /// - /// Permite a busca dos projetos associados ao aluno. + /// Permite a busca dos projetos (abertos ou fechados) associados ao aluno. /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Id do aluno. /// Filtra por projetos encerrados. /// Retorna os projetos do aluno. - Task> GetStudentProjects(int skip, int take, Guid? id, bool isClosed = false); + Task> GetStudentProjectsAsync(int skip, int take, Guid? id, bool isClosed = false); /// - /// Permite a busca dos projetos associados ao professor. + /// Permite a busca dos projetos (abertos ou fechados) associados ao professor. /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Id do professor. /// Filtra por projetos encerrados. /// Retorna os projetos do professor. - Task> GetProfessorProjects(int skip, int take, Guid? id, bool isClosed = false); + Task> GetProfessorProjectsAsync(int skip, int take, Guid? id, bool isClosed = false); + + /// + /// Permite a busca dos projetos em avaliação e que não estão associados ao professor. + /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Id do professor. + /// Retorna os projetos em avaliação. + Task> GetProjectsToEvaluateAsync(int skip, int take, Guid? professorId); /// /// Cria projeto conforme parâmetros fornecidos. /// /// Parâmetros de criação. /// Projeto criado. - Task Create(Project model); + Task CreateAsync(Project model); /// /// Remove projeto através do Id informado. /// /// Id do projeto a ser removido. /// Projeto removido. - Task Delete(Guid? id); + Task DeleteAsync(Guid? id); /// /// Atualiza projeto conforme parâmetros fornecidos. /// /// Parâmetros de atualização. /// Projeto atualizado. - Task Update(Project model); + Task UpdateAsync(Project model); + + /// + /// Atualiza vários projetos. + /// + /// Projetos a serem atualizados. + /// Quantidade de projetos atualizados. + Task UpdateManyAsync(IList projects); + + /// + /// Obtém projeto pelo Id do Edital informado. + /// + /// Id do Edital. + /// Projetos encontrados. + Task> GetProjectByNoticeAsync(Guid? noticeId); + + /// + /// Obtém projetos que possuem data de entrega de relatório parcial ou final próxima. + /// + /// Projetos encontrados. + /// + /// A data de entrega de relatório parcial ou final é considerada próxima quando a mesma está a um mês ou 7 dias de distância. + /// + Task> GetProjectsWithCloseReportDueDateAsync(); + + /// + /// Obtém projetos pendentes e cujo prazo de resolução da pendência esteja vencido. + /// + /// Projetos encontrados. + Task> GetPendingAndOverdueProjectsAsync(); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IStudentDocumentsRepository.cs b/src/Domain/Interfaces/Repositories/IStudentDocumentsRepository.cs index 0639bdaf..6f2848c6 100644 --- a/src/Domain/Interfaces/Repositories/IStudentDocumentsRepository.cs +++ b/src/Domain/Interfaces/Repositories/IStudentDocumentsRepository.cs @@ -1,9 +1,11 @@ using Domain.Entities; using Domain.Interfaces.Repositories.Bases; -namespace Domain.Interfaces.Repositories; -public interface IStudentDocumentsRepository : IGenericCRUDRepository +namespace Domain.Interfaces.Repositories { - Task GetByProjectId(Guid? projectId); - Task GetByStudentId(Guid? studentId); + public interface IStudentDocumentsRepository : IGenericCRUDRepository + { + Task GetByProjectIdAsync(Guid? projectId); + Task GetByStudentIdAsync(Guid? studentId); + } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IStudentRepository.cs b/src/Domain/Interfaces/Repositories/IStudentRepository.cs index 14528c22..1c125c69 100644 --- a/src/Domain/Interfaces/Repositories/IStudentRepository.cs +++ b/src/Domain/Interfaces/Repositories/IStudentRepository.cs @@ -5,5 +5,18 @@ namespace Domain.Interfaces.Repositories { public interface IStudentRepository : IGenericCRUDRepository { + /// + /// Busca aluno pelo código de matrícula + /// + /// Código de matrícula + /// Aluno encontrado + Task GetByRegistrationCodeAsync(string registrationCode); + + /// + /// Obtém estudante pelo Id do usuário informado. + /// + /// Id do usuário. + /// Estudante encontrado. + Task GetByUserIdAsync(Guid? userId); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/ISubAreaRepository.cs b/src/Domain/Interfaces/Repositories/ISubAreaRepository.cs index 9ba0414c..a76e0986 100644 --- a/src/Domain/Interfaces/Repositories/ISubAreaRepository.cs +++ b/src/Domain/Interfaces/Repositories/ISubAreaRepository.cs @@ -5,7 +5,7 @@ namespace Domain.Interfaces.Repositories { public interface ISubAreaRepository : IGenericCRUDRepository { - Task GetByCode(string? code); - Task> GetSubAreasByArea(Guid? areaId, int skip, int take); + Task GetByCodeAsync(string? code); + Task> GetSubAreasByAreaAsync(Guid? areaId, int skip, int take); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/ITypeAssistanceRepository.cs b/src/Domain/Interfaces/Repositories/ITypeAssistanceRepository.cs deleted file mode 100644 index f0576d70..00000000 --- a/src/Domain/Interfaces/Repositories/ITypeAssistanceRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Domain.Entities; -using Domain.Interfaces.Repositories.Bases; - -namespace Domain.Interfaces.Repositories -{ - public interface ITypeAssistanceRepository : IGenericCRUDRepository - { - Task GetTypeAssistanceByName(string name); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/Repositories/IUserRepository.cs b/src/Domain/Interfaces/Repositories/IUserRepository.cs index 57c8c408..02fa3029 100644 --- a/src/Domain/Interfaces/Repositories/IUserRepository.cs +++ b/src/Domain/Interfaces/Repositories/IUserRepository.cs @@ -7,42 +7,46 @@ public interface IUserRepository /// /// Retorna usuários ativos no sistema. /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Usuários encontrados. - Task> GetActiveUsers(int skip, int take); + Task> GetActiveUsersAsync(int skip, int take); /// /// Retorna usuários inativos no sistema. /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. /// Usuários encontrados. - Task> GetInactiveUsers(int skip, int take); + Task> GetInactiveUsersAsync(int skip, int take); /// /// Recupera usuário através do Id informado. /// /// Id do usuário. /// Usuário encontrado. - Task GetById(Guid? id); + Task GetByIdAsync(Guid? id); /// /// Atualiza usuário utilizando os parâmetros informados. /// /// Parâmetros de atualização do usuário. /// Usuário atualizado. - Task Update(User user); + Task UpdateAsync(User user); /// /// Realiza a criação do usuário utilizando os parâmetros informados. /// /// Parâmetros de criação do usuário. /// Usuário criado. - Task Create(User user); + Task CreateAsync(User user); /// /// Realiza a remoção do usuário utilizando o id informado. /// /// Id do usuário. /// Usuário removido. - Task Delete(Guid? id); + Task DeleteAsync(Guid? id); /// /// Retorna usuário com o Email informado. @@ -50,7 +54,7 @@ public interface IUserRepository /// /// Email do usuário. /// Usuário encontrado. - Task GetUserByEmail(string? email); + Task GetUserByEmailAsync(string? email); /// /// Retorna usuário com o CPF informado. @@ -58,6 +62,12 @@ public interface IUserRepository /// /// CPF do usuário. /// Usuário encontrado. - Task GetUserByCPF(string? cpf); + Task GetUserByCPFAsync(string? cpf); + + /// + /// Retorna usuário com permissão de coordenador. + /// + /// Coordenador encontrado. + Task GetCoordinatorAsync(); } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Services/IEmailService.cs b/src/Domain/Interfaces/Services/IEmailService.cs index f227562c..7499653c 100644 --- a/src/Domain/Interfaces/Services/IEmailService.cs +++ b/src/Domain/Interfaces/Services/IEmailService.cs @@ -1,6 +1,12 @@ -namespace Domain.Interfaces.Services; -public interface IEmailService +namespace Domain.Interfaces.Services { - Task SendConfirmationEmail(string? email, string? name, string? token); - Task SendResetPasswordEmail(string? email, string? name, string? token); + public interface IEmailService + { + Task SendConfirmationEmailAsync(string? email, string? name, string? token); + Task SendResetPasswordEmailAsync(string? email, string? name, string? token); + Task SendNoticeEmailAsync(string? email, string? name, DateTime? registrationStartDate, DateTime? registrationEndDate, string? noticeUrl); + Task SendProjectNotificationEmailAsync(string? email, string? name, string? projectTitle, string? status, string? description); + Task SendRequestStudentRegisterEmailAsync(string? email); + Task SendNotificationOfReportDeadlineEmailAsync(string? email, string? name, string? projectTitle, string? reportType, DateTime? reportDeadline); + } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Services/IHashService.cs b/src/Domain/Interfaces/Services/IHashService.cs index ff09bf22..e60474bc 100644 --- a/src/Domain/Interfaces/Services/IHashService.cs +++ b/src/Domain/Interfaces/Services/IHashService.cs @@ -1,6 +1,8 @@ -namespace Domain.Interfaces.Services; -public interface IHashService +namespace Domain.Interfaces.Services { - string HashPassword(string password); - bool VerifyPassword(string password, string? hashedPassword); + public interface IHashService + { + string HashPassword(string password); + bool VerifyPassword(string password, string? hashedPassword); + } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Services/IReportService.cs b/src/Domain/Interfaces/Services/IReportService.cs new file mode 100644 index 00000000..c0892041 --- /dev/null +++ b/src/Domain/Interfaces/Services/IReportService.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Domain.Interfaces.Services +{ + public interface IReportService + { + Task GenerateCertificateAsync(Project project, string cordinatorName, string fileName); + } +} \ No newline at end of file diff --git a/src/Domain/Interfaces/Services/IStorageFileService.cs b/src/Domain/Interfaces/Services/IStorageFileService.cs index f54d720f..3b6d7708 100644 --- a/src/Domain/Interfaces/Services/IStorageFileService.cs +++ b/src/Domain/Interfaces/Services/IStorageFileService.cs @@ -1,19 +1,29 @@ using Microsoft.AspNetCore.Http; -namespace Domain.Interfaces.Services; -public interface IStorageFileService +namespace Domain.Interfaces.Services { - /// - /// Realiza o upload de um arquivo de edital - /// - /// Edital em pdf - /// Caminho completo até o arquivo - /// Caminho final do arquivo - Task UploadFileAsync(IFormFile file, string? filePath = null); + public interface IStorageFileService + { + /// + /// Realiza o upload de um arquivo de edital + /// + /// Edital em pdf + /// Caminho completo até o arquivo + /// Caminho final do arquivo + Task UploadFileAsync(IFormFile file, string? filePath = null); - /// - /// Deleta um arquivo - /// - /// Caminho completo até o arquivo - Task DeleteFile(string filePath); + /// + /// Realiza o upload de um arquivo de edital + /// + /// Edital em pdf + /// Caminho completo até o arquivo + /// Caminho final do arquivo + Task UploadFileAsync(byte[] file, string? filePath); + + /// + /// Deleta um arquivo + /// + /// Caminho completo até o arquivo + Task DeleteFileAsync(string filePath); + } } \ No newline at end of file diff --git a/src/Domain/Interfaces/Services/ITokenAuthenticationService.cs b/src/Domain/Interfaces/Services/ITokenAuthenticationService.cs index 5956c0e5..54ac866b 100644 --- a/src/Domain/Interfaces/Services/ITokenAuthenticationService.cs +++ b/src/Domain/Interfaces/Services/ITokenAuthenticationService.cs @@ -1,19 +1,22 @@ -using Domain.Contracts.Auth; +using Domain.Entities; -namespace Domain.Interfaces.Services; -public interface ITokenAuthenticationService +namespace Domain.Interfaces.Services { - /// - /// Gera um token de autenticação com base no id e role do usuário. - /// - /// Id do usuário - /// Nome do usuário - /// Perfil do usuário - /// Token de autenticação. - UserLoginOutput GenerateToken(Guid? id, string? userName, string? role); + public interface ITokenAuthenticationService + { + /// + /// Gera um token de autenticação com base no id e role do usuário. + /// + /// Id do usuário + /// Id do professor ou do aluno + /// Nome do usuário + /// Perfil do usuário + /// Token de autenticação. + string GenerateToken(Guid? id, Guid? actorId, string? userName, string? role); - /// - /// Retorna as claims do usuário autenticado. - /// - UserClaimsOutput GetUserAuthenticatedClaims(); + /// + /// Retorna as claims do usuário autenticado. + /// + Dictionary GetUserAuthenticatedClaims(); + } } \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Area/ICreateArea.cs b/src/Domain/Interfaces/UseCases/Area/ICreateArea.cs deleted file mode 100644 index 8f5bdb0a..00000000 --- a/src/Domain/Interfaces/UseCases/Area/ICreateArea.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Area; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateArea - { - Task Execute(CreateAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Area/IDeleteArea.cs b/src/Domain/Interfaces/UseCases/Area/IDeleteArea.cs deleted file mode 100644 index 4103b384..00000000 --- a/src/Domain/Interfaces/UseCases/Area/IDeleteArea.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Area; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteArea - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Area/IGetAreaById.cs b/src/Domain/Interfaces/UseCases/Area/IGetAreaById.cs deleted file mode 100644 index 3a9f265b..00000000 --- a/src/Domain/Interfaces/UseCases/Area/IGetAreaById.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; -using Domain.Contracts.Area; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetAreaById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Area/IGetAreasByMainArea.cs b/src/Domain/Interfaces/UseCases/Area/IGetAreasByMainArea.cs deleted file mode 100644 index 95f16399..00000000 --- a/src/Domain/Interfaces/UseCases/Area/IGetAreasByMainArea.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Domain.Contracts.Area; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetAreasByMainArea - { - Task> Execute(Guid? mainAreaId, int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Area/IUpdateArea.cs b/src/Domain/Interfaces/UseCases/Area/IUpdateArea.cs deleted file mode 100644 index ddde90ff..00000000 --- a/src/Domain/Interfaces/UseCases/Area/IUpdateArea.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; -using Domain.Contracts.Area; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateArea - { - Task Execute(Guid? id, UpdateAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Auth/IConfirmEmail.cs b/src/Domain/Interfaces/UseCases/Auth/IConfirmEmail.cs deleted file mode 100644 index 8d0abc18..00000000 --- a/src/Domain/Interfaces/UseCases/Auth/IConfirmEmail.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Domain.Interfaces.UseCases; -public interface IConfirmEmail -{ - Task Execute(string? email, string? token); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Auth/IForgotPassword.cs b/src/Domain/Interfaces/UseCases/Auth/IForgotPassword.cs deleted file mode 100644 index a89af5f5..00000000 --- a/src/Domain/Interfaces/UseCases/Auth/IForgotPassword.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Domain.Interfaces.UseCases; -public interface IForgotPassword -{ - Task Execute(string? email); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Auth/ILogin.cs b/src/Domain/Interfaces/UseCases/Auth/ILogin.cs deleted file mode 100644 index aacdc265..00000000 --- a/src/Domain/Interfaces/UseCases/Auth/ILogin.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Auth; - -namespace Domain.Interfaces.UseCases; -public interface ILogin -{ - Task Execute(UserLoginInput input); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Auth/IResetPassword.cs b/src/Domain/Interfaces/UseCases/Auth/IResetPassword.cs deleted file mode 100644 index d647ed41..00000000 --- a/src/Domain/Interfaces/UseCases/Auth/IResetPassword.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Auth; - -namespace Domain.Interfaces.UseCases; -public interface IResetPassword -{ - Task Execute(UserResetPasswordInput input); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Campus/ICreateCampus.cs b/src/Domain/Interfaces/UseCases/Campus/ICreateCampus.cs deleted file mode 100644 index 532b8649..00000000 --- a/src/Domain/Interfaces/UseCases/Campus/ICreateCampus.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Campus; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateCampus - { - Task Execute(CreateCampusInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Campus/IDeleteCampus.cs b/src/Domain/Interfaces/UseCases/Campus/IDeleteCampus.cs deleted file mode 100644 index 133533f2..00000000 --- a/src/Domain/Interfaces/UseCases/Campus/IDeleteCampus.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Campus; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteCampus - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Campus/IGetCampusById.cs b/src/Domain/Interfaces/UseCases/Campus/IGetCampusById.cs deleted file mode 100644 index 482d1046..00000000 --- a/src/Domain/Interfaces/UseCases/Campus/IGetCampusById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Campus; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetCampusById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Campus/IGetCampuses.cs b/src/Domain/Interfaces/UseCases/Campus/IGetCampuses.cs deleted file mode 100644 index a7732797..00000000 --- a/src/Domain/Interfaces/UseCases/Campus/IGetCampuses.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Campus; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetCampuses - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Campus/IUpdateCampus.cs b/src/Domain/Interfaces/UseCases/Campus/IUpdateCampus.cs deleted file mode 100644 index b00d0abb..00000000 --- a/src/Domain/Interfaces/UseCases/Campus/IUpdateCampus.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Campus; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateCampus - { - Task Execute(Guid? id, UpdateCampusInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Course/ICreateCourse.cs b/src/Domain/Interfaces/UseCases/Course/ICreateCourse.cs deleted file mode 100644 index 5849479d..00000000 --- a/src/Domain/Interfaces/UseCases/Course/ICreateCourse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Course; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateCourse - { - Task Execute(CreateCourseInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Course/IDeleteCourse.cs b/src/Domain/Interfaces/UseCases/Course/IDeleteCourse.cs deleted file mode 100644 index 56542fa0..00000000 --- a/src/Domain/Interfaces/UseCases/Course/IDeleteCourse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Course; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteCourse - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Course/IGetCourseById.cs b/src/Domain/Interfaces/UseCases/Course/IGetCourseById.cs deleted file mode 100644 index acd12eec..00000000 --- a/src/Domain/Interfaces/UseCases/Course/IGetCourseById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Course; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetCourseById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Course/IGetCourses.cs b/src/Domain/Interfaces/UseCases/Course/IGetCourses.cs deleted file mode 100644 index 25803aeb..00000000 --- a/src/Domain/Interfaces/UseCases/Course/IGetCourses.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Course; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetCourses - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Course/IUpdateCourse.cs b/src/Domain/Interfaces/UseCases/Course/IUpdateCourse.cs deleted file mode 100644 index dd8f4a93..00000000 --- a/src/Domain/Interfaces/UseCases/Course/IUpdateCourse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Course; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateCourse - { - Task Execute(Guid? id, UpdateCourseInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/MainArea/ICreateMainArea.cs b/src/Domain/Interfaces/UseCases/MainArea/ICreateMainArea.cs deleted file mode 100644 index f980f976..00000000 --- a/src/Domain/Interfaces/UseCases/MainArea/ICreateMainArea.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using Domain.Contracts.MainArea; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateMainArea - { - Task Execute(CreateMainAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/MainArea/IDeleteMainArea.cs b/src/Domain/Interfaces/UseCases/MainArea/IDeleteMainArea.cs deleted file mode 100644 index 9b9fe50e..00000000 --- a/src/Domain/Interfaces/UseCases/MainArea/IDeleteMainArea.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.MainArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteMainArea - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreaById.cs b/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreaById.cs deleted file mode 100644 index 91d4b6ef..00000000 --- a/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreaById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.MainArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetMainAreaById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreas.cs b/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreas.cs deleted file mode 100644 index 2d0ef4a9..00000000 --- a/src/Domain/Interfaces/UseCases/MainArea/IGetMainAreas.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Domain.Contracts.MainArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetMainAreas - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/MainArea/IUpdateMainArea.cs b/src/Domain/Interfaces/UseCases/MainArea/IUpdateMainArea.cs deleted file mode 100644 index adc66dc4..00000000 --- a/src/Domain/Interfaces/UseCases/MainArea/IUpdateMainArea.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; -using Domain.Contracts.MainArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateMainArea - { - Task Execute(Guid? id, UpdateMainAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Notice/ICreateNotice.cs b/src/Domain/Interfaces/UseCases/Notice/ICreateNotice.cs deleted file mode 100644 index 27b8e05d..00000000 --- a/src/Domain/Interfaces/UseCases/Notice/ICreateNotice.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Notice; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateNotice - { - Task Execute(CreateNoticeInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Notice/IDeleteNotice.cs b/src/Domain/Interfaces/UseCases/Notice/IDeleteNotice.cs deleted file mode 100644 index 9e96fa22..00000000 --- a/src/Domain/Interfaces/UseCases/Notice/IDeleteNotice.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Notice; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteNotice - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Notice/IGetNoticeById.cs b/src/Domain/Interfaces/UseCases/Notice/IGetNoticeById.cs deleted file mode 100644 index 5e643152..00000000 --- a/src/Domain/Interfaces/UseCases/Notice/IGetNoticeById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Notice; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetNoticeById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Notice/IGetNotices.cs b/src/Domain/Interfaces/UseCases/Notice/IGetNotices.cs deleted file mode 100644 index 07cca698..00000000 --- a/src/Domain/Interfaces/UseCases/Notice/IGetNotices.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Notice; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetNotices - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Notice/IUpdateNotice.cs b/src/Domain/Interfaces/UseCases/Notice/IUpdateNotice.cs deleted file mode 100644 index 2f4c364b..00000000 --- a/src/Domain/Interfaces/UseCases/Notice/IUpdateNotice.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Notice; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateNotice - { - Task Execute(Guid? id, UpdateNoticeInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Professor/ICreateProfessor.cs b/src/Domain/Interfaces/UseCases/Professor/ICreateProfessor.cs deleted file mode 100644 index bc609811..00000000 --- a/src/Domain/Interfaces/UseCases/Professor/ICreateProfessor.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Professor; - -namespace Domain.Interfaces.UseCases; -public interface ICreateProfessor -{ - Task Execute(CreateProfessorInput model); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Professor/IDeleteProfessor.cs b/src/Domain/Interfaces/UseCases/Professor/IDeleteProfessor.cs deleted file mode 100644 index ef63b92e..00000000 --- a/src/Domain/Interfaces/UseCases/Professor/IDeleteProfessor.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Professor; - -namespace Domain.Interfaces.UseCases; -public interface IDeleteProfessor -{ - Task Execute(Guid? id); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Professor/IGetProfessorById.cs b/src/Domain/Interfaces/UseCases/Professor/IGetProfessorById.cs deleted file mode 100644 index fdd45c96..00000000 --- a/src/Domain/Interfaces/UseCases/Professor/IGetProfessorById.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Professor; - -namespace Domain.Interfaces.UseCases; -public interface IGetProfessorById -{ - Task Execute(Guid? id); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Professor/IGetProfessors.cs b/src/Domain/Interfaces/UseCases/Professor/IGetProfessors.cs deleted file mode 100644 index ef9c5eda..00000000 --- a/src/Domain/Interfaces/UseCases/Professor/IGetProfessors.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Professor; - -namespace Domain.Interfaces.UseCases; -public interface IGetProfessors -{ - Task> Execute(int skip, int take); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Professor/IUpdateProfessor.cs b/src/Domain/Interfaces/UseCases/Professor/IUpdateProfessor.cs deleted file mode 100644 index 34e79f43..00000000 --- a/src/Domain/Interfaces/UseCases/Professor/IUpdateProfessor.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Domain.Contracts.Professor; - -namespace Domain.Interfaces.UseCases; -public interface IUpdateProfessor -{ - Task Execute(Guid? id, UpdateProfessorInput model); -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProgramType/ICreateProgramType.cs b/src/Domain/Interfaces/UseCases/ProgramType/ICreateProgramType.cs deleted file mode 100644 index 48af3261..00000000 --- a/src/Domain/Interfaces/UseCases/ProgramType/ICreateProgramType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProgramType; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateProgramType - { - Task Execute(CreateProgramTypeInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs b/src/Domain/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs deleted file mode 100644 index d8725db7..00000000 --- a/src/Domain/Interfaces/UseCases/ProgramType/IDeleteProgramType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProgramType; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteProgramType - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs b/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs deleted file mode 100644 index 0ae6ec55..00000000 --- a/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypeById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProgramType; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetProgramTypeById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs b/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs deleted file mode 100644 index 55ad4788..00000000 --- a/src/Domain/Interfaces/UseCases/ProgramType/IGetProgramTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProgramType; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetProgramTypes - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs b/src/Domain/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs deleted file mode 100644 index 0064f948..00000000 --- a/src/Domain/Interfaces/UseCases/ProgramType/IUpdateProgramType.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProgramType; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateProgramType - { - Task Execute(Guid? id, UpdateProgramTypeInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IAppealProject.cs b/src/Domain/Interfaces/UseCases/Project/IAppealProject.cs deleted file mode 100644 index db7ee991..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IAppealProject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IAppealProject - { - Task Execute(Guid? projectId, string? appealDescription); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/ICancelProject.cs b/src/Domain/Interfaces/UseCases/Project/ICancelProject.cs deleted file mode 100644 index a8a565eb..00000000 --- a/src/Domain/Interfaces/UseCases/Project/ICancelProject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface ICancelProject - { - Task Execute(Guid? id, string? observation); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IGetClosedProjects.cs b/src/Domain/Interfaces/UseCases/Project/IGetClosedProjects.cs deleted file mode 100644 index b483ff78..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IGetClosedProjects.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IGetClosedProjects - { - Task> Execute(int skip, int take, bool onlyMyProjects = true); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IGetOpenProjects.cs b/src/Domain/Interfaces/UseCases/Project/IGetOpenProjects.cs deleted file mode 100644 index f284291b..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IGetOpenProjects.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IGetOpenProjects - { - Task> Execute(int skip, int take, bool onlyMyProjects = true); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IGetProjectById.cs b/src/Domain/Interfaces/UseCases/Project/IGetProjectById.cs deleted file mode 100644 index 0b5e8dd2..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IGetProjectById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IGetProjectById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IOpenProject.cs b/src/Domain/Interfaces/UseCases/Project/IOpenProject.cs deleted file mode 100644 index f02a872f..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IOpenProject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IOpenProject - { - Task Execute(OpenProjectInput input); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/ISubmitProject.cs b/src/Domain/Interfaces/UseCases/Project/ISubmitProject.cs deleted file mode 100644 index 7807dbc2..00000000 --- a/src/Domain/Interfaces/UseCases/Project/ISubmitProject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface ISubmitProject - { - Task Execute(Guid? projectId); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Project/IUpdateProject.cs b/src/Domain/Interfaces/UseCases/Project/IUpdateProject.cs deleted file mode 100644 index c2cf1387..00000000 --- a/src/Domain/Interfaces/UseCases/Project/IUpdateProject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Project; - -namespace Domain.Interfaces.UseCases.Project -{ - public interface IUpdateProject - { - Task Execute(Guid? id, UpdateProjectInput input); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs b/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs deleted file mode 100644 index 41c39573..00000000 --- a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateAppealProject.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Domain.Contracts.Project; -using Domain.Contracts.ProjectEvaluation; - -namespace Domain.Interfaces.UseCases.ProjectEvaluation -{ - public interface IEvaluateAppealProject - { - Task Execute(EvaluateAppealProjectInput input); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs b/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs deleted file mode 100644 index e71f5e6c..00000000 --- a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IEvaluateSubmissionProject.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Domain.Contracts.Project; -using Domain.Contracts.ProjectEvaluation; - -namespace Domain.Interfaces.UseCases.ProjectEvaluation -{ - public interface IEvaluateSubmissionProject - { - Task Execute(EvaluateSubmissionProjectInput input); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs b/src/Domain/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs deleted file mode 100644 index 76a0b41f..00000000 --- a/src/Domain/Interfaces/UseCases/ProjectEvaluation/IGetEvaluationByProjectId.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.ProjectEvaluation; - -namespace Domain.Interfaces.UseCases.ProjectEvaluation -{ - public interface IGetEvaluationByProjectId - { - Task Execute(Guid? projectId); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Student/ICreateStudent.cs b/src/Domain/Interfaces/UseCases/Student/ICreateStudent.cs deleted file mode 100644 index be6f1e94..00000000 --- a/src/Domain/Interfaces/UseCases/Student/ICreateStudent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Student; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateStudent - { - Task Execute(CreateStudentInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Student/IDeleteStudent.cs b/src/Domain/Interfaces/UseCases/Student/IDeleteStudent.cs deleted file mode 100644 index 406291cb..00000000 --- a/src/Domain/Interfaces/UseCases/Student/IDeleteStudent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Student; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteStudent - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Student/IGetStudentById.cs b/src/Domain/Interfaces/UseCases/Student/IGetStudentById.cs deleted file mode 100644 index c4fb8d9f..00000000 --- a/src/Domain/Interfaces/UseCases/Student/IGetStudentById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Student; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetStudentById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Student/IGetStudents.cs b/src/Domain/Interfaces/UseCases/Student/IGetStudents.cs deleted file mode 100644 index 1c176fa6..00000000 --- a/src/Domain/Interfaces/UseCases/Student/IGetStudents.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Student; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetStudents - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/Student/IUpdateStudent.cs b/src/Domain/Interfaces/UseCases/Student/IUpdateStudent.cs deleted file mode 100644 index a4044c0d..00000000 --- a/src/Domain/Interfaces/UseCases/Student/IUpdateStudent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.Student; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateStudent - { - Task Execute(Guid? id, UpdateStudentInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs b/src/Domain/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs deleted file mode 100644 index 4f71ff15..00000000 --- a/src/Domain/Interfaces/UseCases/StudentDocuments/ICreateStudentDocuments.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.StudentDocuments; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateStudentDocuments - { - Task Execute(CreateStudentDocumentsInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs b/src/Domain/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs deleted file mode 100644 index 671a29a2..00000000 --- a/src/Domain/Interfaces/UseCases/StudentDocuments/IDeleteStudentDocuments.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.StudentDocuments; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteStudentDocuments - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs b/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs deleted file mode 100644 index efef4e91..00000000 --- a/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByProjectId.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.StudentDocuments; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetStudentDocumentsByProjectId - { - Task Execute(Guid? projectId); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs b/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs deleted file mode 100644 index 43111ffb..00000000 --- a/src/Domain/Interfaces/UseCases/StudentDocuments/IGetStudentDocumentsByStudentId.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.StudentDocuments; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetStudentDocumentsByStudentId - { - Task Execute(Guid? studentId); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs b/src/Domain/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs deleted file mode 100644 index e7096c9e..00000000 --- a/src/Domain/Interfaces/UseCases/StudentDocuments/IUpdateStudentDocuments.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.StudentDocuments; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateStudentDocuments - { - Task Execute(Guid? id, UpdateStudentDocumentsInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/SubArea/ICreateSubArea.cs b/src/Domain/Interfaces/UseCases/SubArea/ICreateSubArea.cs deleted file mode 100644 index 8d40ab12..00000000 --- a/src/Domain/Interfaces/UseCases/SubArea/ICreateSubArea.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.SubArea; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateSubArea - { - Task Execute(CreateSubAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/SubArea/IDeleteSubArea.cs b/src/Domain/Interfaces/UseCases/SubArea/IDeleteSubArea.cs deleted file mode 100644 index de399bbf..00000000 --- a/src/Domain/Interfaces/UseCases/SubArea/IDeleteSubArea.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.SubArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteSubArea - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreaById.cs b/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreaById.cs deleted file mode 100644 index 684fb9cf..00000000 --- a/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreaById.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; -using Domain.Contracts.SubArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetSubAreaById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs b/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs deleted file mode 100644 index d69f567d..00000000 --- a/src/Domain/Interfaces/UseCases/SubArea/IGetSubAreasByArea.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Domain.Contracts.SubArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetSubAreasByArea - { - Task> Execute(Guid? areaId, int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/SubArea/IUpdateSubArea.cs b/src/Domain/Interfaces/UseCases/SubArea/IUpdateSubArea.cs deleted file mode 100644 index e75da25f..00000000 --- a/src/Domain/Interfaces/UseCases/SubArea/IUpdateSubArea.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; -using Domain.Contracts.SubArea; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateSubArea - { - Task Execute(Guid? id, UpdateSubAreaInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/TypeAssistance/ICreateTypeAssistance.cs b/src/Domain/Interfaces/UseCases/TypeAssistance/ICreateTypeAssistance.cs deleted file mode 100644 index c7bbfd69..00000000 --- a/src/Domain/Interfaces/UseCases/TypeAssistance/ICreateTypeAssistance.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.TypeAssistance; - -namespace Domain.Interfaces.UseCases -{ - public interface ICreateTypeAssistance - { - Task Execute(CreateTypeAssistanceInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/TypeAssistance/IDeleteTypeAssistance.cs b/src/Domain/Interfaces/UseCases/TypeAssistance/IDeleteTypeAssistance.cs deleted file mode 100644 index ad9d065f..00000000 --- a/src/Domain/Interfaces/UseCases/TypeAssistance/IDeleteTypeAssistance.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.TypeAssistance; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeleteTypeAssistance - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistanceById.cs b/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistanceById.cs deleted file mode 100644 index 3178c843..00000000 --- a/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistanceById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.TypeAssistance; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetTypeAssistanceById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistances.cs b/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistances.cs deleted file mode 100644 index f1636f2a..00000000 --- a/src/Domain/Interfaces/UseCases/TypeAssistance/IGetTypeAssistances.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.TypeAssistance; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetTypeAssistances - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/TypeAssistance/IUpdateTypeAssistance.cs b/src/Domain/Interfaces/UseCases/TypeAssistance/IUpdateTypeAssistance.cs deleted file mode 100644 index fa18d3f9..00000000 --- a/src/Domain/Interfaces/UseCases/TypeAssistance/IUpdateTypeAssistance.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.TypeAssistance; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateTypeAssistance - { - Task Execute(Guid? id, UpdateTypeAssistanceInput model); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IActivateUser.cs b/src/Domain/Interfaces/UseCases/User/IActivateUser.cs deleted file mode 100644 index 27ad1625..00000000 --- a/src/Domain/Interfaces/UseCases/User/IActivateUser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IActivateUser - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IDeactivateUser.cs b/src/Domain/Interfaces/UseCases/User/IDeactivateUser.cs deleted file mode 100644 index 19fb0a14..00000000 --- a/src/Domain/Interfaces/UseCases/User/IDeactivateUser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IDeactivateUser - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IGetActiveUsers.cs b/src/Domain/Interfaces/UseCases/User/IGetActiveUsers.cs deleted file mode 100644 index 006b7bec..00000000 --- a/src/Domain/Interfaces/UseCases/User/IGetActiveUsers.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetActiveUsers - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IGetInactiveUsers.cs b/src/Domain/Interfaces/UseCases/User/IGetInactiveUsers.cs deleted file mode 100644 index c3385c7a..00000000 --- a/src/Domain/Interfaces/UseCases/User/IGetInactiveUsers.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetInactiveUsers - { - Task> Execute(int skip, int take); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IGetUserById.cs b/src/Domain/Interfaces/UseCases/User/IGetUserById.cs deleted file mode 100644 index 8a4b5fad..00000000 --- a/src/Domain/Interfaces/UseCases/User/IGetUserById.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IGetUserById - { - Task Execute(Guid? id); - } -} \ No newline at end of file diff --git a/src/Domain/Interfaces/UseCases/User/IUpdateUser.cs b/src/Domain/Interfaces/UseCases/User/IUpdateUser.cs deleted file mode 100644 index 67130aa8..00000000 --- a/src/Domain/Interfaces/UseCases/User/IUpdateUser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Contracts.User; - -namespace Domain.Interfaces.UseCases -{ - public interface IUpdateUser - { - Task Execute(UserUpdateInput input); - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/AreaMappings.cs b/src/Domain/Mappings/AreaMappings.cs deleted file mode 100644 index fc5b62c5..00000000 --- a/src/Domain/Mappings/AreaMappings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Area; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class AreaMappings : Profile - { - public AreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.MainArea, opt => opt.MapFrom(src => src.MainArea)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/CampusMappings.cs b/src/Domain/Mappings/CampusMappings.cs deleted file mode 100644 index 6cebf710..00000000 --- a/src/Domain/Mappings/CampusMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Campus; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class CampusMappings : Profile - { - public CampusMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/CourseMappings.cs b/src/Domain/Mappings/CourseMappings.cs deleted file mode 100644 index ea30d504..00000000 --- a/src/Domain/Mappings/CourseMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Course; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class CourseMappings : Profile - { - public CourseMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/MainAreaMappings.cs b/src/Domain/Mappings/MainAreaMappings.cs deleted file mode 100644 index 9d85523c..00000000 --- a/src/Domain/Mappings/MainAreaMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.MainArea; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class MainAreaMappings : Profile - { - public MainAreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/NoticeMappings.cs b/src/Domain/Mappings/NoticeMappings.cs deleted file mode 100644 index b073f06b..00000000 --- a/src/Domain/Mappings/NoticeMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Notice; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class NoticeMappings : Profile - { - public NoticeMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/ProfessorMappings.cs b/src/Domain/Mappings/ProfessorMappings.cs deleted file mode 100644 index b64bfaa0..00000000 --- a/src/Domain/Mappings/ProfessorMappings.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Professor; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class ProfessorMappings : Profile - { - public ProfessorMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - - CreateMap() - .ForMember(dest => dest.Name, - opt => opt.MapFrom(src => src.User != null ? src.User.Name : null)) - .ForMember(dest => dest.Email, - opt => opt.MapFrom(src => src.User != null ? src.User.Email : null)) - .ReverseMap(); - - CreateMap() - .ForMember(dest => dest.User, - opt => opt.MapFrom(src => src.User)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/ProgramTypeMappings.cs b/src/Domain/Mappings/ProgramTypeMappings.cs deleted file mode 100644 index 2c051331..00000000 --- a/src/Domain/Mappings/ProgramTypeMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.ProgramType; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class ProgramTypeMappings : Profile - { - public ProgramTypeMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/ProjectEvaluationMappings.cs b/src/Domain/Mappings/ProjectEvaluationMappings.cs deleted file mode 100644 index ce9db0c3..00000000 --- a/src/Domain/Mappings/ProjectEvaluationMappings.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Domain.Contracts.ProjectEvaluation; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class ProjectEvaluationMappings : Profile - { - public ProjectEvaluationMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/ProjectMappings.cs b/src/Domain/Mappings/ProjectMappings.cs deleted file mode 100644 index 80f307a5..00000000 --- a/src/Domain/Mappings/ProjectMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class ProjectMappings : Profile - { - public ProjectMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/StudentDocumentsMappings.cs b/src/Domain/Mappings/StudentDocumentsMappings.cs deleted file mode 100644 index 54a00c08..00000000 --- a/src/Domain/Mappings/StudentDocumentsMappings.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Domain.Contracts.StudentDocuments; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class StudentDocumentsMappings : Profile - { - public StudentDocumentsMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/StudentMappings.cs b/src/Domain/Mappings/StudentMappings.cs deleted file mode 100644 index da6c736c..00000000 --- a/src/Domain/Mappings/StudentMappings.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Student; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class StudentMappings : Profile - { - public StudentMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - - CreateMap() - .ForMember(dest => dest.Name, - opt => opt.MapFrom(src => src.User != null ? src.User.Name : null)) - .ForMember(dest => dest.Email, - opt => opt.MapFrom(src => src.User != null ? src.User.Email : null)) - .ReverseMap(); - - CreateMap() - .ForMember(dest => dest.User, - opt => opt.MapFrom(src => src.User)) - .ForMember(dest => dest.Campus, - opt => opt.MapFrom(src => src.Campus)) - .ForMember(dest => dest.Course, - opt => opt.MapFrom(src => src.Course)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/SubAreaMappings.cs b/src/Domain/Mappings/SubAreaMappings.cs deleted file mode 100644 index ecb0124e..00000000 --- a/src/Domain/Mappings/SubAreaMappings.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Domain.Contracts.SubArea; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class SubAreaMappings : Profile - { - public SubAreaMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.Area, opt => opt.MapFrom(src => src.Area)) - .ForPath(dest => dest.Area!.MainArea, opt => opt.MapFrom(src => src.Area!.MainArea)) - .ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/TypeAssistanceMappings.cs b/src/Domain/Mappings/TypeAssistanceMappings.cs deleted file mode 100644 index 4ded6947..00000000 --- a/src/Domain/Mappings/TypeAssistanceMappings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Domain.Contracts.TypeAssistance; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class TypeAssistanceMappings : Profile - { - public TypeAssistanceMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/Mappings/UserMappings.cs b/src/Domain/Mappings/UserMappings.cs deleted file mode 100644 index 48f3df53..00000000 --- a/src/Domain/Mappings/UserMappings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using AutoMapper; -using Domain.Contracts.User; -using Domain.Entities; - -namespace Domain.Mappings -{ - public class UserMappings : Profile - { - public UserMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Area/CreateArea.cs b/src/Domain/UseCases/Area/CreateArea.cs deleted file mode 100644 index 3b74e89d..00000000 --- a/src/Domain/UseCases/Area/CreateArea.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Domain.Contracts.Area; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateArea : ICreateArea - { - #region Global Scope - private readonly IAreaRepository _areaRepository; - private readonly IMainAreaRepository _mainAreaRepository; - private readonly IMapper _mapper; - public CreateArea(IAreaRepository areaRepository, IMainAreaRepository mainAreaRepository, IMapper mapper) - { - _areaRepository = areaRepository; - _mainAreaRepository = mainAreaRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateAreaInput model) - { - var entity = await _areaRepository.GetByCode(model.Code); - UseCaseException.BusinessRuleViolation(entity != null, $"There is already a Main Area for the code {model.Code}."); - - // Verifica id da área princial - UseCaseException.NotInformedParam(model.MainAreaId == null, nameof(model.MainAreaId)); - - // Valida se existe área principal - var area = await _mainAreaRepository.GetById(model.MainAreaId); - UseCaseException.BusinessRuleViolation(area?.DeletedAt != null, "The informed Main Area is inactive."); - - // Cria nova área - entity = await _areaRepository.Create(_mapper.Map(model)); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Area/DeleteArea.cs b/src/Domain/UseCases/Area/DeleteArea.cs deleted file mode 100644 index 6d6bef4b..00000000 --- a/src/Domain/UseCases/Area/DeleteArea.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.Area; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class DeleteArea : IDeleteArea - { - #region Global Scope - private readonly IAreaRepository _repository; - private readonly IMapper _mapper; - public DeleteArea(IAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id == null, nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna a área removida - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Area/GetAreaById.cs b/src/Domain/UseCases/Area/GetAreaById.cs deleted file mode 100644 index 5f884d74..00000000 --- a/src/Domain/UseCases/Area/GetAreaById.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Domain.Contracts.Area; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class GetAreaById : IGetAreaById - { - #region Global Scope - private readonly IAreaRepository _repository; - private readonly IMapper _mapper; - public GetAreaById(IAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se Id foi informado. - UseCaseException.NotInformedParam(id is null, nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Area/GetAreasByMainArea.cs b/src/Domain/UseCases/Area/GetAreasByMainArea.cs deleted file mode 100644 index 55114ba6..00000000 --- a/src/Domain/UseCases/Area/GetAreasByMainArea.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Domain.Contracts.Area; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class GetAreasByMainArea : IGetAreasByMainArea - { - #region Global Scope - private readonly IAreaRepository _repository; - private readonly IMapper _mapper; - public GetAreasByMainArea(IAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(Guid? mainAreaId, int skip, int take) - { - // Verifica se mainAreaId foi informado. - UseCaseException.NotInformedParam(mainAreaId is null, nameof(mainAreaId)); - - var entities = await _repository.GetAreasByMainArea(mainAreaId, skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Area/UpdateArea.cs b/src/Domain/UseCases/Area/UpdateArea.cs deleted file mode 100644 index 15ecc449..00000000 --- a/src/Domain/UseCases/Area/UpdateArea.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Domain.Contracts.Area; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateArea : IUpdateArea - { - #region Global Scope - private readonly IAreaRepository _repository; - private readonly IMapper _mapper; - public UpdateArea(IAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateAreaInput input) - { - // Verifica se Id foi informado. - UseCaseException.NotInformedParam(id is null, nameof(id)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.MainArea)); - - // Atualiza atributos permitidos - entity.Name = input.Name; - entity.Code = input.Code; - entity.MainAreaId = input.MainAreaId; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Auth/ConfirmEmail.cs b/src/Domain/UseCases/Auth/ConfirmEmail.cs deleted file mode 100644 index e9bba2b8..00000000 --- a/src/Domain/UseCases/Auth/ConfirmEmail.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class ConfirmEmail : IConfirmEmail - { - #region Global Scope - private readonly IUserRepository _userRepository; - public ConfirmEmail(IUserRepository userRepository) => _userRepository = userRepository; - #endregion - - public async Task Execute(string? email, string? token) - { - // Verifica se o email é nulo - if (string.IsNullOrEmpty(email)) - throw new ArgumentNullException(nameof(email), "Email não informado."); - - // Verifica se o token é nulo - else if (string.IsNullOrEmpty(token)) - throw new ArgumentNullException(nameof(token), "Token não informado."); - - // Busca usuário pelo email informado - var user = await _userRepository.GetUserByEmail(email) - ?? throw new Exception("Usuário não encontrado."); - - // Confirma usuário - user.ConfirmUserEmail(token); - - // Atualiza usuário - await _userRepository.Update(user); - - // Retorna mensagem de sucesso - return "Usuário confirmado com sucesso."; - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Auth/ForgotPassword.cs b/src/Domain/UseCases/Auth/ForgotPassword.cs deleted file mode 100644 index ba4ae43b..00000000 --- a/src/Domain/UseCases/Auth/ForgotPassword.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class ForgotPassword : IForgotPassword - { - #region Global Scope - private readonly IUserRepository _userRepository; - private readonly IEmailService _emailService; - public ForgotPassword(IUserRepository userRepository, IEmailService emailService) - { - _emailService = emailService; - _userRepository = userRepository; - } - #endregion - - public async Task Execute(string? email) - { - // Verifica se o email é nulo - if (string.IsNullOrEmpty(email)) - throw new ArgumentNullException(nameof(email), "Email não informado."); - - // Busca usuário pelo email - var user = await _userRepository.GetUserByEmail(email) - ?? throw new Exception("Nenhum usuário encontrado para o email informado."); - - // Gera token de recuperação de senha - user.GenerateResetPasswordToken(); - - // Salva alterações - await _userRepository.Update(user); - - // Envia email de recuperação de senha - await _emailService.SendResetPasswordEmail(user.Email, user.Name, user.ResetPasswordToken); - - // Verifica se o token foi gerado - if (string.IsNullOrEmpty(user.ResetPasswordToken)) - throw new Exception("Token não gerado."); - - // Retorna token - return "Token de recuperação gerado e enviado por e-mail com sucesso."; - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Auth/Login.cs b/src/Domain/UseCases/Auth/Login.cs deleted file mode 100644 index 143f26c9..00000000 --- a/src/Domain/UseCases/Auth/Login.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Domain.Contracts.Auth; -using Domain.Interfaces.UseCases; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class Login : ILogin - { - #region Global Scope - private readonly ITokenAuthenticationService _tokenService; - private readonly IUserRepository _userRepository; - private readonly IHashService _hashService; - public Login(ITokenAuthenticationService tokenService, IUserRepository userRepository, IHashService hashService) - { - _tokenService = tokenService; - _userRepository = userRepository; - _hashService = hashService; - } - #endregion - - public async Task Execute(UserLoginInput input) - { - // Verifica se o email é nulo - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Email), nameof(input.Email)); - - // Verifica se a senha é nula - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Password), nameof(input.Password)); - - // Busca usuário pelo email - var entity = await _userRepository.GetUserByEmail(input.Email) - ?? throw UseCaseException.NotFoundEntityByParams(nameof(Entities.User)); - - // Verifica se o usuário está confirmado - UseCaseException.BusinessRuleViolation(!entity.IsConfirmed, "User's email has not yet been confirmed."); - - // Verifica se a senha é válida - UseCaseException.BusinessRuleViolation(!_hashService.VerifyPassword(input.Password!, entity.Password), "Invalid credentials."); - - // Gera o token de autenticação e retorna o resultado - return _tokenService.GenerateToken(entity.Id, entity.Name, entity.Role.ToString()); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Auth/ResetPassword.cs b/src/Domain/UseCases/Auth/ResetPassword.cs deleted file mode 100644 index cf2ebf85..00000000 --- a/src/Domain/UseCases/Auth/ResetPassword.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Domain.Contracts.Auth; -using Domain.Interfaces.UseCases; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; - -namespace Domain.UseCases -{ - public class ResetPassword : IResetPassword - { - #region Global Scope - private readonly IUserRepository _userRepository; - private readonly IHashService _hashService; - public ResetPassword(IUserRepository userRepository, IHashService hashService) - { - _userRepository = userRepository; - _hashService = hashService; - } - #endregion - - public async Task Execute(UserResetPasswordInput input) - { - // Verifica se o id do usuário é nulo - if (input.Id == null) - throw new ArgumentNullException(nameof(input.Id), "Id do usuário não informado."); - - // Verifica se a senha é nula - else if (input.Password == null) - throw new ArgumentNullException(nameof(input.Password), "Senha não informada."); - - // Verifica se o token é nulo - else if (input.Token == null) - throw new ArgumentNullException(nameof(input.Token), "Token não informado."); - - // Busca o usuário pelo id - var entity = await _userRepository.GetById(input.Id) - ?? throw new Exception("Nenhum usuário encontrato para o id informado."); - - // Verifica se o token de validação é nulo - if (string.IsNullOrEmpty(entity.ResetPasswordToken)) - throw new Exception("Solicitação de atualização de senha não permitido."); - - // Verifica se o token de validação é igual ao token informado - input.Password = _hashService.HashPassword(input.Password); - - // Atualiza a senha do usuário - if (!entity.UpdatePassword(input.Password, input.Token)) - throw new Exception("Token de validação inválido."); - - // Salva as alterações - await _userRepository.Update(entity); - - // Retorna o resultado - return "Senha atualizada com sucesso."; - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Campus/CreateCampus.cs b/src/Domain/UseCases/Campus/CreateCampus.cs deleted file mode 100644 index 9832872d..00000000 --- a/src/Domain/UseCases/Campus/CreateCampus.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateCampus : ICreateCampus - { - #region Global Scope - private readonly ICampusRepository _repository; - private readonly IMapper _mapper; - public CreateCampus(ICampusRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateCampusInput input) - { - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Verifica se já existe um edital para o período indicado - var entity = await _repository.GetCampusByName(input.Name!); - if (entity != null) - throw new Exception("Já existe um Campus para o nome informado."); - - // Cria entidade - entity = await _repository.Create(_mapper.Map(input)); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Campus/DeleteCampus.cs b/src/Domain/UseCases/Campus/DeleteCampus.cs deleted file mode 100644 index cf519a90..00000000 --- a/src/Domain/UseCases/Campus/DeleteCampus.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteCampus : IDeleteCampus - { - #region Global Scope - private readonly ICampusRepository _repository; - private readonly IMapper _mapper; - public DeleteCampus(ICampusRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o curso removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Campus/GetCampusById.cs b/src/Domain/UseCases/Campus/GetCampusById.cs deleted file mode 100644 index 1b6ba351..00000000 --- a/src/Domain/UseCases/Campus/GetCampusById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetCampusById : IGetCampusById - { - #region Global Scope - private readonly ICampusRepository _repository; - private readonly IMapper _mapper; - public GetCampusById(ICampusRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Campus/GetCampuses.cs b/src/Domain/UseCases/Campus/GetCampuses.cs deleted file mode 100644 index 735e3019..00000000 --- a/src/Domain/UseCases/Campus/GetCampuses.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetCampuses : IGetCampuses - { - #region Global Scope - private readonly ICampusRepository _repository; - private readonly IMapper _mapper; - public GetCampuses(ICampusRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Campus/UpdateCampus.cs b/src/Domain/UseCases/Campus/UpdateCampus.cs deleted file mode 100644 index 016f7697..00000000 --- a/src/Domain/UseCases/Campus/UpdateCampus.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Domain.Contracts.Campus; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateCampus : IUpdateCampus - { - #region Global Scope - private readonly ICampusRepository _repository; - private readonly IMapper _mapper; - public UpdateCampus(ICampusRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateCampusInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) ?? throw new Exception("Campus não encontrado."); - - // Verifica se a entidade foi excluída - if (entity.DeletedAt != null) - throw new Exception("O Campus informado já foi excluído."); - - // Verifica se o nome já está sendo usado - if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) && await _repository.GetCampusByName(input.Name!) != null) - throw new Exception("Já existe um Campus para o nome informado."); - - // Atualiza atributos permitidos - entity.Name = input.Name; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Course/CreateCourse.cs b/src/Domain/UseCases/Course/CreateCourse.cs deleted file mode 100644 index 705fd617..00000000 --- a/src/Domain/UseCases/Course/CreateCourse.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateCourse : ICreateCourse - { - #region Global Scope - private readonly ICourseRepository _repository; - private readonly IMapper _mapper; - public CreateCourse(ICourseRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateCourseInput input) - { - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Verifica se já existe um edital para o período indicado - var entity = await _repository.GetCourseByName(input.Name!); - if (entity != null) - throw new Exception("Já existe um Curso para o nome informado."); - - // Cria entidade - entity = await _repository.Create(_mapper.Map(input)); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Course/DeleteCourse.cs b/src/Domain/UseCases/Course/DeleteCourse.cs deleted file mode 100644 index 8d84b694..00000000 --- a/src/Domain/UseCases/Course/DeleteCourse.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteCourse : IDeleteCourse - { - #region Global Scope - private readonly ICourseRepository _repository; - private readonly IMapper _mapper; - public DeleteCourse(ICourseRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o curso removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Course/GetCourseById.cs b/src/Domain/UseCases/Course/GetCourseById.cs deleted file mode 100644 index 956a29c5..00000000 --- a/src/Domain/UseCases/Course/GetCourseById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetCourseById : IGetCourseById - { - #region Global Scope - private readonly ICourseRepository _repository; - private readonly IMapper _mapper; - public GetCourseById(ICourseRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Course/GetCourses.cs b/src/Domain/UseCases/Course/GetCourses.cs deleted file mode 100644 index f3c430d6..00000000 --- a/src/Domain/UseCases/Course/GetCourses.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetCourses : IGetCourses - { - #region Global Scope - private readonly ICourseRepository _repository; - private readonly IMapper _mapper; - public GetCourses(ICourseRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Course/UpdateCourse.cs b/src/Domain/UseCases/Course/UpdateCourse.cs deleted file mode 100644 index 4e27b259..00000000 --- a/src/Domain/UseCases/Course/UpdateCourse.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Domain.Contracts.Course; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateCourse : IUpdateCourse - { - #region Global Scope - private readonly ICourseRepository _repository; - private readonly IMapper _mapper; - public UpdateCourse(ICourseRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateCourseInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) ?? throw new Exception("Curso não encontrado."); - - // Verifica se a entidade foi excluída - if (entity.DeletedAt != null) - throw new Exception("O Curso informado já foi excluído."); - - // Verifica se o nome já está sendo usado - if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) && await _repository.GetCourseByName(input.Name!) != null) - throw new Exception("Já existe um Curso para o nome informado."); - - // Atualiza atributos permitidos - entity.Name = input.Name; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/MainArea/CreateMainArea.cs b/src/Domain/UseCases/MainArea/CreateMainArea.cs deleted file mode 100644 index 9e17b19b..00000000 --- a/src/Domain/UseCases/MainArea/CreateMainArea.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using System.Threading.Tasks; -using System; - -namespace Domain.UseCases -{ - public class CreateMainArea : ICreateMainArea - { - #region Global Scope - private readonly IMainAreaRepository _repository; - private readonly IMapper _mapper; - public CreateMainArea(IMainAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateMainAreaInput input) - { - // Validação de código da Área - var entity = await _repository.GetByCode(input.Code); - if (entity != null) - throw new Exception($"Já existe uma Área Principal para o código {input.Code}"); - - entity = await _repository.Create(_mapper.Map(input)); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/MainArea/DeleteMainArea.cs b/src/Domain/UseCases/MainArea/DeleteMainArea.cs deleted file mode 100644 index 01b305f4..00000000 --- a/src/Domain/UseCases/MainArea/DeleteMainArea.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteMainArea : IDeleteMainArea - { - #region Global Scope - private readonly IMainAreaRepository _repository; - private readonly IMapper _mapper; - public DeleteMainArea(IMainAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o edital removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/MainArea/GetMainAreaById.cs b/src/Domain/UseCases/MainArea/GetMainAreaById.cs deleted file mode 100644 index bf6e3f7f..00000000 --- a/src/Domain/UseCases/MainArea/GetMainAreaById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetMainAreaById : IGetMainAreaById - { - #region Global Scope - private readonly IMainAreaRepository _repository; - private readonly IMapper _mapper; - public GetMainAreaById(IMainAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/MainArea/GetMainAreas.cs b/src/Domain/UseCases/MainArea/GetMainAreas.cs deleted file mode 100644 index 85726aa0..00000000 --- a/src/Domain/UseCases/MainArea/GetMainAreas.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetMainAreas : IGetMainAreas - { - #region Global Scope - private readonly IMainAreaRepository _repository; - private readonly IMapper _mapper; - public GetMainAreas(IMainAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/MainArea/UpdateMainArea.cs b/src/Domain/UseCases/MainArea/UpdateMainArea.cs deleted file mode 100644 index 76b69e66..00000000 --- a/src/Domain/UseCases/MainArea/UpdateMainArea.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Domain.Contracts.MainArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using System.Threading.Tasks; -using System; - -namespace Domain.UseCases -{ - public class UpdateMainArea : IUpdateMainArea - { - #region Global Scope - private readonly IMainAreaRepository _repository; - private readonly IMapper _mapper; - public UpdateMainArea(IMainAreaRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateMainAreaInput input) - { - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) ?? throw new Exception("Área Principal não encontrada."); - - // Atualiza atributos permitidos - entity.Name = input.Name; - entity.Code = input.Code; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Notice/CreateNotice.cs b/src/Domain/UseCases/Notice/CreateNotice.cs deleted file mode 100644 index d0628195..00000000 --- a/src/Domain/UseCases/Notice/CreateNotice.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateNotice : ICreateNotice - { - #region Global Scope - private readonly INoticeRepository _repository; - private readonly IStorageFileService _storageFileService; - private readonly IMapper _mapper; - public CreateNotice(INoticeRepository repository, IStorageFileService storageFileService, IMapper mapper) - { - _repository = repository; - _storageFileService = storageFileService; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateNoticeInput input) - { - // Mapeia input para entidade - var entity = _mapper.Map(input); - - // Verifica se já existe um edital para o período indicado - var projectFound = await _repository.GetNoticeByPeriod((DateTime)input.StartDate!, (DateTime)input.FinalDate!); - UseCaseException.BusinessRuleViolation(projectFound != null, "A notice already exists for the indicated period."); - - // Salva arquivo no repositório e atualiza atributo DocUrl - if (input.File != null) - input.DocUrl = await _storageFileService.UploadFileAsync(input.File); - - // Cria entidade - entity = await _repository.Create(entity); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Notice/DeleteNotice.cs b/src/Domain/UseCases/Notice/DeleteNotice.cs deleted file mode 100644 index ab4751d4..00000000 --- a/src/Domain/UseCases/Notice/DeleteNotice.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class DeleteNotice : IDeleteNotice - { - #region Global Scope - private readonly INoticeRepository _repository; - private readonly IStorageFileService _storageFileService; - private readonly IMapper _mapper; - public DeleteNotice(INoticeRepository repository, IStorageFileService storageFileService, IMapper mapper) - { - _repository = repository; - _storageFileService = storageFileService; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id == null, nameof(id)); - - // Remove a entidade - var entity = await _repository.Delete(id); - - // Deleta o arquivo do edital - if (!string.IsNullOrEmpty(entity.DocUrl)) - await _storageFileService.DeleteFile(entity.DocUrl); - - // Retorna o edital removido - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Notice/GetNoticeById.cs b/src/Domain/UseCases/Notice/GetNoticeById.cs deleted file mode 100644 index 9ff99060..00000000 --- a/src/Domain/UseCases/Notice/GetNoticeById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetNoticeById : IGetNoticeById - { - #region Global Scope - private readonly INoticeRepository _repository; - private readonly IMapper _mapper; - public GetNoticeById(INoticeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Notice/GetNotices.cs b/src/Domain/UseCases/Notice/GetNotices.cs deleted file mode 100644 index 0d4fa8ec..00000000 --- a/src/Domain/UseCases/Notice/GetNotices.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetNotices : IGetNotices - { - #region Global Scope - private readonly INoticeRepository _repository; - private readonly IMapper _mapper; - public GetNotices(INoticeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Notice/UpdateNotice.cs b/src/Domain/UseCases/Notice/UpdateNotice.cs deleted file mode 100644 index 8bab15b1..00000000 --- a/src/Domain/UseCases/Notice/UpdateNotice.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Domain.Contracts.Notice; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateNotice : IUpdateNotice - { - #region Global Scope - private readonly INoticeRepository _repository; - private readonly IStorageFileService _storageFileService; - private readonly IMapper _mapper; - public UpdateNotice(INoticeRepository repository, IStorageFileService storageFileService, IMapper mapper) - { - _repository = repository; - _storageFileService = storageFileService; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateNoticeInput input) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id == null, nameof(id)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Notice)); - - // Verifica se a entidade foi excluída - UseCaseException.BusinessRuleViolation(entity.DeletedAt != null, "The notice entered has already been deleted."); - - // Salva arquivo no repositório e atualiza atributo DocUrl - if (input.File != null) - entity.DocUrl = await _storageFileService.UploadFileAsync(input.File, entity.DocUrl); - - // Atualiza atributos permitidos - entity.StartDate = input.StartDate ?? entity.StartDate; - entity.FinalDate = input.FinalDate ?? entity.FinalDate; - entity.AppealStartDate = input.AppealStartDate ?? entity.AppealStartDate; - entity.AppealFinalDate = input.AppealFinalDate ?? entity.AppealFinalDate; - entity.SuspensionYears = input.SuspensionYears ?? entity.SuspensionYears; - entity.SendingDocumentationDeadline = input.SendingDocumentationDeadline ?? entity.SendingDocumentationDeadline; - entity.Description = input.Description ?? entity.Description; - - // Salva entidade atualizada no banco - await _repository.Update(entity); - - // Retorna entidade atualizada - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Professor/CreateProfessor.cs b/src/Domain/UseCases/Professor/CreateProfessor.cs deleted file mode 100644 index f1e99284..00000000 --- a/src/Domain/UseCases/Professor/CreateProfessor.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; - -namespace Domain.UseCases -{ - public class CreateProfessor : ICreateProfessor - { - #region Global Scope - private readonly IProfessorRepository _professorRepository; - private readonly IUserRepository _userRepository; - private readonly IEmailService _emailService; - private readonly IHashService _hashService; - private readonly IMapper _mapper; - public CreateProfessor(IProfessorRepository professorRepository, - IUserRepository userRepository, - IEmailService emailService, - IHashService hashService, - IMapper mapper) - { - _professorRepository = professorRepository; - _userRepository = userRepository; - _emailService = emailService; - _hashService = hashService; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateProfessorInput input) - { - // Realiza o map da entidade e a validação dos campos informados - var entity = _mapper.Map(input); - - // Verifica se a senha é nula - if (string.IsNullOrEmpty(input.Password)) - throw new Exception("Senha não informada."); - - // Verifica se já existe um usuário com o e-mail informado - var user = await _userRepository.GetUserByEmail(input.Email); - if (user != null) - throw new Exception("Já existe um usuário com o e-mail informado."); - - // Verifica se já existe um usuário com o CPF informado - user = await _userRepository.GetUserByCPF(input.CPF); - if (user != null) - throw new Exception("Já existe um usuário com o CPF informado."); - - // Gera hash da senha - input.Password = _hashService.HashPassword(input.Password); - - // Cria usuário - user = new Entities.User(input.Name, input.Email, input.Password, input.CPF, Entities.Enums.ERole.PROFESSOR); - - // Adiciona usuário no banco - user = await _userRepository.Create(user); - if (user == null) - throw new Exception("Não foi possível criar o usuário."); - - // Adiciona professor no banco - entity.UserId = user.Id; - entity = await _professorRepository.Create(entity); - if (entity == null) - throw new Exception("Não foi possível criar o professor."); - - // Envia e-mail de confirmação - await _emailService.SendConfirmationEmail(user.Email, user.Name, user.ValidationCode); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Professor/DeleteProfessor.cs b/src/Domain/UseCases/Professor/DeleteProfessor.cs deleted file mode 100644 index a8b9c0d9..00000000 --- a/src/Domain/UseCases/Professor/DeleteProfessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteProfessor : IDeleteProfessor - { - #region Global Scope - private readonly IProfessorRepository _professorRepository; - private readonly IUserRepository _userRepository; - private readonly IMapper _mapper; - public DeleteProfessor(IProfessorRepository professorRepository, IUserRepository userRepository, IMapper mapper) - { - _professorRepository = professorRepository; - _userRepository = userRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se o professor existe - var professor = await _professorRepository.GetById(id) - ?? throw new Exception("Professor não encontrado para o Id informado."); - - // Verifica se o usuário existe - _ = await _userRepository.GetById(professor.UserId) - ?? throw new Exception("Usuário não encontrado para o Id informado."); - - // Remove o professor - professor = await _professorRepository.Delete(id); - if (professor == null) - throw new Exception("O professor não pôde ser removido."); - - // Remove o usuário - _ = await _userRepository.Delete(professor.UserId) - ?? throw new Exception("O usuário não pôde ser removido."); - - // Retorna o professor removido - return _mapper.Map(professor); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Professor/GetProfessorById.cs b/src/Domain/UseCases/Professor/GetProfessorById.cs deleted file mode 100644 index 84e76574..00000000 --- a/src/Domain/UseCases/Professor/GetProfessorById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetProfessorById : IGetProfessorById - { - #region Global Scope - private readonly IProfessorRepository _repository; - private readonly IMapper _mapper; - public GetProfessorById(IProfessorRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Professor/GetProfessors.cs b/src/Domain/UseCases/Professor/GetProfessors.cs deleted file mode 100644 index 05f0d4df..00000000 --- a/src/Domain/UseCases/Professor/GetProfessors.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetProfessors : IGetProfessors - { - #region Global Scope - private readonly IProfessorRepository _repository; - private readonly IMapper _mapper; - public GetProfessors(IProfessorRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Professor/UpdateProfessor.cs b/src/Domain/UseCases/Professor/UpdateProfessor.cs deleted file mode 100644 index a104b440..00000000 --- a/src/Domain/UseCases/Professor/UpdateProfessor.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Domain.Contracts.Professor; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class UpdateProfessor : IUpdateProfessor - { - #region Global Scope - private readonly IProfessorRepository _professorRepository; - private readonly IMapper _mapper; - public UpdateProfessor(IProfessorRepository professorRepository, IMapper mapper) - { - _professorRepository = professorRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateProfessorInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Recupera entidade que será atualizada - var professor = await _professorRepository.GetById(id) - ?? throw new Exception("Nenhum professor encontrado para o Id informado."); - - // Verifica se a entidade foi excluída - if (professor.DeletedAt != null) - throw new Exception("O professor informado já foi excluído."); - - // Atualiza atributos permitidos - professor.IdentifyLattes = input.IdentifyLattes; - professor.SIAPEEnrollment = input.SIAPEEnrollment; - - // Atualiza professor com as informações fornecidas - professor = await _professorRepository.Update(professor); - - // Salva entidade atualizada no banco - return _mapper.Map(professor); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProgramType/CreateProgramType.cs b/src/Domain/UseCases/ProgramType/CreateProgramType.cs deleted file mode 100644 index 0b723758..00000000 --- a/src/Domain/UseCases/ProgramType/CreateProgramType.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateProgramType : ICreateProgramType - { - #region Global Scope - private readonly IProgramTypeRepository _repository; - private readonly IMapper _mapper; - public CreateProgramType(IProgramTypeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateProgramTypeInput input) - { - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Verifica se já existe um tipo de programa com o nome indicado - var entity = await _repository.GetProgramTypeByName(input.Name!); - if (entity != null) - throw new Exception("Já existe um Tipo de Programa para o nome informado."); - - // Cria entidade - entity = await _repository.Create(_mapper.Map(input)); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProgramType/DeleteProgramType.cs b/src/Domain/UseCases/ProgramType/DeleteProgramType.cs deleted file mode 100644 index 04b3df95..00000000 --- a/src/Domain/UseCases/ProgramType/DeleteProgramType.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteProgramType : IDeleteProgramType - { - #region Global Scope - private readonly IProgramTypeRepository _repository; - private readonly IMapper _mapper; - public DeleteProgramType(IProgramTypeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o tipo de programa removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProgramType/GetProgramTypeById.cs b/src/Domain/UseCases/ProgramType/GetProgramTypeById.cs deleted file mode 100644 index fac8ff55..00000000 --- a/src/Domain/UseCases/ProgramType/GetProgramTypeById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetProgramTypeById : IGetProgramTypeById - { - #region Global Scope - private readonly IProgramTypeRepository _repository; - private readonly IMapper _mapper; - public GetProgramTypeById(IProgramTypeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProgramType/GetProgramTypes.cs b/src/Domain/UseCases/ProgramType/GetProgramTypes.cs deleted file mode 100644 index a63dbb8a..00000000 --- a/src/Domain/UseCases/ProgramType/GetProgramTypes.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetProgramTypes : IGetProgramTypes - { - #region Global Scope - private readonly IProgramTypeRepository _repository; - private readonly IMapper _mapper; - public GetProgramTypes(IProgramTypeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProgramType/UpdateProgramType.cs b/src/Domain/UseCases/ProgramType/UpdateProgramType.cs deleted file mode 100644 index eab37dd2..00000000 --- a/src/Domain/UseCases/ProgramType/UpdateProgramType.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Domain.Contracts.ProgramType; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateProgramType : IUpdateProgramType - { - #region Global Scope - private readonly IProgramTypeRepository _repository; - private readonly IMapper _mapper; - public UpdateProgramType(IProgramTypeRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateProgramTypeInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) ?? throw new Exception("Tipo de Programa não encontrado."); - - // Verifica se a entidade foi excluída - if (entity.DeletedAt != null) - throw new Exception("O Tipo de Programa informado já foi excluído."); - - // Verifica se o nome já está sendo usado - if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) - && await _repository.GetProgramTypeByName(input.Name!) != null) - { - throw new Exception("Já existe um Tipo de Programa para o nome informado."); - } - - // Atualiza atributos permitidos - entity.Name = input.Name; - entity.Description = input.Description; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/AppealProject.cs b/src/Domain/UseCases/Project/AppealProject.cs deleted file mode 100644 index 89381634..00000000 --- a/src/Domain/UseCases/Project/AppealProject.cs +++ /dev/null @@ -1,57 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class AppealProject : IAppealProject - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IMapper _mapper; - public AppealProject(IProjectRepository projectRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? projectId, string? appealDescription) - { - // Verifica se Id foi informado. - UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); - - // Verifica se a descrição do recurso foi informada. - UseCaseException.NotInformedParam(string.IsNullOrWhiteSpace(appealDescription), - nameof(appealDescription)); - - // Verifica se o projeto existe - var project = await _projectRepository.GetById(projectId!.Value) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o projeto está em recurso - if (project.Status == EProjectStatus.Rejected) - { - // Altera o status do projeto para submetido - project.Status = EProjectStatus.Evaluation; - project.StatusDescription = EProjectStatus.Evaluation.GetDescription(); - project.AppealObservation = appealDescription; - project.ResubmissionDate = DateTime.Now; - - // Salva alterações no banco de dados - await _projectRepository.Update(project); - - // Retorna o projeto - return _mapper.Map(project); - } - else - { - throw UseCaseException.BusinessRuleViolation("The project is not at a stage that allows appeal."); - } - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/CancelProject.cs b/src/Domain/UseCases/Project/CancelProject.cs deleted file mode 100644 index 8bcfbb97..00000000 --- a/src/Domain/UseCases/Project/CancelProject.cs +++ /dev/null @@ -1,46 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class CancelProject : ICancelProject - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IMapper _mapper; - public CancelProject(IProjectRepository projectRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, string? observation) - { - // Verifica se o projeto existe - var project = await _projectRepository.GetById(id) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o projeto já não foi cancelado ou está encerrado - UseCaseException.BusinessRuleViolation(project.Status != EProjectStatus.Canceled || project.Status != EProjectStatus.Closed, - "Project already canceled or terminated."); - - // Atualiza informações de cancelamento do projeto - project.Status = EProjectStatus.Canceled; - project.StatusDescription = EProjectStatus.Canceled.GetDescription(); - project.CancellationReason = observation; - project.CancellationDate = DateTime.Now; - - // Atualiza projeto - project = await _projectRepository.Update(project); - - // Mapeia entidade para output e retorna - return _mapper.Map(project); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/GetClosedProjects.cs b/src/Domain/UseCases/Project/GetClosedProjects.cs deleted file mode 100644 index ce6cd87f..00000000 --- a/src/Domain/UseCases/Project/GetClosedProjects.cs +++ /dev/null @@ -1,66 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class GetClosedProjects : IGetClosedProjects - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly ITokenAuthenticationService _tokenAuthenticationService; - private readonly IMapper _mapper; - public GetClosedProjects(IProjectRepository projectRepository, - ITokenAuthenticationService tokenAuthenticationService, - IMapper mapper) - { - _projectRepository = projectRepository; - _tokenAuthenticationService = tokenAuthenticationService; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take, bool onlyMyProjects = true) - { - // Obtém as claims do usuário autenticado. - var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); - - // Se o usuário não estiver autenticado, lança uma exceção. - UseCaseException.BusinessRuleViolation(userClaims == null || userClaims.Role == null, - "Not authorized user."); - - // Obtém o tipo de usuário. - var userRole = Enum.Parse(userClaims?.Role!); - - // Obtém a lista de projetos de acordo com o tipo de usuário. - IEnumerable projects; - - // Se o usuário for um professor, retorna apenas os seus projetos. - if (userRole == ERole.PROFESSOR) - projects = await _projectRepository.GetProfessorProjects(skip, take, userClaims?.Id, true); - - // Se o usuário for um aluno, retorna apenas os seus projetos. - else if (userRole == ERole.STUDENT) - projects = await _projectRepository.GetStudentProjects(skip, take, userClaims?.Id, true); - - // Se o usuário for um administrador, permite a busca apenas pelo seu ID. - else if (userRole == ERole.ADMIN && onlyMyProjects) - projects = await _projectRepository.GetProfessorProjects(skip, take, userClaims?.Id, true); - - // Se o usuário for um administrador, permite a busca por todos os projetos. - else if (userRole == ERole.ADMIN && !onlyMyProjects) - projects = await _projectRepository.GetProjects(skip, take, true); - - // Se o usuário não for nenhum dos tipos acima, lança uma exceção. - else - throw UseCaseException.BusinessRuleViolation("Not authorized user."); - - // Mapeia a lista de projetos para uma lista de projetos resumidos e retorna. - return _mapper.Map>(projects); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/GetOpenProjects.cs b/src/Domain/UseCases/Project/GetOpenProjects.cs deleted file mode 100644 index c9d504ed..00000000 --- a/src/Domain/UseCases/Project/GetOpenProjects.cs +++ /dev/null @@ -1,66 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class GetOpenProjects : IGetOpenProjects - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly ITokenAuthenticationService _tokenAuthenticationService; - private readonly IMapper _mapper; - public GetOpenProjects(IProjectRepository projectRepository, - ITokenAuthenticationService tokenAuthenticationService, - IMapper mapper) - { - _projectRepository = projectRepository; - _tokenAuthenticationService = tokenAuthenticationService; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take, bool onlyMyProjects = true) - { - // Obtém as claims do usuário autenticado. - var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); - - // Se o usuário não estiver autenticado, lança uma exceção. - UseCaseException.BusinessRuleViolation(userClaims == null || userClaims.Role == null, - "Not authorized user."); - - // Obtém o tipo de usuário. - var userRole = Enum.Parse(userClaims?.Role!); - - // Obtém a lista de projetos de acordo com o tipo de usuário. - IEnumerable projects; - - // Se o usuário for um professor, retorna apenas os seus projetos. - if (userRole == ERole.PROFESSOR) - projects = await _projectRepository.GetProfessorProjects(skip, take, userClaims?.Id); - - // Se o usuário for um aluno, retorna apenas os seus projetos. - else if (userRole == ERole.STUDENT) - projects = await _projectRepository.GetStudentProjects(skip, take, userClaims?.Id); - - // Se o usuário for um administrador, permite a busca apenas pelo seu ID. - else if (userRole == ERole.ADMIN && onlyMyProjects) - projects = await _projectRepository.GetProfessorProjects(skip, take, userClaims?.Id); - - // Se o usuário for um administrador, permite a busca por todos os projetos. - else if (userRole == ERole.ADMIN && !onlyMyProjects) - projects = await _projectRepository.GetProjects(skip, take); - - // Se o usuário não for nenhum dos tipos acima, lança uma exceção. - else - throw UseCaseException.BusinessRuleViolation("Not authorized user."); - - // Mapeia a lista de projetos para uma lista de projetos resumidos e retorna. - return _mapper.Map>(projects); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/GetProjectById.cs b/src/Domain/UseCases/Project/GetProjectById.cs deleted file mode 100644 index 86ffda2c..00000000 --- a/src/Domain/UseCases/Project/GetProjectById.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class GetProjectById : IGetProjectById - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IMapper _mapper; - public GetProjectById(IProjectRepository projectRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Busca projeto pelo Id informado - var project = await _projectRepository.GetById(id) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Mapeia entidade para output e retorna - return _mapper.Map(project); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/OpenProject.cs b/src/Domain/UseCases/Project/OpenProject.cs deleted file mode 100644 index fbac082a..00000000 --- a/src/Domain/UseCases/Project/OpenProject.cs +++ /dev/null @@ -1,84 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; - -namespace Domain.UseCases.Project; -public class OpenProject : IOpenProject -{ - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IStudentRepository _studentRepository; - private readonly IProfessorRepository _professorRepository; - private readonly INoticeRepository _noticeRepository; - private readonly ISubAreaRepository _subAreaRepository; - private readonly IProgramTypeRepository _programTypeRepository; - private readonly IMapper _mapper; - public OpenProject(IProjectRepository projectRepository, - IStudentRepository studentRepository, - IProfessorRepository professorRepository, - INoticeRepository noticeRepository, - ISubAreaRepository subAreaRepository, - IProgramTypeRepository programTypeRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _studentRepository = studentRepository; - _professorRepository = professorRepository; - _noticeRepository = noticeRepository; - _subAreaRepository = subAreaRepository; - _programTypeRepository = programTypeRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(OpenProjectInput input) - { - // Mapeia input para entidade e realiza validação dos campos informados - var entity = _mapper.Map(input); - - // Verifica se Edital existe - var notice = await _noticeRepository.GetById(input.NoticeId) - ?? throw new ArgumentException("Edital não encontrado."); - - // Verifica se o período do edital é válido - if (notice.StartDate > DateTime.Now || notice.FinalDate < DateTime.Now) - throw new ArgumentException("Fora do período de inscrição no edital."); - - // Verifica se a Subárea existe - _ = await _subAreaRepository.GetById(input.SubAreaId) - ?? throw new ArgumentException("Subárea não encontrada."); - - // Verifica se o Tipo de Programa existe - _ = await _programTypeRepository.GetById(input.ProgramTypeId) - ?? throw new ArgumentException("Tipo de Programa não encontrado."); - - // Verifica se o Professor existe - _ = await _professorRepository.GetById(input.ProfessorId) - ?? throw new ArgumentException("Professor não encontrado."); - - // Caso tenha sido informado algum aluno no processo de abertura do projeto - if (input.StudentId.HasValue) - { - // Verifica se o aluno existe - var student = await _studentRepository.GetById(input.StudentId) - ?? throw new ArgumentException("Aluno não encontrado."); - - // Verifica se o aluno já está em um projeto - var studentProjects = await _projectRepository.GetStudentProjects(0, 1, student.Id); - if (studentProjects.Any()) - throw new ArgumentException("Aluno já está em um projeto."); - } - - // Atualiza o status do projeto - entity.Status = EProjectStatus.Opened; - entity.StatusDescription = EProjectStatus.Opened.GetDescription(); - - // Cria o projeto - var project = await _projectRepository.Create(entity); - - // Mapeia o projeto para o retorno e retorna - return _mapper.Map(project); - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/SubmitProject.cs b/src/Domain/UseCases/Project/SubmitProject.cs deleted file mode 100644 index fabcba45..00000000 --- a/src/Domain/UseCases/Project/SubmitProject.cs +++ /dev/null @@ -1,71 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project -{ - public class SubmitProject : ISubmitProject - { - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IMapper _mapper; - public SubmitProject(IProjectRepository projectRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? projectId) - { - // Verifica se Id foi informado. - UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); - - // Verifica se o projeto existe - var project = await _projectRepository.GetById(projectId!.Value) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o projeto está aberto - if (project.Status == EProjectStatus.Opened) - { - // Verifica se todos os campos do projeto foram preenchidos - UseCaseException.NotInformedParam(project.WorkType1 is null, nameof(project.WorkType1)); - UseCaseException.NotInformedParam(project.WorkType2 is null, nameof(project.WorkType2)); - UseCaseException.NotInformedParam(project.IndexedConferenceProceedings is null, nameof(project.IndexedConferenceProceedings)); - UseCaseException.NotInformedParam(project.NotIndexedConferenceProceedings is null, nameof(project.NotIndexedConferenceProceedings)); - UseCaseException.NotInformedParam(project.CompletedBook is null, nameof(project.CompletedBook)); - UseCaseException.NotInformedParam(project.OrganizedBook is null, nameof(project.OrganizedBook)); - UseCaseException.NotInformedParam(project.BookChapters is null, nameof(project.BookChapters)); - UseCaseException.NotInformedParam(project.BookTranslations is null, nameof(project.BookTranslations)); - UseCaseException.NotInformedParam(project.ParticipationEditorialCommittees is null, nameof(project.ParticipationEditorialCommittees)); - UseCaseException.NotInformedParam(project.FullComposerSoloOrchestraAllTracks is null, nameof(project.FullComposerSoloOrchestraAllTracks)); - UseCaseException.NotInformedParam(project.FullComposerSoloOrchestraCompilation is null, nameof(project.FullComposerSoloOrchestraCompilation)); - UseCaseException.NotInformedParam(project.ChamberOrchestraInterpretation is null, nameof(project.ChamberOrchestraInterpretation)); - UseCaseException.NotInformedParam(project.IndividualAndCollectiveArtPerformances is null, nameof(project.IndividualAndCollectiveArtPerformances)); - UseCaseException.NotInformedParam(project.ScientificCulturalArtisticCollectionsCuratorship is null, nameof(project.ScientificCulturalArtisticCollectionsCuratorship)); - UseCaseException.NotInformedParam(project.PatentLetter is null, nameof(project.PatentLetter)); - UseCaseException.NotInformedParam(project.PatentDeposit is null, nameof(project.PatentDeposit)); - UseCaseException.NotInformedParam(project.SoftwareRegistration is null, nameof(project.SoftwareRegistration)); - - // Altera o status do projeto para submetido - project.Status = EProjectStatus.Submitted; - project.StatusDescription = EProjectStatus.Submitted.GetDescription(); - project.SubmissionDate = DateTime.Now; - - // Salva alterações no banco de dados - await _projectRepository.Update(project); - - // Mapeia entidade para output e retorna - return _mapper.Map(project); - } - else - { - throw UseCaseException.BusinessRuleViolation("The project is not at a stage that allows submission."); - } - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Project/UpdateProject.cs b/src/Domain/UseCases/Project/UpdateProject.cs deleted file mode 100644 index 23504ee3..00000000 --- a/src/Domain/UseCases/Project/UpdateProject.cs +++ /dev/null @@ -1,120 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.Project; -using Domain.Validation; - -namespace Domain.UseCases.Project; -public class UpdateProject : IUpdateProject -{ - #region Global Scope - private readonly IProjectRepository _projectRepository; - private readonly IStudentRepository _studentRepository; - private readonly IProfessorRepository _professorRepository; - private readonly INoticeRepository _noticeRepository; - private readonly ISubAreaRepository _subAreaRepository; - private readonly IProgramTypeRepository _programTypeRepository; - private readonly IMapper _mapper; - public UpdateProject(IProjectRepository projectRepository, - IStudentRepository studentRepository, - IProfessorRepository professorRepository, - INoticeRepository noticeRepository, - ISubAreaRepository subAreaRepository, - IProgramTypeRepository programTypeRepository, - IMapper mapper) - { - _projectRepository = projectRepository; - _studentRepository = studentRepository; - _professorRepository = professorRepository; - _noticeRepository = noticeRepository; - _subAreaRepository = subAreaRepository; - _programTypeRepository = programTypeRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateProjectInput input) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id is null, nameof(id)); - - // Verifica se o projeto existe - var project = await _projectRepository.GetById(id) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o projeto está aberto - if (project!.Status == EProjectStatus.Opened) - { - // Mapeia input para entidade e realiza validação dos campos informados - var entity = _mapper.Map(input); - - // Verifica se a nova Subárea existe - if (entity.SubAreaId != project.SubAreaId) - { - _ = await _subAreaRepository.GetById(entity.SubAreaId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.SubArea)); - } - - // Verifica se o novo Tipo de Programa existe - if (entity.ProgramTypeId != project.ProgramTypeId) - { - _ = await _programTypeRepository.GetById(entity.ProgramTypeId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.ProgramType)); - } - - // Caso tenha sido informado algum aluno no processo de abertura do projeto - if (entity.StudentId.HasValue && entity.StudentId != project.StudentId) - { - // Verifica se o aluno existe - var student = await _studentRepository.GetById(entity.StudentId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Student)); - - // Verifica se o aluno já está em um projeto - var studentProjects = await _projectRepository.GetStudentProjects(0, 1, student.Id); - UseCaseException.BusinessRuleViolation(studentProjects.Any(), "Student is already on a project."); - } - - // Atualiza campos permitidos - project.Title = entity.Title; - project.KeyWord1 = entity.KeyWord1; - project.KeyWord2 = entity.KeyWord2; - project.KeyWord3 = entity.KeyWord3; - project.IsScholarshipCandidate = entity.IsScholarshipCandidate; - project.Objective = entity.Objective; - project.Methodology = entity.Methodology; - project.ExpectedResults = entity.ExpectedResults; - project.ActivitiesExecutionSchedule = entity.ActivitiesExecutionSchedule; - project.WorkType1 = entity.WorkType1; - project.WorkType2 = entity.WorkType2; - project.IndexedConferenceProceedings = entity.IndexedConferenceProceedings; - project.NotIndexedConferenceProceedings = entity.NotIndexedConferenceProceedings; - project.CompletedBook = entity.CompletedBook; - project.OrganizedBook = entity.OrganizedBook; - project.BookChapters = entity.BookChapters; - project.BookTranslations = entity.BookTranslations; - project.ParticipationEditorialCommittees = entity.ParticipationEditorialCommittees; - project.FullComposerSoloOrchestraAllTracks = entity.FullComposerSoloOrchestraAllTracks; - project.FullComposerSoloOrchestraCompilation = entity.FullComposerSoloOrchestraCompilation; - project.ChamberOrchestraInterpretation = entity.ChamberOrchestraInterpretation; - project.IndividualAndCollectiveArtPerformances = entity.IndividualAndCollectiveArtPerformances; - project.ScientificCulturalArtisticCollectionsCuratorship = entity.ScientificCulturalArtisticCollectionsCuratorship; - project.PatentLetter = entity.PatentLetter; - project.PatentDeposit = entity.PatentDeposit; - project.SoftwareRegistration = entity.SoftwareRegistration; - project.ProgramTypeId = entity.ProgramTypeId; - project.StudentId = entity.StudentId; - project.SubAreaId = entity.SubAreaId; - - // Atualiza o projeto - project = await _projectRepository.Update(entity); - - // Mapeia o projeto para o retorno e retorna - return _mapper.Map(project); - } - else - { - throw UseCaseException.BusinessRuleViolation("The project is not at a stage that allows for changes."); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProjectEvaluation/EvaluateAppealProject.cs b/src/Domain/UseCases/ProjectEvaluation/EvaluateAppealProject.cs deleted file mode 100644 index 14614655..00000000 --- a/src/Domain/UseCases/ProjectEvaluation/EvaluateAppealProject.cs +++ /dev/null @@ -1,96 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Contracts.ProjectEvaluation; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases.ProjectEvaluation; -using Domain.Validation; - -namespace Domain.UseCases.ProjectEvaluation -{ - public class EvaluateAppealProject : IEvaluateAppealProject - { - #region Global Scope - private readonly IMapper _mapper; - private readonly IProjectRepository _projectRepository; - private readonly ITokenAuthenticationService _tokenAuthenticationService; - private readonly IProjectEvaluationRepository _projectEvaluationRepository; - public EvaluateAppealProject(IMapper mapper, - IProjectRepository projectRepository, - ITokenAuthenticationService tokenAuthenticationService, - IProjectEvaluationRepository projectEvaluationRepository) - { - _mapper = mapper; - _projectRepository = projectRepository; - _tokenAuthenticationService = tokenAuthenticationService; - _projectEvaluationRepository = projectEvaluationRepository; - } - #endregion - - public async Task Execute(EvaluateAppealProjectInput input) - { - // Obtém informações do usuário logado. - var user = _tokenAuthenticationService.GetUserAuthenticatedClaims(); - - // Verifica se o usuário logado é um avaliador. - UseCaseException.BusinessRuleViolation(user.Role != ERole.ADMIN.GetDescription() || user.Role != ERole.PROFESSOR.GetDescription(), - "User is not an evaluator."); - - // Busca avaliação do projeto pelo Id. - var projectEvaluation = await _projectEvaluationRepository.GetByProjectId(input.ProjectId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.ProjectEvaluation)); - - // Recupera projeto pelo Id. - var project = await _projectRepository.GetById(input.ProjectId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o avaliador é o professor orientador do projeto. - UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.ProfessorId == user.Id, - "Evaluator is the project advisor."); - - // Verifica se o projeto está na fase de recurso. - UseCaseException.BusinessRuleViolation(projectEvaluation.Project?.Status != EProjectStatus.Evaluation, - "Project is not in the evaluation phase."); - - // Verifica se o edital está na fase de recurso. - UseCaseException.BusinessRuleViolation(projectEvaluation?.Project?.Notice?.AppealStartDate > DateTime.Now || projectEvaluation?.Project?.Notice?.AppealFinalDate < DateTime.Now, - "Notice isn't in the appeal stage."); - - // Verifica se o status da avaliação foi informado. - UseCaseException.NotInformedParam(input.AppealEvaluationStatus is null, nameof(input.AppealEvaluationStatus)); - - // Verifica se descrição da avaliação foi informada. - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.AppealEvaluationDescription), nameof(input.AppealEvaluationDescription)); - - // Atualiza a avaliação do recurso. - projectEvaluation!.AppealEvaluatorId = user.Id; - projectEvaluation.AppealEvaluationDate = DateTime.Now; - - // Atualiza a descrição e o status da avaliação do recurso. - projectEvaluation.AppealEvaluationDescription = input.AppealEvaluationDescription; - projectEvaluation.AppealEvaluationStatus = (EProjectStatus)input.AppealEvaluationStatus!; - - // Atualiza avaliação do projeto. - await _projectEvaluationRepository.Update(projectEvaluation); - - // TODO: Se projeto foi aceito, adiciona prazo para envio da documentação. - if ((EProjectStatus)input.AppealEvaluationStatus == EProjectStatus.Accepted) - { - project.Status = EProjectStatus.DocumentAnalysis; - project.StatusDescription = EProjectStatus.DocumentAnalysis.GetDescription(); - } - else - { - project.Status = EProjectStatus.Rejected; - project.StatusDescription = EProjectStatus.Rejected.GetDescription(); - } - - // Atualiza projeto. - var output = await _projectRepository.Update(project); - - // Mapeia dados de saída e retorna. - return _mapper.Map(output); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs b/src/Domain/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs deleted file mode 100644 index aba0bc92..00000000 --- a/src/Domain/UseCases/ProjectEvaluation/EvaluateSubmissionProject.cs +++ /dev/null @@ -1,96 +0,0 @@ -using AutoMapper; -using Domain.Contracts.Project; -using Domain.Contracts.ProjectEvaluation; -using Domain.Entities.Enums; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases.ProjectEvaluation; -using Domain.Validation; - -namespace Domain.UseCases.ProjectEvaluation -{ - public class EvaluateSubmissionProject : IEvaluateSubmissionProject - { - #region Global Scope - private readonly IMapper _mapper; - private readonly IProjectRepository _projectRepository; - private readonly ITokenAuthenticationService _tokenAuthenticationService; - private readonly IProjectEvaluationRepository _projectEvaluationRepository; - public EvaluateSubmissionProject(IMapper mapper, - IProjectRepository projectRepository, - ITokenAuthenticationService tokenAuthenticationService, - IProjectEvaluationRepository projectEvaluationRepository) - { - _mapper = mapper; - _projectRepository = projectRepository; - _tokenAuthenticationService = tokenAuthenticationService; - _projectEvaluationRepository = projectEvaluationRepository; - } - #endregion - - public async Task Execute(EvaluateSubmissionProjectInput input) - { - // Obtém informações do usuário logado. - var user = _tokenAuthenticationService.GetUserAuthenticatedClaims(); - - // Verifica se o usuário logado é um avaliador. - UseCaseException.BusinessRuleViolation(user.Role != ERole.ADMIN.GetDescription() || user.Role != ERole.PROFESSOR.GetDescription(), - "User is not an evaluator."); - - // Verifica se já existe alguma avaliação para o projeto. - var projectEvaluation = await _projectEvaluationRepository.GetByProjectId(input.ProjectId); - UseCaseException.BusinessRuleViolation(projectEvaluation != null, - "Project already evaluated."); - - // Busca projeto pelo Id. - var project = await _projectRepository.GetById(input.ProjectId) - ?? throw UseCaseException.NotFoundEntityById(nameof(Entities.Project)); - - // Verifica se o avaliador é o professor orientador do projeto. - UseCaseException.BusinessRuleViolation(project.ProfessorId == user.Id, - "Evaluator is the project advisor."); - - // Verifica se o projeto está na fase de submissão. - UseCaseException.BusinessRuleViolation(project.Status != EProjectStatus.Submitted, - "Project is not in the submission phase."); - - // Verifica se o edital ainda está aberto. - UseCaseException.BusinessRuleViolation(project.Notice?.StartDate > DateTime.Now || project.Notice?.FinalDate < DateTime.Now, - "Notice is closed."); - - // Verifica se o status da avaliação foi informado. - UseCaseException.NotInformedParam(input.SubmissionEvaluationStatus is null, nameof(input.SubmissionEvaluationStatus)); - - // Verifica se a descrição da avaliação foi informada. - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.SubmissionEvaluationDescription), nameof(input.SubmissionEvaluationDescription)); - - // Atribui informações de avaliação. - input.SubmissionEvaluationDate = DateTime.Now; - input.SubmissionEvaluatorId = user.Id; - - // Mapeia dados de entrada para entidade. - projectEvaluation = _mapper.Map(input); - - // Adiciona avaliação do projeto. - await _projectEvaluationRepository.Create(projectEvaluation); - - // TODO: Se projeto foi aceito, adiciona prazo para envio da documentação. - if (projectEvaluation.SubmissionEvaluationStatus == EProjectStatus.Accepted) - { - project.Status = EProjectStatus.DocumentAnalysis; - project.StatusDescription = EProjectStatus.DocumentAnalysis.GetDescription(); - } - else - { - project.Status = EProjectStatus.Rejected; - project.StatusDescription = EProjectStatus.Rejected.GetDescription(); - } - - // Atualiza projeto. - var output = await _projectRepository.Update(project); - - // Mapeia dados de saída e retorna. - return _mapper.Map(output); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs b/src/Domain/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs deleted file mode 100644 index d2108a30..00000000 --- a/src/Domain/UseCases/ProjectEvaluation/GetEvaluationByProjectId.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AutoMapper; -using Domain.Contracts.ProjectEvaluation; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases.ProjectEvaluation; -using Domain.Validation; - -namespace Domain.UseCases.ProjectEvaluation -{ - public class GetEvaluationByProjectId : IGetEvaluationByProjectId - { - #region Global Scope - private readonly IMapper _mapper; - private readonly IProjectEvaluationRepository _repository; - public GetEvaluationByProjectId(IMapper mapper, - IProjectEvaluationRepository repository) - { - _mapper = mapper; - _repository = repository; - } - #endregion - - public async Task Execute(Guid? projectId) - { - // Verifica se Id foi informado. - UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); - - // Obtém a avaliação do projeto pelo Id do projeto. - var entity = await _repository.GetByProjectId(projectId); - - // Converte e retorna o resultado. - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Student/CreateStudent.cs b/src/Domain/UseCases/Student/CreateStudent.cs deleted file mode 100644 index 9792e540..00000000 --- a/src/Domain/UseCases/Student/CreateStudent.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; - -namespace Domain.UseCases -{ - public class CreateStudent : ICreateStudent - { - #region Global Scope - private readonly IStudentRepository _studentRepository; - private readonly IUserRepository _userRepository; - private readonly ICourseRepository _courseRepository; - private readonly ICampusRepository _campusRepository; - private readonly IEmailService _emailService; - private readonly IHashService _hashService; - private readonly IMapper _mapper; - public CreateStudent(IStudentRepository studentRepository, - IUserRepository userRepository, - ICampusRepository campusRepository, - ICourseRepository courseRepository, - IEmailService emailService, - IHashService hashService, - IMapper mapper) - { - _studentRepository = studentRepository; - _userRepository = userRepository; - _campusRepository = campusRepository; - _courseRepository = courseRepository; - _emailService = emailService; - _hashService = hashService; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateStudentInput input) - { - // Realiza o map da entidade e a validação dos campos informados - var entity = _mapper.Map(input); - - // Verifica se já existe um usuário com o e-mail informado - var user = await _userRepository.GetUserByEmail(input.Email); - if (user != null) - throw new Exception("Já existe um usuário com o e-mail informado."); - - // Verifica se já existe um usuário com o CPF informado - user = await _userRepository.GetUserByCPF(input.CPF); - if (user != null) - throw new Exception("Já existe um usuário com o CPF informado."); - - // Verifica se curso informado existe - var course = await _courseRepository.GetById(input.CourseId); - if (course == null || course.DeletedAt != null) - throw new Exception("Curso informado não existe."); - - // Verifica se campus informado existe - var campus = await _campusRepository.GetById(input.CampusId); - if (campus == null || campus.DeletedAt != null) - throw new Exception("Campus informado não existe."); - - // Verifica se a senha é nula - if (string.IsNullOrEmpty(input.Password)) - throw new Exception("Senha não informada."); - - // Gera hash da senha - input.Password = _hashService.HashPassword(input.Password); - - // Cria usuário - user = new Entities.User(input.Name, input.Email, input.Password, input.CPF, Entities.Enums.ERole.STUDENT); - - // Adiciona usuário no banco - user = await _userRepository.Create(user); - if (user == null) - throw new Exception("Não foi possível criar o usuário."); - - // Adiciona estudante no banco - entity.UserId = user.Id; - entity = await _studentRepository.Create(entity); - if (entity == null) - throw new Exception("Não foi possível criar o estudante."); - - // Envia e-mail de confirmação - await _emailService.SendConfirmationEmail(user.Email, user.Name, user.ValidationCode); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Student/DeleteStudent.cs b/src/Domain/UseCases/Student/DeleteStudent.cs deleted file mode 100644 index dc293766..00000000 --- a/src/Domain/UseCases/Student/DeleteStudent.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteStudent : IDeleteStudent - { - #region Global Scope - private readonly IStudentRepository _studentRepository; - private readonly IUserRepository _userRepository; - private readonly IMapper _mapper; - public DeleteStudent(IStudentRepository studentRepository, IUserRepository userRepository, IMapper mapper) - { - _studentRepository = studentRepository; - _userRepository = userRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se o estudante existe - var student = await _studentRepository.GetById(id) ?? throw new Exception("Estudante não encontrado para o Id informado."); - - // Verifica se o usuário existe - _ = await _userRepository.GetById(student.UserId) ?? throw new Exception("Usuário não encontrado para o Id informado."); - - // Remove o estudante - student = await _studentRepository.Delete(id); - if (student == null) - throw new Exception("O estudante não pôde ser removido."); - - // Remove o usuário - _ = await _userRepository.Delete(student.UserId) ?? throw new Exception("O usuário não pôde ser removido."); - - // Retorna o estudante removido - return _mapper.Map(student); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Student/GetStudentById.cs b/src/Domain/UseCases/Student/GetStudentById.cs deleted file mode 100644 index 66149626..00000000 --- a/src/Domain/UseCases/Student/GetStudentById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetStudentById : IGetStudentById - { - #region Global Scope - private readonly IStudentRepository _repository; - private readonly IMapper _mapper; - public GetStudentById(IStudentRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Student/GetStudents.cs b/src/Domain/UseCases/Student/GetStudents.cs deleted file mode 100644 index c32c5a29..00000000 --- a/src/Domain/UseCases/Student/GetStudents.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetStudents : IGetStudents - { - #region Global Scope - private readonly IStudentRepository _repository; - private readonly IMapper _mapper; - public GetStudents(IStudentRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/Student/UpdateStudent.cs b/src/Domain/UseCases/Student/UpdateStudent.cs deleted file mode 100644 index cfdc5ced..00000000 --- a/src/Domain/UseCases/Student/UpdateStudent.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Domain.Contracts.Student; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Entities.Enums; - -namespace Domain.UseCases -{ - public class UpdateStudent : IUpdateStudent - { - #region Global Scope - private readonly IStudentRepository _studentRepository; - private readonly IMapper _mapper; - public UpdateStudent(IStudentRepository studentRepository, IMapper mapper) - { - _studentRepository = studentRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateStudentInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Recupera entidade que será atualizada - var student = await _studentRepository.GetById(id) ?? throw new Exception("Nenhum estudante encontrado para o Id informado."); - - // Verifica se a entidade foi excluída - if (student.DeletedAt != null) - throw new Exception("O estudante informado já foi excluído."); - - // Atualiza atributos permitidos - student.BirthDate = input.BirthDate; - student.CampusId = input.CampusId; - student.CellPhone = input.CellPhone; - student.CellPhoneDDD = input.CellPhoneDDD; - student.CEP = input.CEP; - student.City = input.City; - student.CourseId = input.CourseId; - student.DispatchDate = input.DispatchDate; - student.HomeAddress = input.HomeAddress; - student.IssuingAgency = input.IssuingAgency; - student.Phone = input.Phone; - student.PhoneDDD = input.PhoneDDD; - student.RG = input.RG; - student.StartYear = input.StartYear; - student.UF = input.UF; - student.TypeAssistanceId = input.TypeAssistanceId; - - // Enums - student.Race = (ERace)input.Race; - student.Gender = (EGender)input.Gender; - - // Atualiza estudante com as informações fornecidas - student = await _studentRepository.Update(student); - - // Salva entidade atualizada no banco - return _mapper.Map(student); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/StudentDocuments/CreateStudentDocuments.cs b/src/Domain/UseCases/StudentDocuments/CreateStudentDocuments.cs deleted file mode 100644 index 5569bd6b..00000000 --- a/src/Domain/UseCases/StudentDocuments/CreateStudentDocuments.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; -using Domain.Interfaces.Services; -using Microsoft.AspNetCore.Http; - -namespace Domain.UseCases -{ - public class CreateStudentDocuments : ICreateStudentDocuments - { - #region Global Scope - private readonly IStudentDocumentsRepository _studentDocumentRepository; - private readonly IProjectRepository _projectRepository; - private readonly IStorageFileService _storageFileService; - private readonly IMapper _mapper; - - /// - /// Lista de arquivos que foram salvos na nuvem para remoção em caso de erro. - /// - private readonly List _urlFiles; - - public CreateStudentDocuments( - IStudentDocumentsRepository studentDocumentsRepository, - IProjectRepository projectRepository, - IStorageFileService storageFileService, - IMapper mapper) - { - _studentDocumentRepository = studentDocumentsRepository; - _projectRepository = projectRepository; - _storageFileService = storageFileService; - _mapper = mapper; - - _urlFiles = new List(); - } - #endregion - - public async Task Execute(CreateStudentDocumentsInput input) - { - // Verifica se já há documentos para o projeto informado - var documents = await _studentDocumentRepository.GetByProjectId(input.ProjectId!); - UseCaseException.BusinessRuleViolation(documents is null, "Student documents already exist for the indicated project."); - - // Verifica se o projeto existe - var project = await _projectRepository.GetById(input.ProjectId!); - UseCaseException.NotFoundEntityById(project is null, nameof(project)); - - // Verifica se o projeto se encontra em situação de submissão de documentos - UseCaseException.BusinessRuleViolation( - project?.Status != Entities.Enums.EProjectStatus.DocumentAnalysis, - "The project is not in the documents presentation phase."); - - // Cria entidade a partir do input informado - var entity = new Entities.StudentDocuments(input.ProjectId, input.AgencyNumber, input.AccountNumber); - - // Verifica se o aluno é menor de idade - if (project?.Student?.BirthDate > DateTime.Now.AddYears(-18)) - { - // Verifica se foi informado a autorização dos pais - UseCaseException.BusinessRuleViolation(input.ParentalAuthorization is null, - "Parental authorization must be provided for underage students."); - - // Salva autorização dos pais - entity.ParentalAuthorization = await TryToSaveFileInCloud(input.ParentalAuthorization!); - } - - // Salva demais arquivos na nuvem - entity.IdentityDocument = await TryToSaveFileInCloud(input.IdentityDocument!); - entity.CPF = await TryToSaveFileInCloud(input.CPF!); - entity.Photo3x4 = await TryToSaveFileInCloud(input.Photo3x4!); - entity.SchoolHistory = await TryToSaveFileInCloud(input.SchoolHistory!); - entity.ScholarCommitmentAgreement = await TryToSaveFileInCloud(input.ScholarCommitmentAgreement!); - entity.AccountOpeningProof = await TryToSaveFileInCloud(input.AccountOpeningProof!); - - // Cria entidade - entity = await _studentDocumentRepository.Create(entity); - - // Salva entidade no banco - return _mapper.Map(entity); - } - - private async Task TryToSaveFileInCloud(IFormFile file) - { - try - { - string url = await _storageFileService.UploadFileAsync(file); - _urlFiles.Add(url); - return url; - } - catch - { - // Caso dê erro, remove da nuvem os arquivos que foram salvos - foreach (var url in _urlFiles) - await _storageFileService.DeleteFile(url); - throw; - } - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/StudentDocuments/DeleteStudentDocuments.cs b/src/Domain/UseCases/StudentDocuments/DeleteStudentDocuments.cs deleted file mode 100644 index bb18573a..00000000 --- a/src/Domain/UseCases/StudentDocuments/DeleteStudentDocuments.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class DeleteStudentDocuments : IDeleteStudentDocuments - { - #region Global Scope - private readonly IStudentDocumentsRepository _repository; - private readonly IMapper _mapper; - public DeleteStudentDocuments(IStudentDocumentsRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id is null, nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o tipo de programa removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs b/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs deleted file mode 100644 index 1fdbc99d..00000000 --- a/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByProjectId.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class GetStudentDocumentsByProjectId : IGetStudentDocumentsByProjectId - { - #region Global Scope - private readonly IStudentDocumentsRepository _repository; - private readonly IMapper _mapper; - public GetStudentDocumentsByProjectId( - IStudentDocumentsRepository repository, - IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? projectId) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(projectId is null, nameof(projectId)); - - // Busca documentos do estudante pelo id do projeto - var entity = await _repository.GetByProjectId(projectId); - - // Retorna entidade mapeada - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs b/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs deleted file mode 100644 index 3f0d0d11..00000000 --- a/src/Domain/UseCases/StudentDocuments/GetStudentDocumentsByStudentId.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class GetStudentDocumentsByStudentId : IGetStudentDocumentsByStudentId - { - #region Global Scope - private readonly IStudentDocumentsRepository _repository; - private readonly IMapper _mapper; - public GetStudentDocumentsByStudentId( - IStudentDocumentsRepository repository, - IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? studentId) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(studentId is null, nameof(studentId)); - - // Busca documentos do estudante pelo id do projeto - var entity = await _repository.GetByStudentId(studentId); - - // Retorna entidade mapeada - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/StudentDocuments/UpdateStudentDocuments.cs b/src/Domain/UseCases/StudentDocuments/UpdateStudentDocuments.cs deleted file mode 100644 index 10a6d8c8..00000000 --- a/src/Domain/UseCases/StudentDocuments/UpdateStudentDocuments.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Domain.Contracts.StudentDocuments; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; -using Domain.Interfaces.Services; -using Microsoft.AspNetCore.Http; - -namespace Domain.UseCases -{ - public class UpdateStudentDocuments : IUpdateStudentDocuments - { - #region Global Scope - private readonly IStudentDocumentsRepository _studentDocumentRepository; - private readonly IProjectRepository _projectRepository; - private readonly IStorageFileService _storageFileService; - private readonly IMapper _mapper; - - public UpdateStudentDocuments( - IStudentDocumentsRepository studentDocumentsRepository, - IProjectRepository projectRepository, - IStorageFileService storageFileService, - IMapper mapper) - { - _studentDocumentRepository = studentDocumentsRepository; - _projectRepository = projectRepository; - _storageFileService = storageFileService; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateStudentDocumentsInput input) - { - // Verifica se o id foi informado - UseCaseException.NotInformedParam(id is null, nameof(id)); - - // Verifica se já foram enviados documentos para o projeto informado - var studentDocuments = await _studentDocumentRepository.GetById(id!); - UseCaseException.NotFoundEntityById(studentDocuments is not null, nameof(studentDocuments)); - - // Verifica se o projeto se encontra em situação de submissão de documentos - UseCaseException.BusinessRuleViolation( - studentDocuments!.Project?.Status != Entities.Enums.EProjectStatus.DocumentAnalysis - || studentDocuments!.Project?.Status != Entities.Enums.EProjectStatus.Pending, - "The project is not in the documents presentation phase."); - - // Atualiza entidade a partir do input informado - studentDocuments!.AgencyNumber = input.AgencyNumber; - studentDocuments!.AccountNumber = input.AccountNumber; - - // Atualiza arquivos na nuvem - await TryToSaveFileInCloud(input.IdentityDocument!, studentDocuments.IdentityDocument); - await TryToSaveFileInCloud(input.CPF!, studentDocuments.CPF); - await TryToSaveFileInCloud(input.Photo3x4!, studentDocuments.Photo3x4); - await TryToSaveFileInCloud(input.SchoolHistory!, studentDocuments.SchoolHistory); - await TryToSaveFileInCloud(input.ScholarCommitmentAgreement!, studentDocuments.ScholarCommitmentAgreement); - await TryToSaveFileInCloud(input.ParentalAuthorization!, studentDocuments.ParentalAuthorization); - await TryToSaveFileInCloud(input.AccountOpeningProof!, studentDocuments.AccountOpeningProof); - - // Atualiza entidade - studentDocuments = await _studentDocumentRepository.Update(studentDocuments); - - // Se o projeto está no status de pendente, atualiza para o status de análise de documentos - if (studentDocuments.Project?.Status == Entities.Enums.EProjectStatus.Pending) - { - var project = await _projectRepository.GetById(studentDocuments.ProjectId); - project!.Status = Entities.Enums.EProjectStatus.DocumentAnalysis; - await _projectRepository.Update(project); - } - - // Retorna entidade atualizada - return _mapper.Map(studentDocuments); - } - - private async Task TryToSaveFileInCloud(IFormFile file, string? url) - { - try - { - if (file is null) return; - await _storageFileService.UploadFileAsync(file, url); - } - catch - { - throw; - } - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/SubArea/CreateSubArea.cs b/src/Domain/UseCases/SubArea/CreateSubArea.cs deleted file mode 100644 index eb29d6a3..00000000 --- a/src/Domain/UseCases/SubArea/CreateSubArea.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using System.Threading.Tasks; -using System; - -namespace Domain.UseCases -{ - public class CreateSubArea : ICreateSubArea - { - #region Global Scope - private readonly ISubAreaRepository _subAreaRepository; - private readonly IAreaRepository _areaRepository; - private readonly IMapper _mapper; - public CreateSubArea(ISubAreaRepository subAreaRepository, IAreaRepository areaRepository, IMapper mapper) - { - _subAreaRepository = subAreaRepository; - _areaRepository = areaRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateSubAreaInput input) - { - var entity = await _subAreaRepository.GetByCode(input.Code); - if (entity != null) - throw new Exception($"Já existe uma Subárea para o código {input.Code}"); - - // Verifica id da área - if (input.AreaId == null) - throw new Exception("O Id da Área não pode ser vazio."); - - // Valida se existe área - var area = await _areaRepository.GetById(input.AreaId) ?? throw new Exception("A Área informada não existe."); - if (area.DeletedAt != null) - throw new Exception("A Área informada está inativa."); - - // Cria nova área - entity = await _subAreaRepository.Create(_mapper.Map(input)); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/SubArea/DeleteSubArea.cs b/src/Domain/UseCases/SubArea/DeleteSubArea.cs deleted file mode 100644 index b72b100e..00000000 --- a/src/Domain/UseCases/SubArea/DeleteSubArea.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteSubArea : IDeleteSubArea - { - #region Global Scope - private readonly ISubAreaRepository _repository; - private readonly IMapper _mapper; - public DeleteSubArea(ISubAreaRepository subAreaRepository, IMapper mapper) - { - _repository = subAreaRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o entidade removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/SubArea/GetSubAreaById.cs b/src/Domain/UseCases/SubArea/GetSubAreaById.cs deleted file mode 100644 index 9d7d6a4f..00000000 --- a/src/Domain/UseCases/SubArea/GetSubAreaById.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetSubAreaById : IGetSubAreaById - { - #region Global Scope - private readonly ISubAreaRepository _subAreaRepository; - private readonly IMapper _mapper; - public GetSubAreaById(ISubAreaRepository subAreaRepository, IMapper mapper) - { - _subAreaRepository = subAreaRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - var entity = await _subAreaRepository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/SubArea/GetSubAreasByArea.cs b/src/Domain/UseCases/SubArea/GetSubAreasByArea.cs deleted file mode 100644 index e4553c78..00000000 --- a/src/Domain/UseCases/SubArea/GetSubAreasByArea.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using System.Threading.Tasks; -using System.Linq; -using System; -using System.Collections.Generic; - -namespace Domain.UseCases -{ - public class GetSubAreasByArea : IGetSubAreasByArea - { - #region Global Scope - private readonly ISubAreaRepository _subAreaRepository; - private readonly IMapper _mapper; - public GetSubAreasByArea(ISubAreaRepository subAreaRepository, IMapper mapper) - { - _subAreaRepository = subAreaRepository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(Guid? areaId, int skip, int take) - { - var entities = await _subAreaRepository.GetSubAreasByArea(areaId, skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/SubArea/UpdateSubArea.cs b/src/Domain/UseCases/SubArea/UpdateSubArea.cs deleted file mode 100644 index c140fc43..00000000 --- a/src/Domain/UseCases/SubArea/UpdateSubArea.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Domain.Contracts.SubArea; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using System.Threading.Tasks; -using System; - -namespace Domain.UseCases -{ - public class UpdateSubArea : IUpdateSubArea - { - #region Global Scope - private readonly ISubAreaRepository _subAreaRepository; - private readonly IMapper _mapper; - public UpdateSubArea(ISubAreaRepository subAreaRepository, IMapper mapper) - { - _subAreaRepository = subAreaRepository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateSubAreaInput input) - { - // Recupera entidade que será atualizada - var entity = await _subAreaRepository.GetById(id) ?? throw new Exception("Subárea não encontrada."); - - // Atualiza atributos permitidos - entity.Name = input.Name; - entity.Code = input.Code; - entity.AreaId = input.AreaId; - - // Salva entidade atualizada no banco - var model = await _subAreaRepository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/TypeAssistance/CreateTypeAssistance.cs b/src/Domain/UseCases/TypeAssistance/CreateTypeAssistance.cs deleted file mode 100644 index 312c7a5a..00000000 --- a/src/Domain/UseCases/TypeAssistance/CreateTypeAssistance.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class CreateTypeAssistance : ICreateTypeAssistance - { - #region Global Scope - private readonly ITypeAssistanceRepository _repository; - private readonly IMapper _mapper; - public CreateTypeAssistance(ITypeAssistanceRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(CreateTypeAssistanceInput input) - { - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Verifica se já existe um tipo de programa com o nome indicado - var entity = await _repository.GetTypeAssistanceByName(input.Name!); - if (entity != null) - throw new Exception("Já existe um Tipo de Programa para o nome informado."); - - // Cria entidade - entity = await _repository.Create(_mapper.Map(input)); - - // Salva entidade no banco - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/TypeAssistance/DeleteTypeAssistance.cs b/src/Domain/UseCases/TypeAssistance/DeleteTypeAssistance.cs deleted file mode 100644 index 31ff6faf..00000000 --- a/src/Domain/UseCases/TypeAssistance/DeleteTypeAssistance.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class DeleteTypeAssistance : IDeleteTypeAssistance - { - #region Global Scope - private readonly ITypeAssistanceRepository _repository; - private readonly IMapper _mapper; - public DeleteTypeAssistance(ITypeAssistanceRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Remove a entidade - var model = await _repository.Delete(id); - - // Retorna o tipo de programa removido - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/TypeAssistance/GetTypeAssistanceById.cs b/src/Domain/UseCases/TypeAssistance/GetTypeAssistanceById.cs deleted file mode 100644 index eb687bb8..00000000 --- a/src/Domain/UseCases/TypeAssistance/GetTypeAssistanceById.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetTypeAssistanceById : IGetTypeAssistanceById - { - #region Global Scope - private readonly ITypeAssistanceRepository _repository; - private readonly IMapper _mapper; - public GetTypeAssistanceById(ITypeAssistanceRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - if (id == null) - throw new ArgumentNullException(nameof(id)); - - var entity = await _repository.GetById(id); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/TypeAssistance/GetTypeAssistances.cs b/src/Domain/UseCases/TypeAssistance/GetTypeAssistances.cs deleted file mode 100644 index e9558c2f..00000000 --- a/src/Domain/UseCases/TypeAssistance/GetTypeAssistances.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; - -namespace Domain.UseCases -{ - public class GetTypeAssistances : IGetTypeAssistances - { - #region Global Scope - private readonly ITypeAssistanceRepository _repository; - private readonly IMapper _mapper; - public GetTypeAssistances(ITypeAssistanceRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetAll(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/TypeAssistance/UpdateTypeAssistance.cs b/src/Domain/UseCases/TypeAssistance/UpdateTypeAssistance.cs deleted file mode 100644 index ebd5314f..00000000 --- a/src/Domain/UseCases/TypeAssistance/UpdateTypeAssistance.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Domain.Contracts.TypeAssistance; -using Domain.Interfaces.UseCases; -using AutoMapper; -using Domain.Interfaces.Repositories; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateTypeAssistance : IUpdateTypeAssistance - { - #region Global Scope - private readonly ITypeAssistanceRepository _repository; - private readonly IMapper _mapper; - public UpdateTypeAssistance(ITypeAssistanceRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id, UpdateTypeAssistanceInput input) - { - // Verifica se o id foi informado - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Verifica se nome foi informado - UseCaseException.NotInformedParam(string.IsNullOrEmpty(input.Name), nameof(input.Name)); - - // Recupera entidade que será atualizada - var entity = await _repository.GetById(id) - ?? throw new Exception("Bolsa de Assistência não encontrado."); - - // Verifica se a entidade foi excluída - if (entity.DeletedAt != null) - throw new Exception("O Bolsa de Assistência informado já foi excluído."); - - // Verifica se o nome já está sendo usado - if (!string.Equals(entity.Name, input.Name, StringComparison.OrdinalIgnoreCase) - && await _repository.GetTypeAssistanceByName(input.Name!) != null) - { - throw new Exception("Já existe um Bolsa de Assistência para o nome informado."); - } - - // Atualiza atributos permitidos - entity.Name = input.Name; - entity.Description = input.Description; - - // Salva entidade atualizada no banco - var model = await _repository.Update(entity); - return _mapper.Map(model); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/ActivateUser.cs b/src/Domain/UseCases/User/ActivateUser.cs deleted file mode 100644 index f134b6d8..00000000 --- a/src/Domain/UseCases/User/ActivateUser.cs +++ /dev/null @@ -1,36 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class ActivateUser : IActivateUser - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly IMapper _mapper; - public ActivateUser(IUserRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se id é nulo - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Encontra usuário pelo Id e o ativa - var user = await _repository.GetById(id) - ?? throw new Exception("Nenhum usuário encontrato para o id informado."); - user.ActivateEntity(); - - // Atualiza usuário - var entity = await _repository.Update(user); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/DeactivateUser.cs b/src/Domain/UseCases/User/DeactivateUser.cs deleted file mode 100644 index 749cb60a..00000000 --- a/src/Domain/UseCases/User/DeactivateUser.cs +++ /dev/null @@ -1,36 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class DeactivateUser : IDeactivateUser - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly IMapper _mapper; - public DeactivateUser(IUserRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se id é nulo - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Encontra usuário pelo Id e o desativa - var user = await _repository.GetById(id) - ?? throw new Exception("Nenhum usuário encontrato para o id informado."); - user.DeactivateEntity(); - - // Atualiza usuário - var entity = await _repository.Update(user); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/GetActiveUsers.cs b/src/Domain/UseCases/User/GetActiveUsers.cs deleted file mode 100644 index 240b7738..00000000 --- a/src/Domain/UseCases/User/GetActiveUsers.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class GetActiveUsers : IGetActiveUsers - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly IMapper _mapper; - public GetActiveUsers(IUserRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetActiveUsers(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/GetInactiveUsers.cs b/src/Domain/UseCases/User/GetInactiveUsers.cs deleted file mode 100644 index 6cc2046e..00000000 --- a/src/Domain/UseCases/User/GetInactiveUsers.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class GetInactiveUsers : IGetInactiveUsers - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly IMapper _mapper; - public GetInactiveUsers(IUserRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task> Execute(int skip, int take) - { - var entities = await _repository.GetInactiveUsers(skip, take); - return _mapper.Map>(entities).AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/GetUserById.cs b/src/Domain/UseCases/User/GetUserById.cs deleted file mode 100644 index 9dd611ad..00000000 --- a/src/Domain/UseCases/User/GetUserById.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.UseCases; - -namespace Domain.UseCases -{ - public class GetUserById : IGetUserById - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly IMapper _mapper; - public GetUserById(IUserRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - #endregion - - public async Task Execute(Guid? id) - { - // Verifica se o id informado é nulo - if (id == null) - throw new ArgumentNullException(nameof(id)); - - // Busca usuário pelo id informado - var entity = await _repository.GetById(id) - ?? throw new Exception("Nenhum usuário encontrato para o id informado."); - - // Retorna usuário encontrado - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/UseCases/User/UpdateUser.cs b/src/Domain/UseCases/User/UpdateUser.cs deleted file mode 100644 index 6389fbdf..00000000 --- a/src/Domain/UseCases/User/UpdateUser.cs +++ /dev/null @@ -1,45 +0,0 @@ -using AutoMapper; -using Domain.Contracts.User; -using Domain.Interfaces.Repositories; -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases; -using Domain.Validation; - -namespace Domain.UseCases -{ - public class UpdateUser : IUpdateUser - { - #region Global Scope - private readonly IUserRepository _repository; - private readonly ITokenAuthenticationService _tokenAuthenticationService; - private readonly IMapper _mapper; - public UpdateUser(IUserRepository repository, ITokenAuthenticationService tokenAuthenticationService, IMapper mapper) - { - _repository = repository; - _tokenAuthenticationService = tokenAuthenticationService; - _mapper = mapper; - } - #endregion - - public async Task Execute(UserUpdateInput input) - { - // Busca as claims do usuário autenticado - var userClaims = _tokenAuthenticationService.GetUserAuthenticatedClaims(); - - // Verifica se o id informado é nulo - UseCaseException.NotInformedParam(userClaims.Id is null, nameof(userClaims.Id)); - - // Busca usuário pelo id informado - var user = await _repository.GetById(userClaims.Id) - ?? throw new Exception("Nenhum usuário encontrato para o id informado."); - - // Atualiza atributos permitidos - user.Name = input.Name; - user.CPF = input.CPF; - - // Salva usuário atualizado no banco - var entity = await _repository.Update(user); - return _mapper.Map(entity); - } - } -} \ No newline at end of file diff --git a/src/Domain/Validation/ExceptionMessageFactory.cs b/src/Domain/Validation/ExceptionMessageFactory.cs index d790a8b2..d6eada9f 100644 --- a/src/Domain/Validation/ExceptionMessageFactory.cs +++ b/src/Domain/Validation/ExceptionMessageFactory.cs @@ -1,12 +1,15 @@ namespace Domain.Validation; public static class ExceptionMessageFactory { - public static string MinLength(string prop, int length) => $"The value of ({prop}) is too short. The minimum length is {length} characters."; - public static string MaxLength(string prop, int length) => $"The value of ({prop}) is too long. The maximum length is {length} characters."; - public static string WithLength(string prop, int length) => $"Invalid value for ({prop}). The number of characters should be {length}."; - public static string Required(string prop) => $"Invalid value for ({prop}). {prop} must be provided."; - public static string Invalid(string prop) => $"Invalid value for ({prop})."; - public static string InvalidEmail(string prop) => $"Invalid value for ({prop}). The provided email is not valid."; - public static string InvalidCpf() => "Invalid CPF value. The number sequence is not a valid CPF according to government logic."; - public static string LessThan(string prop, string value) => $"The value of ({prop}) cannot be less than {value}."; + public static string MinLength(string prop, int length) => $"O valor de ({prop}) é muito curto. O comprimento mínimo é de {length} caracteres."; + public static string MaxLength(string prop, int length) => $"O valor de ({prop}) é muito longo. O comprimento máximo é de {length} caracteres."; + public static string WithLength(string prop, int length) => $"Valor inválido para ({prop}). O número de caracteres deve ser {length}."; + public static string MinValue(string prop, int length) => $"O valor de ({prop}) deve ser maior ou igual a {length}."; + public static string MaxValue(string prop, int length) => $"O valor de ({prop}) deve ser menor ou igual a {length}."; + public static string Required(string prop) => $"Valor inválido para ({prop}). {prop} deve ser fornecido."; + public static string Invalid(string prop) => $"Valor inválido para ({prop})."; + public static string InvalidEmail(string prop) => $"Valor inválido para ({prop}). O email fornecido não é válido."; + public static string InvalidCpf() => "Valor de CPF inválido. A sequência numérica não é um CPF válido de acordo com a lógica do governo."; + public static string LessThan(string prop, string value) => $"O valor de ({prop}) não pode ser menor que {value}."; + public static string BusinessRuleViolation(string message) => message; } \ No newline at end of file diff --git a/src/Domain/Validation/UseCaseException.cs b/src/Domain/Validation/UseCaseException.cs deleted file mode 100644 index dfafe943..00000000 --- a/src/Domain/Validation/UseCaseException.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Domain.Validation; -public class UseCaseException : Exception -{ - public UseCaseException(string error) : base(error) { } - public UseCaseException() : base() { } - public UseCaseException(string? message, Exception? innerException) : base(message, innerException) { } - - public static Exception BusinessRuleViolation(string message) => new UseCaseException(message); - public static Exception NotFoundEntityById(string entityName) => new UseCaseException($"Entity ({entityName}) not found by informed id."); - public static Exception NotFoundEntityByParams(string entityName) => new UseCaseException($"Entity ({entityName}) not found by informed parameters."); - - public static void NotFoundEntityById(bool hasError, string entityName) - { - if (hasError) throw new UseCaseException($"Entity ({entityName}) not found by informed id."); - } - - public static void BusinessRuleViolation(bool hasError, string message) - { - if (hasError) throw new UseCaseException(message); - } - - public static void NotInformedParam(bool hasError, string paramName) - { - if (hasError) throw new UseCaseException($"Parameter ({paramName}) is required."); - } -} diff --git a/src/GPIC.BackEnd.sln b/src/GPIC.BackEnd.sln new file mode 100644 index 00000000..0d3489a8 --- /dev/null +++ b/src/GPIC.BackEnd.sln @@ -0,0 +1,79 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{A5928034-02D5-427D-B83C-10F8F249CA1C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{C7DC5551-8325-461D-9710-05EEC9EF8791}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{6CC50902-ECC4-4A3A-A03D-115DD1378038}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence", "Infrastructure\Persistence\Persistence.csproj", "{03F5DBCF-08FA-46A4-ACBB-F933C3C1B520}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Infrastructure\Services\Services.csproj", "{3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IoC", "Infrastructure\IoC\IoC.csproj", "{92667C72-14F6-44A5-9152-8B512ED249DA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPI", "Infrastructure\WebAPI\WebAPI.csproj", "{BE5B727D-878C-4E0E-AD3B-60B32781DFFF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebFunctions", "Infrastructure\WebFunctions\WebFunctions.csproj", "{A994F6CD-1C40-4C27-8BDF-85AFF7543A03}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain.Tests", "Domain.Tests\Domain.Tests.csproj", "{4E50EC16-4163-4B66-BA9A-33BB45E99035}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.Tests", "Application.Tests\Application.Tests.csproj", "{682499DF-F2AE-429A-B48D-CBE55C09F223}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A5928034-02D5-427D-B83C-10F8F249CA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5928034-02D5-427D-B83C-10F8F249CA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5928034-02D5-427D-B83C-10F8F249CA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5928034-02D5-427D-B83C-10F8F249CA1C}.Release|Any CPU.Build.0 = Release|Any CPU + {C7DC5551-8325-461D-9710-05EEC9EF8791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7DC5551-8325-461D-9710-05EEC9EF8791}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7DC5551-8325-461D-9710-05EEC9EF8791}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7DC5551-8325-461D-9710-05EEC9EF8791}.Release|Any CPU.Build.0 = Release|Any CPU + {03F5DBCF-08FA-46A4-ACBB-F933C3C1B520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03F5DBCF-08FA-46A4-ACBB-F933C3C1B520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03F5DBCF-08FA-46A4-ACBB-F933C3C1B520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03F5DBCF-08FA-46A4-ACBB-F933C3C1B520}.Release|Any CPU.Build.0 = Release|Any CPU + {3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8}.Release|Any CPU.Build.0 = Release|Any CPU + {92667C72-14F6-44A5-9152-8B512ED249DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92667C72-14F6-44A5-9152-8B512ED249DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92667C72-14F6-44A5-9152-8B512ED249DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92667C72-14F6-44A5-9152-8B512ED249DA}.Release|Any CPU.Build.0 = Release|Any CPU + {BE5B727D-878C-4E0E-AD3B-60B32781DFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE5B727D-878C-4E0E-AD3B-60B32781DFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE5B727D-878C-4E0E-AD3B-60B32781DFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE5B727D-878C-4E0E-AD3B-60B32781DFFF}.Release|Any CPU.Build.0 = Release|Any CPU + {A994F6CD-1C40-4C27-8BDF-85AFF7543A03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A994F6CD-1C40-4C27-8BDF-85AFF7543A03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A994F6CD-1C40-4C27-8BDF-85AFF7543A03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A994F6CD-1C40-4C27-8BDF-85AFF7543A03}.Release|Any CPU.Build.0 = Release|Any CPU + {4E50EC16-4163-4B66-BA9A-33BB45E99035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E50EC16-4163-4B66-BA9A-33BB45E99035}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E50EC16-4163-4B66-BA9A-33BB45E99035}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E50EC16-4163-4B66-BA9A-33BB45E99035}.Release|Any CPU.Build.0 = Release|Any CPU + {682499DF-F2AE-429A-B48D-CBE55C09F223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {682499DF-F2AE-429A-B48D-CBE55C09F223}.Debug|Any CPU.Build.0 = Debug|Any CPU + {682499DF-F2AE-429A-B48D-CBE55C09F223}.Release|Any CPU.ActiveCfg = Release|Any CPU + {682499DF-F2AE-429A-B48D-CBE55C09F223}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {03F5DBCF-08FA-46A4-ACBB-F933C3C1B520} = {6CC50902-ECC4-4A3A-A03D-115DD1378038} + {3236030B-AD8F-4FAB-AAF6-3808D7B3F8E8} = {6CC50902-ECC4-4A3A-A03D-115DD1378038} + {92667C72-14F6-44A5-9152-8B512ED249DA} = {6CC50902-ECC4-4A3A-A03D-115DD1378038} + {BE5B727D-878C-4E0E-AD3B-60B32781DFFF} = {6CC50902-ECC4-4A3A-A03D-115DD1378038} + {A994F6CD-1C40-4C27-8BDF-85AFF7543A03} = {6CC50902-ECC4-4A3A-A03D-115DD1378038} + EndGlobalSection +EndGlobal diff --git a/src/Infrastructure/IoC/ApplicationDI.cs b/src/Infrastructure/IoC/ApplicationDI.cs new file mode 100644 index 00000000..f845fdd2 --- /dev/null +++ b/src/Infrastructure/IoC/ApplicationDI.cs @@ -0,0 +1,223 @@ +using Domain.Mappings; +using Application.UseCases.ActivityType; +using Application.UseCases.Area; +using Application.UseCases.AssistanceType; +using Application.UseCases.Auth; +using Application.UseCases.Campus; +using Application.UseCases.Course; +using Application.UseCases.MainArea; +using Application.UseCases.Notice; +using Application.UseCases.Professor; +using Application.UseCases.ProgramType; +using Application.UseCases.Project; +using Application.UseCases.ProjectEvaluation; +using Application.UseCases.Student; +using Application.UseCases.StudentDocuments; +using Application.UseCases.SubArea; +using Application.UseCases.User; +using Application.Interfaces.UseCases.ActivityType; +using Application.Interfaces.UseCases.Area; +using Application.Interfaces.UseCases.AssistanceType; +using Application.Interfaces.UseCases.Auth; +using Application.Interfaces.UseCases.Campus; +using Application.Interfaces.UseCases.Course; +using Application.Interfaces.UseCases.MainArea; +using Application.Interfaces.UseCases.Notice; +using Application.Interfaces.UseCases.Professor; +using Application.Interfaces.UseCases.ProgramType; +using Application.Interfaces.UseCases.Project; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Application.Interfaces.UseCases.Student; +using Application.Interfaces.UseCases.StudentDocuments; +using Application.Interfaces.UseCases.SubArea; +using Application.Interfaces.UseCases.User; +using Microsoft.Extensions.DependencyInjection; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Application.UseCases.ProjectFinalReport; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Application.UseCases.ProjectPartialReport; + +namespace Infrastructure.IoC +{ + public static class ApplicationDI + { + public static IServiceCollection AddApplication(this IServiceCollection services) + { + #region UseCases + #region Area + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Area + + #region ActivityType + services.AddScoped(); + services.AddScoped(); + #endregion ActivityType + + #region AssistanceType + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion AssistanceType + + #region Auth + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Auth + + #region Campus + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Campus + + #region Course + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Course + + #region MainArea + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion MainArea + + #region Notice + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Notice + + #region Professor + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Professor + + #region ProgramType + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion ProgramType + + #region Project + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Project + + #region ProjectEvaluation + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion ProjectEvaluation + + #region ProjectFinalReport + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion ProjectFinalReport + + #region ProjectPartialReport + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion ProjectPartialReport + + #region Student + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Student + + #region StudentDocuments + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion StudentDocuments + + #region SubArea + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion SubArea + + #region User + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion User + + #endregion UseCases + + #region Port Mappers + services.AddAutoMapper(typeof(AreaMappings)); + services.AddAutoMapper(typeof(ActivityMappings)); + services.AddAutoMapper(typeof(AssistanceTypeMappings)); + services.AddAutoMapper(typeof(CampusMappings)); + services.AddAutoMapper(typeof(CourseMappings)); + services.AddAutoMapper(typeof(MainAreaMappings)); + services.AddAutoMapper(typeof(NoticeMappings)); + services.AddAutoMapper(typeof(ProfessorMappings)); + services.AddAutoMapper(typeof(ProgramTypeMappings)); + services.AddAutoMapper(typeof(ProjectEvaluationMappings)); + services.AddAutoMapper(typeof(ProjectMappings)); + services.AddAutoMapper(typeof(StudentDocumentsMappings)); + services.AddAutoMapper(typeof(StudentMappings)); + services.AddAutoMapper(typeof(SubAreaMappings)); + services.AddAutoMapper(typeof(UserMappings)); + #endregion Port Mappers + + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/DependencyAdaptersInjection.cs b/src/Infrastructure/IoC/DependencyAdaptersInjection.cs deleted file mode 100644 index 10c6a514..00000000 --- a/src/Infrastructure/IoC/DependencyAdaptersInjection.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Adapters.Mappings; -using Adapters.Interfaces; -using Adapters.PresenterController; -using Microsoft.Extensions.DependencyInjection; - -namespace Infrastructure.IoC; -public static class DependencyAdaptersInjection -{ - public static IServiceCollection AddAdapters(this IServiceCollection services) - { - #region PresenterControllers - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Gateways Mappers - services.AddAutoMapper(typeof(AreaMappings)); - services.AddAutoMapper(typeof(AuthMappings)); - services.AddAutoMapper(typeof(CampusMappings)); - services.AddAutoMapper(typeof(CourseMappings)); - services.AddAutoMapper(typeof(MainAreaMappings)); - services.AddAutoMapper(typeof(NoticeMappings)); - services.AddAutoMapper(typeof(ProfessorMappings)); - services.AddAutoMapper(typeof(ProgramTypeMappings)); - services.AddAutoMapper(typeof(ProjectEvaluationMapping)); - services.AddAutoMapper(typeof(ProjectMappings)); - services.AddAutoMapper(typeof(TypeAssistanceMappings)); - services.AddAutoMapper(typeof(StudentDocumentsMappings)); - services.AddAutoMapper(typeof(StudentMappings)); - services.AddAutoMapper(typeof(SubAreaMappings)); - services.AddAutoMapper(typeof(UserMappings)); - #endregion - - return services; - } -} \ No newline at end of file diff --git a/src/Infrastructure/IoC/DependencyDomainInjection.cs b/src/Infrastructure/IoC/DependencyDomainInjection.cs deleted file mode 100644 index 0e0b57da..00000000 --- a/src/Infrastructure/IoC/DependencyDomainInjection.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Domain.Interfaces.Services; -using Domain.Interfaces.UseCases; -using Domain.Interfaces.UseCases.Project; -using Domain.Interfaces.UseCases.ProjectEvaluation; -using Domain.Mappings; -using Domain.UseCases; -using Domain.UseCases.Project; -using Domain.UseCases.ProjectEvaluation; -using Infrastructure.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Infrastructure.IoC; -public static class DependencyDomainInjection -{ - public static IServiceCollection AddDomain(this IServiceCollection services) - { - #region External Services - services.AddHttpContextAccessor(); - services.AddScoped(); - services.AddScoped(); -#if !DEBUG - services.AddScoped(); -#else - services.AddScoped(); -#endif - #endregion - - #region UseCases - #region Area - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Auth - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Campus - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Course - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region MainArea - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Notice - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Professor - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region ProgramType - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Project - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region ProjectEvaluation - // services.AddScoped(); - // services.AddScoped(); - // services.AddScoped(); - #endregion - - #region Student - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region TypeAssistance - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region StudentDocuments - // services.AddScoped(); - // services.AddScoped(); - // services.AddScoped(); - // services.AddScoped(); - // services.AddScoped(); - // services.AddScoped(); - #endregion - - #region SubArea - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region User - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #endregion - - #region Contract Mappers - services.AddAutoMapper(typeof(AreaMappings)); - services.AddAutoMapper(typeof(CampusMappings)); - services.AddAutoMapper(typeof(CourseMappings)); - services.AddAutoMapper(typeof(MainAreaMappings)); - services.AddAutoMapper(typeof(NoticeMappings)); - services.AddAutoMapper(typeof(ProfessorMappings)); - services.AddAutoMapper(typeof(ProgramTypeMappings)); - services.AddAutoMapper(typeof(ProjectEvaluationMappings)); - services.AddAutoMapper(typeof(ProjectMappings)); - services.AddAutoMapper(typeof(TypeAssistanceMappings)); - services.AddAutoMapper(typeof(StudentDocumentsMappings)); - services.AddAutoMapper(typeof(StudentMappings)); - services.AddAutoMapper(typeof(SubAreaMappings)); - services.AddAutoMapper(typeof(UserMappings)); - #endregion - - return services; - } -} \ No newline at end of file diff --git a/src/Infrastructure/IoC/DependencyInjection.cs b/src/Infrastructure/IoC/DependencyInjection.cs deleted file mode 100644 index e2c1ed65..00000000 --- a/src/Infrastructure/IoC/DependencyInjection.cs +++ /dev/null @@ -1,94 +0,0 @@ -using AspNetCoreRateLimit; -using Domain.Interfaces.Repositories; -using Infrastructure.IoC.Utils; -using Infrastructure.Persistence.Context; -using Infrastructure.Persistence.Repositories; -using Infrastructure.Services; -using Infrastructure.Services.Email.Configs; -using Infrastructure.Services.Email.Factories; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Serilog; - -namespace Infrastructure.IoC; -public static class DependencyInjection -{ - public static IServiceCollection AddInfrastructure(this IServiceCollection services) - { - // Define valores das propriedades de configuração - IConfiguration configuration = SettingsConfiguration.GetConfiguration(); - services.AddSingleton(configuration); - - // Carrega informações de ambiente (.env) - var dotEnvSecrets = new DotEnvSecrets(); - services.AddScoped(); - - #region Inicialização do banco de dados -#if !DEBUG - services.AddDbContext( - o => o.UseNpgsql(configuration.GetConnectionString("DefaultConnection"), - b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); -#else - services.AddDbContext( - o => o.UseNpgsql(dotEnvSecrets.GetDatabaseConnectionString(), - b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); -#endif - #endregion - - #region Serviço de Log  - Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) - .CreateLogger(); - services.AddLogging(loggingBuilder => - { - loggingBuilder.ClearProviders(); - loggingBuilder.AddSerilog(Log.Logger, dispose: true); - }); - #endregion - - #region Serviço de E-mail - var smtpConfig = new SmtpConfiguration(); - configuration.GetSection("SmtpConfiguration").Bind(smtpConfig); - smtpConfig.Password = dotEnvSecrets.GetSmtpUserPassword(); - smtpConfig.Username = dotEnvSecrets.GetSmtpUserName(); - services.AddSingleton(); - services.AddSingleton(sp => - { - var factory = sp.GetRequiredService(); - return factory.Create(smtpConfig); - }); - #endregion - - #region Repositórios - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Rate Limit - services.AddMemoryCache(); - services.AddInMemoryRateLimiting(); - services.Configure(configuration.GetSection("IpRateLimiting")); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - #endregion - - return services; - } -} \ No newline at end of file diff --git a/src/Infrastructure/IoC/DependencyInjectionJWT.cs b/src/Infrastructure/IoC/DependencyInjectionJWT.cs deleted file mode 100644 index 59707ca3..00000000 --- a/src/Infrastructure/IoC/DependencyInjectionJWT.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Infrastructure.IoC.Utils; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using System.Text; - -namespace Infrastructure.IoC; -public static class DependencyInjectionJWT -{ - public static IServiceCollection AddInfrastructureJWT(this IServiceCollection services) - { - // Carrega informações de ambiente (.env) - var dotEnvSecrets = new DotEnvSecrets(); - services.AddSingleton(dotEnvSecrets); - - /// Informar o tipo de autenticação; - /// Definir o modelo de desafio de autenticação. - services.AddAuthentication(opt => - { - opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - - /// Habilita a autenticação JWT usando o esquema e desafio definidos; - /// Validar o token. - .AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - /// Valores válidos - ValidIssuer = dotEnvSecrets.GetJwtIssuer(), - ValidAudience = dotEnvSecrets.GetJwtAudience(), - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(dotEnvSecrets.GetJwtSecret())), - /// Se não fizer isso ele vai inserir + 5min em cima - /// do que foi definido na geração do Token. - ClockSkew = TimeSpan.Zero - }; - }); - - /// Define as políticas de autorização - services.AddAuthorization(options => - { - options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("ADMIN")); - options.AddPolicy("RequireProfessorRole", policy => policy.RequireRole("PROFESSOR")); - options.AddPolicy("RequireStudentRole", policy => policy.RequireRole("STUDENT")); - }); - return services; - } -} \ No newline at end of file diff --git a/src/Infrastructure/IoC/DependencyInjectionSwagger.cs b/src/Infrastructure/IoC/DependencyInjectionSwagger.cs deleted file mode 100644 index b5dde348..00000000 --- a/src/Infrastructure/IoC/DependencyInjectionSwagger.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Infrastructure.IoC -{ - public static class DependencyInjectionSwagger - { - public static IServiceCollection AddInfrastructureSwagger(this IServiceCollection services) - { - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(c => - { - // Adiciona documentação com Swagger - c.SwaggerDoc("v1", new OpenApiInfo - { - Version = "v1", - Title = "Infrastructure.WebAPI", - Description = "API Rest criada em .NET 7.0 para controle de projetos de iniciação científica do CEFET." - }); - - // Adiciona comentários dos métodos nas rotas do Swagger - c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Infrastructure.WebAPI.xml")); - - // Adiciona o JWT como esquema de segurança - c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() - { - Name = "Authorization", - Type = SecuritySchemeType.ApiKey, - Scheme = "Bearer", - BearerFormat = "JWT", - In = ParameterLocation.Header, - Description = "JWT Authorization header using the Bearer scheme." - }); - - // Adiciona o JWT como esquema de segurança global para todas as rotas - c.AddSecurityRequirement(new OpenApiSecurityRequirement() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } - }); - }); - services.AddSingleton(); - return services; - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/IoC/ExternalServicesDI.cs b/src/Infrastructure/IoC/ExternalServicesDI.cs new file mode 100644 index 00000000..6242dbe7 --- /dev/null +++ b/src/Infrastructure/IoC/ExternalServicesDI.cs @@ -0,0 +1,46 @@ +using Domain.Interfaces.Services; +using Infrastructure.IoC.Utils; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Services; +using Services.Email.Configs; +using Services.Email.Factories; + +namespace Infrastructure.IoC +{ + public static class ExternalServicesDI + { + public static IServiceCollection AddExternalServices(this IServiceCollection services) + { + // Define valores das propriedades de configuração + IConfiguration configuration = SettingsConfiguration.GetConfiguration(); + + // Carrega informações de ambiente (.env) + DotEnvSecrets dotEnvSecrets = new(); + + #region Serviço de E-mail + SmtpConfiguration smtpConfig = new(); + configuration.GetSection("SmtpConfiguration").Bind(smtpConfig); + smtpConfig.Password = dotEnvSecrets.GetSmtpUserPassword(); + smtpConfig.Username = dotEnvSecrets.GetSmtpUserName(); + services.AddSingleton(); + services.AddSingleton(sp => + { + IEmailServiceFactory factory = sp.GetRequiredService(); + return factory.Create(smtpConfig, dotEnvSecrets.GetFrontEndUrl()); + }); + #endregion Serviço de E-mail + + #region Demais Serviços + services.AddHttpContextAccessor(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + // services.AddScoped(); + services.AddScoped(); + #endregion Demais Serviços + + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/InfrastructureDI.cs b/src/Infrastructure/IoC/InfrastructureDI.cs new file mode 100644 index 00000000..afe41016 --- /dev/null +++ b/src/Infrastructure/IoC/InfrastructureDI.cs @@ -0,0 +1,40 @@ +using Infrastructure.IoC.Utils; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Services; + +namespace Infrastructure.IoC +{ + public static class InfrastructureDI + { + public static IServiceCollection AddInfrastructure(this IServiceCollection services, ref IConfiguration? configuration, HostBuilderContext? hostContext = null) + { + #region AppSettings e DotEnv + // Define valores das propriedades de configuração + configuration = SettingsConfiguration.GetConfiguration(hostContext); + services.AddSingleton(configuration); + + // Carrega informações de ambiente (.env) + DotEnvSecrets dotEnvSecrets = new(); + services.AddScoped(); + #endregion AppSettings e DotEnv + + #region Serviço de Log + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .WriteTo.Seq(dotEnvSecrets.GetSeqUrl(), apiKey: dotEnvSecrets.GetSeqApiKey()) + .CreateLogger(); + services.AddLogging(loggingBuilder => + { + loggingBuilder.ClearProviders(); + loggingBuilder.AddSerilog(Log.Logger, dispose: true); + }); + #endregion Serviço de Log + + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/IoC.csproj b/src/Infrastructure/IoC/IoC.csproj index 446907af..bcfd766a 100644 --- a/src/Infrastructure/IoC/IoC.csproj +++ b/src/Infrastructure/IoC/IoC.csproj @@ -3,13 +3,21 @@ net7.0 enable enable - 0.0.1 + 0.1.0 - - - - + + + + + + + + + + + + diff --git a/src/Infrastructure/IoC/JwtDI.cs b/src/Infrastructure/IoC/JwtDI.cs new file mode 100644 index 00000000..fdc77bef --- /dev/null +++ b/src/Infrastructure/IoC/JwtDI.cs @@ -0,0 +1,55 @@ +using System.Text; +using Infrastructure.IoC.Utils; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; + +namespace Infrastructure.IoC +{ + public static class JwtDI + { + public static IServiceCollection AddInfrastructureJWT(this IServiceCollection services) + { + // Carrega informações de ambiente (.env) + DotEnvSecrets dotEnvSecrets = new(); + services.AddSingleton(dotEnvSecrets); + + /// Informar o tipo de autenticação; + /// Definir o modelo de desafio de autenticação. + services.AddAuthentication(opt => + { + opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + + /// Habilita a autenticação JWT usando o esquema e desafio definidos; + /// Validar o token. + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + /// Valores válidos + ValidIssuer = dotEnvSecrets.GetJwtIssuer(), + ValidAudience = dotEnvSecrets.GetJwtAudience(), + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(dotEnvSecrets.GetJwtSecret())), + /// Se não fizer isso ele vai inserir + 5min em cima + /// do que foi definido na geração do Token. + ClockSkew = TimeSpan.Zero + }; + }); + + /// Define as políticas de autorização + services.AddAuthorization(options => + { + options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("ADMIN")); + options.AddPolicy("RequireProfessorRole", policy => policy.RequireRole("PROFESSOR")); + options.AddPolicy("RequireStudentRole", policy => policy.RequireRole("STUDENT")); + }); + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/PersistenceDI.cs b/src/Infrastructure/IoC/PersistenceDI.cs new file mode 100644 index 00000000..e3a4f05a --- /dev/null +++ b/src/Infrastructure/IoC/PersistenceDI.cs @@ -0,0 +1,64 @@ +using Domain.Interfaces.Repositories; +using Infrastructure.IoC.Utils; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Repositories; + +namespace Infrastructure.IoC +{ + public static class PersistenceDI + { + public static IServiceCollection AddPersistence(this IServiceCollection services) + { + // Carrega informações de ambiente (.env) + DotEnvSecrets dotEnvSecrets = new(); + + #region Inicialização do banco de dados + services.AddDbContext( + o => o.UseNpgsql(dotEnvSecrets.GetDatabaseConnectionString(), + b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); + #endregion Inicialização do banco de dados + + #region Migração do banco de dados + if (dotEnvSecrets.ExecuteMigration()) + { + try + { + var dbContext = services.BuildServiceProvider().GetService(); + dbContext?.Database.Migrate(); + Console.WriteLine("Migração realizada com sucesso."); + } + catch (Exception ex) + { + Console.WriteLine($"Ocorreu um erro durante a migração do banco de dados. {ex}"); + } + } + #endregion Migração do banco de dados + + #region Repositórios + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion Repositórios + + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/SwaggerDI.cs b/src/Infrastructure/IoC/SwaggerDI.cs new file mode 100644 index 00000000..2d38bfe9 --- /dev/null +++ b/src/Infrastructure/IoC/SwaggerDI.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Infrastructure.IoC +{ + public static class SwaggerDI + { + public static IServiceCollection AddInfrastructureSwagger(this IServiceCollection services) + { + _ = services.AddEndpointsApiExplorer(); + _ = services.AddSwaggerGen(c => + { + // Adiciona documentação com Swagger + c.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Infrastructure.WebAPI", + Description = "API Rest criada em .NET 7.0 para controle de projetos de iniciação científica do CEFET." + }); + + // Adiciona comentários dos métodos nas rotas do Swagger + c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Infrastructure.WebAPI.xml")); + + // Adiciona o JWT como esquema de segurança + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT Authorization header using the Bearer scheme." + }); + + // Adiciona o JWT como esquema de segurança global para todas as rotas + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + }); + _ = services.AddSingleton(); + return services; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/IoC/Utils/DotEnvSecrets.cs b/src/Infrastructure/IoC/Utils/DotEnvSecrets.cs index 11993b09..38aaab23 100644 --- a/src/Infrastructure/IoC/Utils/DotEnvSecrets.cs +++ b/src/Infrastructure/IoC/Utils/DotEnvSecrets.cs @@ -1,31 +1,40 @@ -using Infrastructure.Services; +using Services; -namespace Infrastructure.IoC.Utils; -public class DotEnvSecrets : IDotEnvSecrets +namespace Infrastructure.IoC.Utils { - public DotEnvSecrets() + public class DotEnvSecrets : IDotEnvSecrets { - // Caminho base para o arquivo appsettings.json - var basePath = Path.GetDirectoryName(typeof(DotEnvSecrets).Assembly.Location); - - // Carrega informações de ambiente (.env) - DotNetEnv.Env.Load(Path.Combine(basePath!, ".env")); + public DotEnvSecrets() + { + // Caminho base para o arquivo appsettings.json + string? basePath = Path.GetDirectoryName(typeof(DotEnvSecrets).Assembly.Location); + + // Carrega informações de ambiente (.env) + _ = DotNetEnv.Env.Load(Path.Combine(basePath!, ".env")); + } + + public string GetFrontEndUrl() => DotNetEnv.Env.GetString("FRONTEND_URL"); + public string GetSeqUrl() => DotNetEnv.Env.GetString("SEQ_URL"); + public string GetSeqApiKey() => DotNetEnv.Env.GetString("SEQ_API_KEY"); + public string GetBlobStorageConnectionString() => DotNetEnv.Env.GetString("AZURE_BLOB_STORAGE_CONNECTION_STRING"); + public string GetBlobStorageContainerName() => DotNetEnv.Env.GetString("AZURE_BLOB_STORAGE_CONTAINER_NAME"); + public string GetDatabaseConnectionString() => DotNetEnv.Env.GetString("POSTGRES_CONNECTION_STRING"); + public string GetSmtpUserName() => DotNetEnv.Env.GetString("SMTP_EMAIL_USERNAME"); + public string GetSmtpUserPassword() => DotNetEnv.Env.GetString("SMTP_EMAIL_PASSWORD"); + public string GetJwtSecret() => DotNetEnv.Env.GetString("JWT_SECRET_KEY"); + public string GetJwtIssuer() => DotNetEnv.Env.GetString("JWT_ISSUER"); + public string GetJwtAudience() => DotNetEnv.Env.GetString("JWT_AUDIENCE"); + public string GetJwtExpirationTime() => DotNetEnv.Env.GetString("JWT_EXPIRE_IN"); + public bool ExecuteMigration() + { + try + { + return DotNetEnv.Env.GetBool("EXECUTE_MIGRATION"); + } + catch + { + return false; + } + } } - - public string GetBlobStorageConnectionString() => DotNetEnv.Env.GetString("AZURE_BLOB_STORAGE_CONNECTION_STRING"); - public string GetBlobStorageContainerName() => DotNetEnv.Env.GetString("AZURE_BLOB_STORAGE_CONTAINER_NAME"); - - public string GetDatabaseConnectionString() => DotNetEnv.Env.GetString("AZURE_POSTGRES_CONNECTION_STRING"); - - public string GetSmtpUserName() => DotNetEnv.Env.GetString("SMTP_EMAIL_USERNAME"); - - public string GetSmtpUserPassword() => DotNetEnv.Env.GetString("SMTP_EMAIL_PASSWORD"); - - public string GetJwtSecret() => DotNetEnv.Env.GetString("JWT_SECRET_KEY"); - - public string GetJwtIssuer() => DotNetEnv.Env.GetString("JWT_ISSUER"); - - public string GetJwtAudience() => DotNetEnv.Env.GetString("JWT_AUDIENCE"); - - public string GetJwtExpirationTime() => DotNetEnv.Env.GetString("JWT_EXPIRE_IN"); } \ No newline at end of file diff --git a/src/Infrastructure/IoC/Utils/SettingsConfiguration.cs b/src/Infrastructure/IoC/Utils/SettingsConfiguration.cs index 281e4020..d5fbcd64 100644 --- a/src/Infrastructure/IoC/Utils/SettingsConfiguration.cs +++ b/src/Infrastructure/IoC/Utils/SettingsConfiguration.cs @@ -1,21 +1,38 @@ using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; -namespace Infrastructure.IoC.Utils; -public static class SettingsConfiguration +namespace Infrastructure.IoC.Utils { - public static IConfiguration GetConfiguration() + public static class SettingsConfiguration { - // Caminho base para o arquivo appsettings.json - var basePath = Path.GetDirectoryName(typeof(SettingsConfiguration).Assembly.Location); + public static IConfiguration GetConfiguration(HostBuilderContext? hostContext = null) + { + // Caminho base para o arquivo appsettings.json + string basePath = AppContext.BaseDirectory; - // Carrega informações de ambiente (.env) - DotNetEnv.Env.Load(Path.Combine(basePath!, ".env")); + // Carrega informações de ambiente (.env) + DotNetEnv.Env.Load(Path.Combine(basePath, ".env")); - // Retorna configurações - return new ConfigurationBuilder() - .SetBasePath(basePath!) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .Build(); + // Adiciona o context do host caso exista + if (hostContext != null) + { + // Retorna configurações + return new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddConfiguration(hostContext.Configuration) + .AddEnvironmentVariables() + .Build(); + } + else + { + // Retorna configurações + return new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables() + .Build(); + } + } } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Context/ApplicationDbContext.cs b/src/Infrastructure/Persistence/Context/ApplicationDbContext.cs index 65f9be81..e8f91707 100644 --- a/src/Infrastructure/Persistence/Context/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/Context/ApplicationDbContext.cs @@ -15,34 +15,25 @@ public class ApplicationDbContext : DbContext /// Using DbContextOptions for more information and examples. /// /// The options for this context. - public ApplicationDbContext(DbContextOptions options) : base(options) - { - Areas = Set(); - AssistanceScholarships = Set(); - Campuses = Set(); - Courses = Set(); - MainAreas = Set(); - Notices = Set(); - Professors = Set(); - Projects = Set(); - ProgramTypes = Set(); - TypeAssistances = Set(); - Students = Set(); - SubAreas = Set(); - Users = Set(); - } + public ApplicationDbContext(DbContextOptions options) : base(options) { } public DbSet Areas { get; set; } - public DbSet AssistanceScholarships { get; set; } + public DbSet Activities { get; set; } + public DbSet ActivityTypes { get; set; } + public DbSet AssistanceTypes { get; set; } public DbSet Campuses { get; set; } public DbSet Courses { get; set; } public DbSet MainAreas { get; set; } public DbSet Notices { get; set; } public DbSet Professors { get; set; } public DbSet Projects { get; set; } + public DbSet ProjectActivities { get; set; } + public DbSet ProjectEvaluations { get; set; } + public DbSet ProjectFinalReports { get; set; } + public DbSet ProjectPartialReports { get; set; } public DbSet ProgramTypes { get; set; } - public DbSet TypeAssistances { get; set; } public DbSet Students { get; set; } + public DbSet StudentDocuments { get; set; } public DbSet SubAreas { get; set; } public DbSet Users { get; set; } @@ -52,5 +43,7 @@ protected override void OnModelCreating(ModelBuilder builder) base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseLazyLoadingProxies(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityConfiguration.cs new file mode 100644 index 00000000..ccdbeeef --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityConfiguration.cs @@ -0,0 +1,29 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntitiesConfiguration +{ + public class ActivityConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Activities"); + + builder.HasKey(t => t.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + + builder.Property(p => p.Name).HasMaxLength(300).IsRequired(); + builder.Property(p => p.Points).IsRequired(); + builder.Property(p => p.Limits); + builder.Property(p => p.ActivityTypeId).IsRequired(); + builder.Property(p => p.DeletedAt); + + builder.HasOne(a => a.ActivityType) + .WithMany(t => t.Activities) + .HasForeignKey(a => a.ActivityTypeId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityTypeConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityTypeConfiguration.cs new file mode 100644 index 00000000..968e873d --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ActivityTypeConfiguration.cs @@ -0,0 +1,28 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntitiesConfiguration +{ + public class ActivityTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ActivityTypes"); + + builder.HasKey(t => t.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + + builder.Property(p => p.Name).HasMaxLength(300).IsRequired(); + builder.Property(p => p.Unity).HasMaxLength(300).IsRequired(); + builder.Property(p => p.NoticeId).IsRequired(); + builder.Property(p => p.DeletedAt); + + builder.HasOne(a => a.Notice) + .WithMany() + .HasForeignKey(a => a.NoticeId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/AssistanceTypeConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/AssistanceTypeConfiguration.cs new file mode 100644 index 00000000..cf810954 --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/AssistanceTypeConfiguration.cs @@ -0,0 +1,21 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.EntitiesConfiguration +{ + public class AssistanceTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("AssistanceTypes"); + builder.HasKey(t => t.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + builder.Property(p => p.Name).HasMaxLength(300).IsRequired(); + builder.Property(p => p.Description).HasMaxLength(300); + builder.Property(p => p.DeletedAt); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/EntityBuilder.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/EntityBuilder.cs new file mode 100644 index 00000000..e69de29b diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/MainAreaConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/MainAreaConfiguration.cs index d0e86b2f..49ea127f 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/MainAreaConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/MainAreaConfiguration.cs @@ -1,5 +1,4 @@ -using System; -using Domain.Entities; +using Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/NoticeConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/NoticeConfiguration.cs index cd5152ee..07632c0c 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/NoticeConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/NoticeConfiguration.cs @@ -11,14 +11,20 @@ public void Configure(EntityTypeBuilder builder) builder.ToTable("Notices"); builder.HasKey(t => t.Id); builder.Property(p => p.Id).ValueGeneratedOnAdd(); - builder.Property(p => p.StartDate).IsRequired(); - builder.Property(p => p.FinalDate).IsRequired(); + builder.Property(p => p.RegistrationStartDate).IsRequired(); + builder.Property(p => p.RegistrationEndDate).IsRequired(); + builder.Property(p => p.EvaluationStartDate).IsRequired(); + builder.Property(p => p.EvaluationEndDate).IsRequired(); builder.Property(p => p.AppealStartDate).IsRequired(); - builder.Property(p => p.AppealFinalDate).IsRequired(); + builder.Property(p => p.AppealEndDate).IsRequired(); + builder.Property(p => p.SendingDocsStartDate).IsRequired(); + builder.Property(p => p.SendingDocsEndDate).IsRequired(); + builder.Property(p => p.PartialReportDeadline).IsRequired(); + builder.Property(p => p.FinalReportDeadline).IsRequired(); builder.Property(p => p.SuspensionYears).IsRequired(); - builder.Property(p => p.SendingDocumentationDeadline).IsRequired(); - builder.Property(p => p.Description).HasMaxLength(300); builder.Property(p => p.DocUrl).HasMaxLength(300); + builder.Property(p => p.Description).HasMaxLength(300); + builder.Property(p => p.CreatedAt).HasDefaultValueSql("now() at time zone 'utc'").IsRequired(); builder.Property(p => p.DeletedAt); builder.HasQueryFilter(x => x.DeletedAt == null); diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProfessorConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProfessorConfiguration.cs index f24833bb..46983e9f 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/ProfessorConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProfessorConfiguration.cs @@ -20,6 +20,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(p => p.UserId) .IsRequired(); builder.Property(p => p.DeletedAt); + builder.Property(p => p.SuspensionEndDate); builder.HasOne(a => a.User) .WithOne() diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectActivityConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectActivityConfiguration.cs new file mode 100644 index 00000000..dfad62b3 --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectActivityConfiguration.cs @@ -0,0 +1,32 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntitiesConfiguration +{ + public class ProjectActivityConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ProjectActivities"); + + builder.HasKey(t => t.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + + builder.Property(p => p.ActivityId).IsRequired(); + builder.Property(p => p.ProjectId).IsRequired(); + builder.Property(p => p.InformedActivities).IsRequired(); + builder.Property(p => p.FoundActivities); + builder.Property(p => p.DeletedAt); + + builder.HasOne(a => a.Project) + .WithMany() + .HasForeignKey(a => a.ProjectId); + builder.HasOne(a => a.Activity) + .WithMany() + .HasForeignKey(a => a.ActivityId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectConfiguration.cs index d9657568..89b506ef 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectConfiguration.cs @@ -21,24 +21,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(p => p.Methodology).HasMaxLength(1500); builder.Property(p => p.ExpectedResults).HasMaxLength(1500); builder.Property(p => p.ActivitiesExecutionSchedule).HasMaxLength(1500); - builder.Property(p => p.WorkType1).HasDefaultValue(0); - builder.Property(p => p.WorkType2).HasDefaultValue(0); - builder.Property(p => p.IndexedConferenceProceedings).HasDefaultValue(0); - builder.Property(p => p.NotIndexedConferenceProceedings).HasDefaultValue(0); - builder.Property(p => p.CompletedBook).HasDefaultValue(0); - builder.Property(p => p.OrganizedBook).HasDefaultValue(0); - builder.Property(p => p.BookChapters).HasDefaultValue(0); - builder.Property(p => p.BookTranslations).HasDefaultValue(0); - builder.Property(p => p.ParticipationEditorialCommittees).HasDefaultValue(0); - builder.Property(p => p.FullComposerSoloOrchestraAllTracks).HasDefaultValue(0); - builder.Property(p => p.FullComposerSoloOrchestraCompilation).HasDefaultValue(0); - builder.Property(p => p.ChamberOrchestraInterpretation).HasDefaultValue(0); - builder.Property(p => p.IndividualAndCollectiveArtPerformances).HasDefaultValue(0); - builder.Property(p => p.ScientificCulturalArtisticCollectionsCuratorship).HasDefaultValue(0); - builder.Property(p => p.PatentLetter).HasDefaultValue(0); - builder.Property(p => p.PatentDeposit).HasDefaultValue(0); - builder.Property(p => p.SoftwareRegistration).HasDefaultValue(0); - builder.Property(p => p.StudentId).IsRequired(); + builder.Property(p => p.StudentId); builder.Property(p => p.ProgramTypeId).IsRequired(); builder.Property(p => p.ProfessorId).IsRequired(); builder.Property(p => p.SubAreaId).IsRequired(); @@ -47,9 +30,10 @@ public void Configure(EntityTypeBuilder builder) builder.Property(p => p.StatusDescription).IsRequired(); builder.Property(p => p.AppealObservation); builder.Property(p => p.SubmissionDate); - builder.Property(p => p.ResubmissionDate); + builder.Property(p => p.AppealDate); builder.Property(p => p.CancellationDate); builder.Property(p => p.CancellationReason); + builder.Property(p => p.CertificateUrl); builder.Property(p => p.DeletedAt); builder.HasOne(a => a.Student) diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectEvaluationConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectEvaluationConfiguration.cs new file mode 100644 index 00000000..d535f882 --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectEvaluationConfiguration.cs @@ -0,0 +1,54 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntitiesConfiguration +{ + public class ProjectEvaluationConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ProjectEvaluations"); + builder.HasKey(t => t.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + + builder.Property(p => p.IsProductivityFellow).IsRequired(); + builder.Property(p => p.SubmissionEvaluationStatus).IsRequired(); + builder.Property(p => p.SubmissionEvaluationDate).IsRequired(); + builder.Property(p => p.SubmissionEvaluationDescription).IsRequired(); + builder.Property(p => p.AppealEvaluationStatus); + builder.Property(p => p.AppealEvaluationDate); + builder.Property(p => p.AppealEvaluationDescription); + builder.Property(p => p.DocumentsEvaluationDate); + builder.Property(p => p.DocumentsEvaluationDescription); + + builder.Property(p => p.APIndex).HasDefaultValue(0); + builder.Property(p => p.FinalScore).HasDefaultValue(0); + builder.Property(p => p.Qualification).IsRequired(); + builder.Property(p => p.ProjectProposalObjectives).IsRequired(); + builder.Property(p => p.AcademicScientificProductionCoherence).IsRequired(); + builder.Property(p => p.ProposalMethodologyAdaptation).IsRequired(); + builder.Property(p => p.EffectiveContributionToResearch).IsRequired(); + + builder.Property(p => p.ProjectId).IsRequired(); + builder.Property(p => p.SubmissionEvaluatorId).IsRequired(); + builder.Property(p => p.AppealEvaluatorId); + builder.Property(p => p.DocumentsEvaluatorId); + + builder.HasOne(a => a.Project) + .WithOne() + .HasForeignKey(a => a.ProjectId); + builder.HasOne(a => a.SubmissionEvaluator) + .WithMany() + .HasForeignKey(a => a.SubmissionEvaluatorId); + builder.HasOne(a => a.AppealEvaluator) + .WithMany() + .HasForeignKey(a => a.AppealEvaluatorId); + builder.HasOne(a => a.DocumentsEvaluator) + .WithMany() + .HasForeignKey(a => a.DocumentsEvaluatorId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectFinalReportConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectFinalReportConfiguration.cs new file mode 100644 index 00000000..6abadc8a --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectFinalReportConfiguration.cs @@ -0,0 +1,36 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.EntitiesConfiguration +{ + public class ProjectFinalReportConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ProjectFinalReports"); + builder.HasKey(t => t.Id); + builder.Property(p => p.Id) + .ValueGeneratedOnAdd(); + + builder.Property(p => p.ReportUrl) + .IsRequired(); + builder.Property(p => p.SendDate) + .IsRequired(); + builder.Property(p => p.ProjectId) + .IsRequired(); + builder.Property(p => p.UserId) + .IsRequired(); + builder.Property(p => p.DeletedAt); + + builder.HasOne(a => a.Project) + .WithMany() + .HasForeignKey(a => a.ProjectId); + builder.HasOne(a => a.User) + .WithMany() + .HasForeignKey(a => a.UserId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectPartialReportConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectPartialReportConfiguration.cs new file mode 100644 index 00000000..79b9a549 --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/ProjectPartialReportConfiguration.cs @@ -0,0 +1,37 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.EntitiesConfiguration +{ + public class ProjectPartialReportConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ProjectPartialReports"); + builder.HasKey(t => t.Id); + builder.Property(p => p.Id) + .ValueGeneratedOnAdd(); + + builder.Property(p => p.AdditionalInfo); + builder.Property(p => p.CurrentDevelopmentStage) + .IsRequired(); + builder.Property(p => p.ScholarPerformance) + .IsRequired(); + builder.Property(p => p.ProjectId) + .IsRequired(); + builder.Property(p => p.UserId) + .IsRequired(); + builder.Property(p => p.DeletedAt); + + builder.HasOne(a => a.Project) + .WithMany() + .HasForeignKey(a => a.ProjectId); + builder.HasOne(a => a.User) + .WithMany() + .HasForeignKey(a => a.UserId); + + builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/StudentConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/StudentConfiguration.cs index 8f04e84a..d531bfb1 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/StudentConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/StudentConfiguration.cs @@ -12,6 +12,9 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(t => t.Id); builder.Property(p => p.Id) .ValueGeneratedOnAdd(); + builder.Property(p => p.RegistrationCode) + .IsRequired() + .HasMaxLength(20); builder.Property(p => p.BirthDate) .IsRequired(); builder.Property(p => p.RG) @@ -47,7 +50,7 @@ public void Configure(EntityTypeBuilder builder) .IsRequired(); builder.Property(p => p.StartYear) .IsRequired(); - builder.Property(p => p.TypeAssistanceId) + builder.Property(p => p.AssistanceTypeId) .IsRequired(); builder.Property(p => p.UserId) .IsRequired(); @@ -56,7 +59,7 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(a => a.User).WithOne().HasForeignKey(a => a.UserId); builder.HasOne(a => a.Campus).WithMany().HasForeignKey(a => a.CampusId); builder.HasOne(a => a.Course).WithMany().HasForeignKey(a => a.CourseId); - builder.HasOne(a => a.TypeAssistance).WithMany().HasForeignKey(a => a.TypeAssistanceId); + builder.HasOne(a => a.AssistanceType).WithMany().HasForeignKey(a => a.AssistanceTypeId); builder.HasQueryFilter(x => x.DeletedAt == null); } diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/StudentDocumentsConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/StudentDocumentsConfiguration.cs new file mode 100644 index 00000000..61526902 --- /dev/null +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/StudentDocumentsConfiguration.cs @@ -0,0 +1,33 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntitiesConfiguration +{ + public class StudentDocumentsConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + _ = builder.ToTable("StudentDocuments"); + _ = builder.HasKey(t => t.Id); + _ = builder.Property(p => p.Id).ValueGeneratedOnAdd(); + + _ = builder.Property(p => p.IdentityDocument).IsRequired(); + _ = builder.Property(p => p.CPF).IsRequired(); + _ = builder.Property(p => p.Photo3x4).IsRequired(); + _ = builder.Property(p => p.SchoolHistory).IsRequired(); + _ = builder.Property(p => p.ScholarCommitmentAgreement).IsRequired(); + _ = builder.Property(p => p.ParentalAuthorization); + _ = builder.Property(p => p.AgencyNumber).IsRequired(); + _ = builder.Property(p => p.AccountNumber).IsRequired(); + _ = builder.Property(p => p.AccountOpeningProof).IsRequired(); + _ = builder.Property(p => p.ProjectId).IsRequired(); + + _ = builder.HasOne(a => a.Project) + .WithOne() + .HasForeignKey(a => a.ProjectId); + + _ = builder.HasQueryFilter(x => x.DeletedAt == null); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/TypeAssistanceConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/TypeAssistanceConfiguration.cs deleted file mode 100644 index 03d63775..00000000 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/TypeAssistanceConfiguration.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Infrastructure.Persistence.EntitiesConfiguration -{ - public class TypeAssistanceConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("TypeAssistances"); - builder.HasKey(t => t.Id); - builder.Property(p => p.Id).ValueGeneratedOnAdd(); - builder.Property(p => p.Name).HasMaxLength(300).IsRequired(); - builder.Property(p => p.Description).HasMaxLength(300); - builder.Property(p => p.DeletedAt); - - builder.HasQueryFilter(x => x.DeletedAt == null); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/EntitiesConfiguration/UserConfiguration.cs b/src/Infrastructure/Persistence/EntitiesConfiguration/UserConfiguration.cs index c298ac76..fa320f01 100644 --- a/src/Infrastructure/Persistence/EntitiesConfiguration/UserConfiguration.cs +++ b/src/Infrastructure/Persistence/EntitiesConfiguration/UserConfiguration.cs @@ -19,6 +19,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(p => p.ValidationCode).HasMaxLength(6); builder.Property(p => p.ResetPasswordToken).HasMaxLength(6); builder.Property(p => p.IsConfirmed).IsRequired(); + builder.Property(p => p.IsCoordinator).IsRequired(); builder.Property(p => p.DeletedAt); builder.HasQueryFilter(x => x.DeletedAt == null); diff --git a/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.Designer.cs new file mode 100644 index 00000000..4a83de71 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.Designer.cs @@ -0,0 +1,1105 @@ +// +using System; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230907134628_Initialize")] + partial class Initialize + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Activity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Limits") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Points") + .IsRequired() + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("ActivityTypeId"); + + b.ToTable("Activities", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("NoticeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Unity") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.HasIndex("NoticeId"); + + b.ToTable("ActivityTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Area", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MainAreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("MainAreaId"); + + b.ToTable("Areas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.AssistanceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("AssistanceTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("Campuses", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("Courses", "public"); + }); + + modelBuilder.Entity("Domain.Entities.MainArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("MainAreas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Notice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppealEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("AppealStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("DocUrl") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("EvaluationEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("EvaluationStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("FinalReportDeadline") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("PartialReportDeadline") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("RegistrationEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("RegistrationStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SendingDocsEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SendingDocsStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SuspensionYears") + .IsRequired() + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Notices", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentifyLattes") + .HasColumnType("bigint"); + + b.Property("SIAPEEnrollment") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("SuspensionEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Professors", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProgramType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("ProgramTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivitiesExecutionSchedule") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("AppealDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AppealObservation") + .HasColumnType("text"); + + b.Property("CancellationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CancellationReason") + .HasColumnType("text"); + + b.Property("CertificateUrl") + .HasColumnType("text"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpectedResults") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("IsScholarshipCandidate") + .HasColumnType("boolean"); + + b.Property("KeyWord1") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("KeyWord2") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("KeyWord3") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Methodology") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("NoticeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Objective") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("ProfessorId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ProgramTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StatusDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("StudentId") + .HasColumnType("uuid"); + + b.Property("SubAreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("NoticeId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("ProgramTypeId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubAreaId"); + + b.ToTable("Projects", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectActivity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FoundActivities") + .HasColumnType("integer"); + + b.Property("InformedActivities") + .IsRequired() + .HasColumnType("integer"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectActivities", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectEvaluation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("APIndex") + .ValueGeneratedOnAdd() + .HasColumnType("double precision") + .HasDefaultValue(0.0); + + b.Property("AcademicScientificProductionCoherence") + .HasColumnType("integer"); + + b.Property("AppealEvaluationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AppealEvaluationDescription") + .HasColumnType("text"); + + b.Property("AppealEvaluationStatus") + .HasColumnType("integer"); + + b.Property("AppealEvaluatorId") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocumentsEvaluationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DocumentsEvaluationDescription") + .HasColumnType("text"); + + b.Property("DocumentsEvaluatorId") + .HasColumnType("uuid"); + + b.Property("EffectiveContributionToResearch") + .HasColumnType("integer"); + + b.Property("FinalScore") + .ValueGeneratedOnAdd() + .HasColumnType("double precision") + .HasDefaultValue(0.0); + + b.Property("IsProductivityFellow") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ProjectProposalObjectives") + .HasColumnType("integer"); + + b.Property("ProposalMethodologyAdaptation") + .HasColumnType("integer"); + + b.Property("Qualification") + .HasColumnType("integer"); + + b.Property("SubmissionEvaluationDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SubmissionEvaluationDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionEvaluationStatus") + .HasColumnType("integer"); + + b.Property("SubmissionEvaluatorId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppealEvaluatorId"); + + b.HasIndex("DocumentsEvaluatorId"); + + b.HasIndex("ProjectId") + .IsUnique(); + + b.HasIndex("SubmissionEvaluatorId"); + + b.ToTable("ProjectEvaluations", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectFinalReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ReportUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectFinalReports", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectPartialReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalInfo") + .HasColumnType("text"); + + b.Property("CurrentDevelopmentStage") + .HasColumnType("integer"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ScholarPerformance") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectPartialReports", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Student", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssistanceTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CEP") + .HasColumnType("bigint"); + + b.Property("CampusId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("CellPhone") + .HasColumnType("bigint"); + + b.Property("CellPhoneDDD") + .HasColumnType("integer"); + + b.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CourseId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DispatchDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gender") + .HasColumnType("integer"); + + b.Property("HomeAddress") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("IssuingAgency") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Phone") + .HasColumnType("bigint"); + + b.Property("PhoneDDD") + .HasColumnType("integer"); + + b.Property("RG") + .HasColumnType("bigint"); + + b.Property("Race") + .HasColumnType("integer"); + + b.Property("RegistrationCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartYear") + .IsRequired() + .HasColumnType("text"); + + b.Property("UF") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character(2)") + .IsFixedLength(); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssistanceTypeId"); + + b.HasIndex("CampusId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Students", "public"); + }); + + modelBuilder.Entity("Domain.Entities.StudentDocuments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("AccountOpeningProof") + .IsRequired() + .HasColumnType("text"); + + b.Property("AgencyNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CPF") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityDocument") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentalAuthorization") + .HasColumnType("text"); + + b.Property("Photo3x4") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ScholarCommitmentAgreement") + .IsRequired() + .HasColumnType("text"); + + b.Property("SchoolHistory") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique(); + + b.ToTable("StudentDocuments", "public"); + }); + + modelBuilder.Entity("Domain.Entities.SubArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("AreaId"); + + b.ToTable("SubAreas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CPF") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("IsCoordinator") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("ResetPasswordToken") + .HasMaxLength(6) + .HasColumnType("character varying(6)"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("ValidationCode") + .HasMaxLength(6) + .HasColumnType("character varying(6)"); + + b.HasKey("Id"); + + b.ToTable("Users", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Activity", b => + { + b.HasOne("Domain.Entities.ActivityType", "ActivityType") + .WithMany("Activities") + .HasForeignKey("ActivityTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ActivityType"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.HasOne("Domain.Entities.Notice", "Notice") + .WithMany() + .HasForeignKey("NoticeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notice"); + }); + + modelBuilder.Entity("Domain.Entities.Area", b => + { + b.HasOne("Domain.Entities.MainArea", "MainArea") + .WithMany() + .HasForeignKey("MainAreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MainArea"); + }); + + modelBuilder.Entity("Domain.Entities.Professor", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Professor", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Project", b => + { + b.HasOne("Domain.Entities.Notice", "Notice") + .WithMany() + .HasForeignKey("NoticeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Professor", "Professor") + .WithMany() + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.ProgramType", "ProgramType") + .WithMany() + .HasForeignKey("ProgramTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Student", "Student") + .WithMany() + .HasForeignKey("StudentId"); + + b.HasOne("Domain.Entities.SubArea", "SubArea") + .WithMany() + .HasForeignKey("SubAreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notice"); + + b.Navigation("Professor"); + + b.Navigation("ProgramType"); + + b.Navigation("Student"); + + b.Navigation("SubArea"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectActivity", b => + { + b.HasOne("Domain.Entities.Activity", "Activity") + .WithMany() + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectEvaluation", b => + { + b.HasOne("Domain.Entities.User", "AppealEvaluator") + .WithMany() + .HasForeignKey("AppealEvaluatorId"); + + b.HasOne("Domain.Entities.User", "DocumentsEvaluator") + .WithMany() + .HasForeignKey("DocumentsEvaluatorId"); + + b.HasOne("Domain.Entities.Project", "Project") + .WithOne() + .HasForeignKey("Domain.Entities.ProjectEvaluation", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "SubmissionEvaluator") + .WithMany() + .HasForeignKey("SubmissionEvaluatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppealEvaluator"); + + b.Navigation("DocumentsEvaluator"); + + b.Navigation("Project"); + + b.Navigation("SubmissionEvaluator"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectFinalReport", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectPartialReport", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Student", b => + { + b.HasOne("Domain.Entities.AssistanceType", "AssistanceType") + .WithMany() + .HasForeignKey("AssistanceTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Campus", "Campus") + .WithMany() + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Course", "Course") + .WithMany() + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Student", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssistanceType"); + + b.Navigation("Campus"); + + b.Navigation("Course"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.StudentDocuments", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithOne() + .HasForeignKey("Domain.Entities.StudentDocuments", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Domain.Entities.SubArea", b => + { + b.HasOne("Domain.Entities.Area", "Area") + .WithMany() + .HasForeignKey("AreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Area"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.Navigation("Activities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.cs b/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.cs new file mode 100644 index 00000000..647af457 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230907134628_Initialize.cs @@ -0,0 +1,805 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Migrations +{ + /// + public partial class Initialize : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "public"); + + migrationBuilder.CreateTable( + name: "AssistanceTypes", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Description = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AssistanceTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Campuses", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Campuses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Courses", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Courses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MainAreas", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MainAreas", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Notices", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RegistrationStartDate = table.Column(type: "timestamp with time zone", nullable: false), + RegistrationEndDate = table.Column(type: "timestamp with time zone", nullable: false), + EvaluationStartDate = table.Column(type: "timestamp with time zone", nullable: false), + EvaluationEndDate = table.Column(type: "timestamp with time zone", nullable: false), + AppealStartDate = table.Column(type: "timestamp with time zone", nullable: false), + AppealEndDate = table.Column(type: "timestamp with time zone", nullable: false), + SendingDocsStartDate = table.Column(type: "timestamp with time zone", nullable: false), + SendingDocsEndDate = table.Column(type: "timestamp with time zone", nullable: false), + PartialReportDeadline = table.Column(type: "timestamp with time zone", nullable: false), + FinalReportDeadline = table.Column(type: "timestamp with time zone", nullable: false), + SuspensionYears = table.Column(type: "integer", nullable: false), + DocUrl = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + Description = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Notices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProgramTypes", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Description = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProgramTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Email = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Password = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + CPF = table.Column(type: "character varying(15)", maxLength: 15, nullable: false), + Role = table.Column(type: "integer", nullable: false), + IsConfirmed = table.Column(type: "boolean", nullable: false), + ValidationCode = table.Column(type: "character varying(6)", maxLength: 6, nullable: true), + ResetPasswordToken = table.Column(type: "character varying(6)", maxLength: 6, nullable: true), + IsCoordinator = table.Column(type: "boolean", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Areas", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + MainAreaId = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Areas", x => x.Id); + table.ForeignKey( + name: "FK_Areas_MainAreas_MainAreaId", + column: x => x.MainAreaId, + principalSchema: "public", + principalTable: "MainAreas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ActivityTypes", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Unity = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + NoticeId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ActivityTypes", x => x.Id); + table.ForeignKey( + name: "FK_ActivityTypes_Notices_NoticeId", + column: x => x.NoticeId, + principalSchema: "public", + principalTable: "Notices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Professors", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + SIAPEEnrollment = table.Column(type: "character varying(7)", maxLength: 7, nullable: false), + IdentifyLattes = table.Column(type: "bigint", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + SuspensionEndDate = table.Column(type: "timestamp with time zone", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Professors", x => x.Id); + table.ForeignKey( + name: "FK_Professors_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Students", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RegistrationCode = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + BirthDate = table.Column(type: "timestamp with time zone", nullable: false), + RG = table.Column(type: "bigint", nullable: false), + IssuingAgency = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DispatchDate = table.Column(type: "timestamp with time zone", nullable: false), + Gender = table.Column(type: "integer", nullable: false), + Race = table.Column(type: "integer", nullable: false), + HomeAddress = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + City = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + UF = table.Column(type: "character(2)", fixedLength: true, maxLength: 2, nullable: false), + CEP = table.Column(type: "bigint", nullable: false), + PhoneDDD = table.Column(type: "integer", nullable: true), + Phone = table.Column(type: "bigint", nullable: true), + CellPhoneDDD = table.Column(type: "integer", nullable: true), + CellPhone = table.Column(type: "bigint", nullable: true), + CampusId = table.Column(type: "uuid", nullable: false), + CourseId = table.Column(type: "uuid", nullable: false), + StartYear = table.Column(type: "text", nullable: false), + AssistanceTypeId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Students", x => x.Id); + table.ForeignKey( + name: "FK_Students_AssistanceTypes_AssistanceTypeId", + column: x => x.AssistanceTypeId, + principalSchema: "public", + principalTable: "AssistanceTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Students_Campuses_CampusId", + column: x => x.CampusId, + principalSchema: "public", + principalTable: "Campuses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Students_Courses_CourseId", + column: x => x.CourseId, + principalSchema: "public", + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Students_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SubAreas", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + AreaId = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SubAreas", x => x.Id); + table.ForeignKey( + name: "FK_SubAreas_Areas_AreaId", + column: x => x.AreaId, + principalSchema: "public", + principalTable: "Areas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Activities", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + Points = table.Column(type: "double precision", nullable: false), + Limits = table.Column(type: "double precision", nullable: true), + ActivityTypeId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Activities", x => x.Id); + table.ForeignKey( + name: "FK_Activities_ActivityTypes_ActivityTypeId", + column: x => x.ActivityTypeId, + principalSchema: "public", + principalTable: "ActivityTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Projects", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: true), + KeyWord1 = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + KeyWord2 = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + KeyWord3 = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + IsScholarshipCandidate = table.Column(type: "boolean", nullable: false), + Objective = table.Column(type: "character varying(1500)", maxLength: 1500, nullable: true), + Methodology = table.Column(type: "character varying(1500)", maxLength: 1500, nullable: true), + ExpectedResults = table.Column(type: "character varying(1500)", maxLength: 1500, nullable: true), + ActivitiesExecutionSchedule = table.Column(type: "character varying(1500)", maxLength: 1500, nullable: true), + ProgramTypeId = table.Column(type: "uuid", nullable: false), + ProfessorId = table.Column(type: "uuid", nullable: false), + SubAreaId = table.Column(type: "uuid", nullable: false), + NoticeId = table.Column(type: "uuid", nullable: false), + StudentId = table.Column(type: "uuid", nullable: true), + Status = table.Column(type: "integer", nullable: false), + StatusDescription = table.Column(type: "text", nullable: false), + AppealObservation = table.Column(type: "text", nullable: true), + SubmissionDate = table.Column(type: "timestamp with time zone", nullable: true), + AppealDate = table.Column(type: "timestamp with time zone", nullable: true), + CancellationDate = table.Column(type: "timestamp with time zone", nullable: true), + CancellationReason = table.Column(type: "text", nullable: true), + CertificateUrl = table.Column(type: "text", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Projects", x => x.Id); + table.ForeignKey( + name: "FK_Projects_Notices_NoticeId", + column: x => x.NoticeId, + principalSchema: "public", + principalTable: "Notices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Projects_Professors_ProfessorId", + column: x => x.ProfessorId, + principalSchema: "public", + principalTable: "Professors", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Projects_ProgramTypes_ProgramTypeId", + column: x => x.ProgramTypeId, + principalSchema: "public", + principalTable: "ProgramTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Projects_Students_StudentId", + column: x => x.StudentId, + principalSchema: "public", + principalTable: "Students", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Projects_SubAreas_SubAreaId", + column: x => x.SubAreaId, + principalSchema: "public", + principalTable: "SubAreas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProjectActivities", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + InformedActivities = table.Column(type: "integer", nullable: false), + FoundActivities = table.Column(type: "integer", nullable: true), + ProjectId = table.Column(type: "uuid", nullable: false), + ActivityId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectActivities", x => x.Id); + table.ForeignKey( + name: "FK_ProjectActivities_Activities_ActivityId", + column: x => x.ActivityId, + principalSchema: "public", + principalTable: "Activities", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectActivities_Projects_ProjectId", + column: x => x.ProjectId, + principalSchema: "public", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProjectEvaluations", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProjectId = table.Column(type: "uuid", nullable: false), + IsProductivityFellow = table.Column(type: "boolean", nullable: false), + SubmissionEvaluatorId = table.Column(type: "uuid", nullable: false), + SubmissionEvaluationDate = table.Column(type: "timestamp with time zone", nullable: false), + SubmissionEvaluationStatus = table.Column(type: "integer", nullable: false), + SubmissionEvaluationDescription = table.Column(type: "text", nullable: false), + AppealEvaluatorId = table.Column(type: "uuid", nullable: true), + AppealEvaluationDate = table.Column(type: "timestamp with time zone", nullable: true), + AppealEvaluationStatus = table.Column(type: "integer", nullable: true), + AppealEvaluationDescription = table.Column(type: "text", nullable: true), + DocumentsEvaluatorId = table.Column(type: "uuid", nullable: true), + DocumentsEvaluationDate = table.Column(type: "timestamp with time zone", nullable: true), + DocumentsEvaluationDescription = table.Column(type: "text", nullable: true), + APIndex = table.Column(type: "double precision", nullable: false, defaultValue: 0.0), + FinalScore = table.Column(type: "double precision", nullable: false, defaultValue: 0.0), + Qualification = table.Column(type: "integer", nullable: false), + ProjectProposalObjectives = table.Column(type: "integer", nullable: false), + AcademicScientificProductionCoherence = table.Column(type: "integer", nullable: false), + ProposalMethodologyAdaptation = table.Column(type: "integer", nullable: false), + EffectiveContributionToResearch = table.Column(type: "integer", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectEvaluations", x => x.Id); + table.ForeignKey( + name: "FK_ProjectEvaluations_Projects_ProjectId", + column: x => x.ProjectId, + principalSchema: "public", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectEvaluations_Users_AppealEvaluatorId", + column: x => x.AppealEvaluatorId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_ProjectEvaluations_Users_DocumentsEvaluatorId", + column: x => x.DocumentsEvaluatorId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_ProjectEvaluations_Users_SubmissionEvaluatorId", + column: x => x.SubmissionEvaluatorId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProjectFinalReports", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ReportUrl = table.Column(type: "text", nullable: false), + SendDate = table.Column(type: "timestamp with time zone", nullable: false), + ProjectId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectFinalReports", x => x.Id); + table.ForeignKey( + name: "FK_ProjectFinalReports_Projects_ProjectId", + column: x => x.ProjectId, + principalSchema: "public", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectFinalReports_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ProjectPartialReports", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CurrentDevelopmentStage = table.Column(type: "integer", nullable: false), + ScholarPerformance = table.Column(type: "integer", nullable: false), + AdditionalInfo = table.Column(type: "text", nullable: true), + ProjectId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectPartialReports", x => x.Id); + table.ForeignKey( + name: "FK_ProjectPartialReports_Projects_ProjectId", + column: x => x.ProjectId, + principalSchema: "public", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectPartialReports_Users_UserId", + column: x => x.UserId, + principalSchema: "public", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "StudentDocuments", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProjectId = table.Column(type: "uuid", nullable: false), + IdentityDocument = table.Column(type: "text", nullable: false), + CPF = table.Column(type: "text", nullable: false), + Photo3x4 = table.Column(type: "text", nullable: false), + SchoolHistory = table.Column(type: "text", nullable: false), + ScholarCommitmentAgreement = table.Column(type: "text", nullable: false), + ParentalAuthorization = table.Column(type: "text", nullable: true), + AgencyNumber = table.Column(type: "text", nullable: false), + AccountNumber = table.Column(type: "text", nullable: false), + AccountOpeningProof = table.Column(type: "text", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StudentDocuments", x => x.Id); + table.ForeignKey( + name: "FK_StudentDocuments_Projects_ProjectId", + column: x => x.ProjectId, + principalSchema: "public", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Activities_ActivityTypeId", + schema: "public", + table: "Activities", + column: "ActivityTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_ActivityTypes_NoticeId", + schema: "public", + table: "ActivityTypes", + column: "NoticeId"); + + migrationBuilder.CreateIndex( + name: "IX_Areas_MainAreaId", + schema: "public", + table: "Areas", + column: "MainAreaId"); + + migrationBuilder.CreateIndex( + name: "IX_Professors_UserId", + schema: "public", + table: "Professors", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ProjectActivities_ActivityId", + schema: "public", + table: "ProjectActivities", + column: "ActivityId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectActivities_ProjectId", + schema: "public", + table: "ProjectActivities", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectEvaluations_AppealEvaluatorId", + schema: "public", + table: "ProjectEvaluations", + column: "AppealEvaluatorId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectEvaluations_DocumentsEvaluatorId", + schema: "public", + table: "ProjectEvaluations", + column: "DocumentsEvaluatorId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectEvaluations_ProjectId", + schema: "public", + table: "ProjectEvaluations", + column: "ProjectId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ProjectEvaluations_SubmissionEvaluatorId", + schema: "public", + table: "ProjectEvaluations", + column: "SubmissionEvaluatorId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectFinalReports_ProjectId", + schema: "public", + table: "ProjectFinalReports", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectFinalReports_UserId", + schema: "public", + table: "ProjectFinalReports", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectPartialReports_ProjectId", + schema: "public", + table: "ProjectPartialReports", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectPartialReports_UserId", + schema: "public", + table: "ProjectPartialReports", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_NoticeId", + schema: "public", + table: "Projects", + column: "NoticeId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_ProfessorId", + schema: "public", + table: "Projects", + column: "ProfessorId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_ProgramTypeId", + schema: "public", + table: "Projects", + column: "ProgramTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_StudentId", + schema: "public", + table: "Projects", + column: "StudentId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_SubAreaId", + schema: "public", + table: "Projects", + column: "SubAreaId"); + + migrationBuilder.CreateIndex( + name: "IX_StudentDocuments_ProjectId", + schema: "public", + table: "StudentDocuments", + column: "ProjectId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Students_AssistanceTypeId", + schema: "public", + table: "Students", + column: "AssistanceTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_Students_CampusId", + schema: "public", + table: "Students", + column: "CampusId"); + + migrationBuilder.CreateIndex( + name: "IX_Students_CourseId", + schema: "public", + table: "Students", + column: "CourseId"); + + migrationBuilder.CreateIndex( + name: "IX_Students_UserId", + schema: "public", + table: "Students", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SubAreas_AreaId", + schema: "public", + table: "SubAreas", + column: "AreaId"); + + Persistence.Seeds.Seeder.Execute(migrationBuilder); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ProjectActivities", + schema: "public"); + + migrationBuilder.DropTable( + name: "ProjectEvaluations", + schema: "public"); + + migrationBuilder.DropTable( + name: "ProjectFinalReports", + schema: "public"); + + migrationBuilder.DropTable( + name: "ProjectPartialReports", + schema: "public"); + + migrationBuilder.DropTable( + name: "StudentDocuments", + schema: "public"); + + migrationBuilder.DropTable( + name: "Activities", + schema: "public"); + + migrationBuilder.DropTable( + name: "Projects", + schema: "public"); + + migrationBuilder.DropTable( + name: "ActivityTypes", + schema: "public"); + + migrationBuilder.DropTable( + name: "Professors", + schema: "public"); + + migrationBuilder.DropTable( + name: "ProgramTypes", + schema: "public"); + + migrationBuilder.DropTable( + name: "Students", + schema: "public"); + + migrationBuilder.DropTable( + name: "SubAreas", + schema: "public"); + + migrationBuilder.DropTable( + name: "Notices", + schema: "public"); + + migrationBuilder.DropTable( + name: "AssistanceTypes", + schema: "public"); + + migrationBuilder.DropTable( + name: "Campuses", + schema: "public"); + + migrationBuilder.DropTable( + name: "Courses", + schema: "public"); + + migrationBuilder.DropTable( + name: "Users", + schema: "public"); + + migrationBuilder.DropTable( + name: "Areas", + schema: "public"); + + migrationBuilder.DropTable( + name: "MainAreas", + schema: "public"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 00000000..166ad00b --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,1102 @@ +// +using System; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Activity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Limits") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Points") + .IsRequired() + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("ActivityTypeId"); + + b.ToTable("Activities", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("NoticeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Unity") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.HasIndex("NoticeId"); + + b.ToTable("ActivityTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Area", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MainAreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("MainAreaId"); + + b.ToTable("Areas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.AssistanceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("AssistanceTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("Campuses", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("Courses", "public"); + }); + + modelBuilder.Entity("Domain.Entities.MainArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("MainAreas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Notice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppealEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("AppealStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("DocUrl") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("EvaluationEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("EvaluationStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("FinalReportDeadline") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("PartialReportDeadline") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("RegistrationEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("RegistrationStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SendingDocsEndDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SendingDocsStartDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SuspensionYears") + .IsRequired() + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Notices", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Professor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentifyLattes") + .HasColumnType("bigint"); + + b.Property("SIAPEEnrollment") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("SuspensionEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Professors", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProgramType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("ProgramTypes", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivitiesExecutionSchedule") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("AppealDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AppealObservation") + .HasColumnType("text"); + + b.Property("CancellationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CancellationReason") + .HasColumnType("text"); + + b.Property("CertificateUrl") + .HasColumnType("text"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpectedResults") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("IsScholarshipCandidate") + .HasColumnType("boolean"); + + b.Property("KeyWord1") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("KeyWord2") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("KeyWord3") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Methodology") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("NoticeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Objective") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)"); + + b.Property("ProfessorId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ProgramTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StatusDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("StudentId") + .HasColumnType("uuid"); + + b.Property("SubAreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("SubmissionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("NoticeId"); + + b.HasIndex("ProfessorId"); + + b.HasIndex("ProgramTypeId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubAreaId"); + + b.ToTable("Projects", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectActivity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FoundActivities") + .HasColumnType("integer"); + + b.Property("InformedActivities") + .IsRequired() + .HasColumnType("integer"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectActivities", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectEvaluation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("APIndex") + .ValueGeneratedOnAdd() + .HasColumnType("double precision") + .HasDefaultValue(0.0); + + b.Property("AcademicScientificProductionCoherence") + .HasColumnType("integer"); + + b.Property("AppealEvaluationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("AppealEvaluationDescription") + .HasColumnType("text"); + + b.Property("AppealEvaluationStatus") + .HasColumnType("integer"); + + b.Property("AppealEvaluatorId") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DocumentsEvaluationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DocumentsEvaluationDescription") + .HasColumnType("text"); + + b.Property("DocumentsEvaluatorId") + .HasColumnType("uuid"); + + b.Property("EffectiveContributionToResearch") + .HasColumnType("integer"); + + b.Property("FinalScore") + .ValueGeneratedOnAdd() + .HasColumnType("double precision") + .HasDefaultValue(0.0); + + b.Property("IsProductivityFellow") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ProjectProposalObjectives") + .HasColumnType("integer"); + + b.Property("ProposalMethodologyAdaptation") + .HasColumnType("integer"); + + b.Property("Qualification") + .HasColumnType("integer"); + + b.Property("SubmissionEvaluationDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("SubmissionEvaluationDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmissionEvaluationStatus") + .HasColumnType("integer"); + + b.Property("SubmissionEvaluatorId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AppealEvaluatorId"); + + b.HasIndex("DocumentsEvaluatorId"); + + b.HasIndex("ProjectId") + .IsUnique(); + + b.HasIndex("SubmissionEvaluatorId"); + + b.ToTable("ProjectEvaluations", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectFinalReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ReportUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendDate") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectFinalReports", "public"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectPartialReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalInfo") + .HasColumnType("text"); + + b.Property("CurrentDevelopmentStage") + .HasColumnType("integer"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ScholarPerformance") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectPartialReports", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Student", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssistanceTypeId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CEP") + .HasColumnType("bigint"); + + b.Property("CampusId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("CellPhone") + .HasColumnType("bigint"); + + b.Property("CellPhoneDDD") + .HasColumnType("integer"); + + b.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CourseId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DispatchDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gender") + .HasColumnType("integer"); + + b.Property("HomeAddress") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("IssuingAgency") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Phone") + .HasColumnType("bigint"); + + b.Property("PhoneDDD") + .HasColumnType("integer"); + + b.Property("RG") + .HasColumnType("bigint"); + + b.Property("Race") + .HasColumnType("integer"); + + b.Property("RegistrationCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("StartYear") + .IsRequired() + .HasColumnType("text"); + + b.Property("UF") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character(2)") + .IsFixedLength(); + + b.Property("UserId") + .IsRequired() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssistanceTypeId"); + + b.HasIndex("CampusId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Students", "public"); + }); + + modelBuilder.Entity("Domain.Entities.StudentDocuments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("AccountOpeningProof") + .IsRequired() + .HasColumnType("text"); + + b.Property("AgencyNumber") + .IsRequired() + .HasColumnType("text"); + + b.Property("CPF") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IdentityDocument") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentalAuthorization") + .HasColumnType("text"); + + b.Property("Photo3x4") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("ScholarCommitmentAgreement") + .IsRequired() + .HasColumnType("text"); + + b.Property("SchoolHistory") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique(); + + b.ToTable("StudentDocuments", "public"); + }); + + modelBuilder.Entity("Domain.Entities.SubArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaId") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("AreaId"); + + b.ToTable("SubAreas", "public"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CPF") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("IsConfirmed") + .HasColumnType("boolean"); + + b.Property("IsCoordinator") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("ResetPasswordToken") + .HasMaxLength(6) + .HasColumnType("character varying(6)"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("ValidationCode") + .HasMaxLength(6) + .HasColumnType("character varying(6)"); + + b.HasKey("Id"); + + b.ToTable("Users", "public"); + }); + + modelBuilder.Entity("Domain.Entities.Activity", b => + { + b.HasOne("Domain.Entities.ActivityType", "ActivityType") + .WithMany("Activities") + .HasForeignKey("ActivityTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ActivityType"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.HasOne("Domain.Entities.Notice", "Notice") + .WithMany() + .HasForeignKey("NoticeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notice"); + }); + + modelBuilder.Entity("Domain.Entities.Area", b => + { + b.HasOne("Domain.Entities.MainArea", "MainArea") + .WithMany() + .HasForeignKey("MainAreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MainArea"); + }); + + modelBuilder.Entity("Domain.Entities.Professor", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Professor", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Project", b => + { + b.HasOne("Domain.Entities.Notice", "Notice") + .WithMany() + .HasForeignKey("NoticeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Professor", "Professor") + .WithMany() + .HasForeignKey("ProfessorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.ProgramType", "ProgramType") + .WithMany() + .HasForeignKey("ProgramTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Student", "Student") + .WithMany() + .HasForeignKey("StudentId"); + + b.HasOne("Domain.Entities.SubArea", "SubArea") + .WithMany() + .HasForeignKey("SubAreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notice"); + + b.Navigation("Professor"); + + b.Navigation("ProgramType"); + + b.Navigation("Student"); + + b.Navigation("SubArea"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectActivity", b => + { + b.HasOne("Domain.Entities.Activity", "Activity") + .WithMany() + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectEvaluation", b => + { + b.HasOne("Domain.Entities.User", "AppealEvaluator") + .WithMany() + .HasForeignKey("AppealEvaluatorId"); + + b.HasOne("Domain.Entities.User", "DocumentsEvaluator") + .WithMany() + .HasForeignKey("DocumentsEvaluatorId"); + + b.HasOne("Domain.Entities.Project", "Project") + .WithOne() + .HasForeignKey("Domain.Entities.ProjectEvaluation", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "SubmissionEvaluator") + .WithMany() + .HasForeignKey("SubmissionEvaluatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppealEvaluator"); + + b.Navigation("DocumentsEvaluator"); + + b.Navigation("Project"); + + b.Navigation("SubmissionEvaluator"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectFinalReport", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ProjectPartialReport", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Student", b => + { + b.HasOne("Domain.Entities.AssistanceType", "AssistanceType") + .WithMany() + .HasForeignKey("AssistanceTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Campus", "Campus") + .WithMany() + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Course", "Course") + .WithMany() + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Student", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssistanceType"); + + b.Navigation("Campus"); + + b.Navigation("Course"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.StudentDocuments", b => + { + b.HasOne("Domain.Entities.Project", "Project") + .WithOne() + .HasForeignKey("Domain.Entities.StudentDocuments", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Domain.Entities.SubArea", b => + { + b.HasOne("Domain.Entities.Area", "Area") + .WithMany() + .HasForeignKey("AreaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Area"); + }); + + modelBuilder.Entity("Domain.Entities.ActivityType", b => + { + b.Navigation("Activities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Persistence.csproj b/src/Infrastructure/Persistence/Persistence.csproj index 8a763b61..3defe0fc 100644 --- a/src/Infrastructure/Persistence/Persistence.csproj +++ b/src/Infrastructure/Persistence/Persistence.csproj @@ -4,7 +4,7 @@ net7.0 enable enable - 0.0.1 + 0.1.0 @@ -29,19 +29,22 @@ - + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive none - - + + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -52,6 +55,12 @@ + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Infrastructure/Persistence/README.md b/src/Infrastructure/Persistence/README.md new file mode 100644 index 00000000..f4461473 --- /dev/null +++ b/src/Infrastructure/Persistence/README.md @@ -0,0 +1,45 @@ +# Infraestructure.Persistence + +## DataBase - PostgreSQL + +Para levantar o banco de dados é necessário executar o comando abaixo na pasta raiz: + +```bash +cd docker +docker compose up -d +``` + +Em seguida, é preciso acessar o pgAdmin através da rota abaixo: + +- [PGAdmin](http://localhost:16543/browser) + +E criar um servidor utilizando as informações de _host_, _username_, _password_ e _database_ que estão informadas no arquivo docker-compose.yaml utilizado. +Exemplo: + +- **host**: copet-system-db +- **username**: copet-admin +- **password**: Copet@123 +- **database**: COPET_DB +- **port**: 5432 + +## Migrations + +Criando as Migrations para criação e atualização das tabelas do banco de dados: + +```bash +cd src/Infrastructure/WebAPI +dotnet ef migrations add Initialize --project ../Persistence/Persistence.csproj +``` + +Executando as Migrations: + +```bash +dotnet ef database update +``` + +Removendo as Migrations: + +```bash +cd src/Infrastructure/WebAPI +dotnet ef migrations remove --project ../Persistence/Persistence.csproj +``` diff --git a/src/Infrastructure/Persistence/Repositories/ActivityRepository.cs b/src/Infrastructure/Persistence/Repositories/ActivityRepository.cs new file mode 100644 index 00000000..f03fbde3 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ActivityRepository.cs @@ -0,0 +1,56 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ActivityRepository : IActivityRepository + { + private readonly ApplicationDbContext _context; + public ActivityRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(Activity model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Activities + .OrderBy(x => x.Name) + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.Activities + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task DeleteAsync(Guid? id) + { + Activity model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task UpdateAsync(Activity model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ActivityTypeRepository.cs b/src/Infrastructure/Persistence/Repositories/ActivityTypeRepository.cs new file mode 100644 index 00000000..4646b031 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ActivityTypeRepository.cs @@ -0,0 +1,91 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ActivityTypeRepository : IActivityTypeRepository + { + #region Global Scope + private readonly ApplicationDbContext _context; + public ActivityTypeRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope + + public async Task CreateAsync(ActivityType model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task DeleteAsync(Guid? id) + { + ActivityType model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.ActivityTypes + .Include(x => x.Notice) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id) + ?? throw new Exception($"Nenhum tipo de atividade encontrado para o id {id}"); + } + + public async Task> GetByNoticeIdAsync(Guid? noticeId) + { + List activityTypes = await _context.ActivityTypes + .Include(x => x.Activities) + .Where(x => x.NoticeId == noticeId) + .ToListAsync() + ?? throw new Exception("Nenhum tipo de atividade encontrado."); + + // Force loading of the Activities collection for each ActivityType + foreach (ActivityType? activityType in activityTypes) + { + // Explicitly load the Activities collection + await _context.Entry(activityType) + .Collection(x => x.Activities!) + .LoadAsync(); + } + + return activityTypes; + } + + public async Task> GetLastNoticeActivitiesAsync() + { + Guid lastNoticeId = await _context.Notices + .AsAsyncEnumerable() + .OrderByDescending(x => x.CreatedAt) + .Select(x => x.Id) + .FirstOrDefaultAsync() + ?? throw new Exception("Nenhum Edital encontrado."); + return await GetByNoticeIdAsync(lastNoticeId); + } + + public async Task UpdateAsync(ActivityType model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.ActivityTypes + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .OrderBy(x => x.Name) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/AreaRepository.cs b/src/Infrastructure/Persistence/Repositories/AreaRepository.cs index b14fa99d..379e7ef1 100644 --- a/src/Infrastructure/Persistence/Repositories/AreaRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/AreaRepository.cs @@ -3,38 +3,47 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class AreaRepository : IAreaRepository { #region Global Scope private readonly ApplicationDbContext _context; - public AreaRepository(ApplicationDbContext context) => _context = context; - #endregion + public AreaRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Area model) + public async Task CreateAsync(Area model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetByCode(string? code) => await _context.Areas + public async Task GetByCodeAsync(string? code) + { + return await _context.Areas .Where(x => x.Code == code) .ToAsyncEnumerable() .FirstOrDefaultAsync(); + } - public async Task> GetAreasByMainArea(Guid? mainAreaId, int skip, int take) => await _context.Areas + public async Task> GetAreasByMainAreaAsync(Guid? mainAreaId, int skip, int take) + { + return await _context.Areas .Where(x => x.MainAreaId == mainAreaId) + .OrderBy(x => x.Name) .Skip(skip) .Take(take) .Include(x => x.MainArea) .AsAsyncEnumerable() - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) + public async Task GetByIdAsync(Guid? id) { return await _context.Areas .Include(x => x.MainArea) @@ -44,25 +53,25 @@ public async Task> GetAreasByMainArea(Guid? mainAreaId, int sk ?? throw new Exception($"Nenhuma Área encontrada para o id {id}"); } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Area model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Area model) + public async Task UpdateAsync(Area model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public Task> GetAll(int skip, int take) + public Task> GetAllAsync(int skip, int take) { throw new NotImplementedException(); } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/AssistanceTypeRepository.cs b/src/Infrastructure/Persistence/Repositories/AssistanceTypeRepository.cs new file mode 100644 index 00000000..456280a7 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/AssistanceTypeRepository.cs @@ -0,0 +1,70 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class AssistanceTypeRepository : IAssistanceTypeRepository + { + #region Global Scope + private readonly ApplicationDbContext _context; + public AssistanceTypeRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope + + #region Public Methods + public async Task CreateAsync(AssistanceType model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.AssistanceTypes + .OrderBy(x => x.Name) + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.AssistanceTypes + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task DeleteAsync(Guid? id) + { + AssistanceType model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task UpdateAsync(AssistanceType model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task GetAssistanceTypeByNameAsync(string name) + { + string loweredName = name.ToLower(System.Globalization.CultureInfo.CurrentCulture); + List entities = await _context.AssistanceTypes + .Where(x => x.Name!.ToLower(System.Globalization.CultureInfo.CurrentCulture) == loweredName) + .AsAsyncEnumerable() + .ToListAsync(); + return entities.FirstOrDefault(); + } + #endregion Public Methods + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/CampusRepository.cs b/src/Infrastructure/Persistence/Repositories/CampusRepository.cs index b06ba577..7299bc80 100644 --- a/src/Infrastructure/Persistence/Repositories/CampusRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/CampusRepository.cs @@ -4,60 +4,68 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class CampusRepository : ICampusRepository { #region Global Scope private readonly ApplicationDbContext _context; - public CampusRepository(ApplicationDbContext context) => _context = context; - #endregion + public CampusRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Campus model) + public async Task CreateAsync(Campus model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.Campuses + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Campuses + .OrderBy(x => x.Name) .Skip(skip) .Take(take) .AsAsyncEnumerable() - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.Campuses + public async Task GetByIdAsync(Guid? id) + { + return await _context.Campuses .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Campus model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Campus model) + public async Task UpdateAsync(Campus model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetCampusByName(string name) + public async Task GetCampusByNameAsync(string name) { - string loweredName = name.ToLower(); - var entities = await _context.Campuses - .Where(x => x.Name!.ToLower() == loweredName) + string loweredName = name.ToLower(System.Globalization.CultureInfo.CurrentCulture); + List entities = await _context.Campuses + .Where(x => x.Name!.ToLower(System.Globalization.CultureInfo.CurrentCulture) == loweredName) .AsAsyncEnumerable() .ToListAsync(); return entities.FirstOrDefault(); } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/CourseRepository.cs b/src/Infrastructure/Persistence/Repositories/CourseRepository.cs index c37b87f4..5d4243f2 100644 --- a/src/Infrastructure/Persistence/Repositories/CourseRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/CourseRepository.cs @@ -1,67 +1,71 @@ -using System; -using System.Collections.Generic; -using System.Data.Entity; -using System.Linq; -using System.Threading.Tasks; +using System.Data.Entity; using Domain.Entities; using Domain.Interfaces.Repositories; using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class CourseRepository : ICourseRepository { #region Global Scope private readonly ApplicationDbContext _context; - public CourseRepository(ApplicationDbContext context) => _context = context; - #endregion + public CourseRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Course model) + public async Task CreateAsync(Course model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.Courses + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Courses + .OrderBy(x => x.Name) .Skip(skip) .Take(take) .AsAsyncEnumerable() - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.Courses + public async Task GetByIdAsync(Guid? id) + { + return await _context.Courses .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Course model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Course model) + public async Task UpdateAsync(Course model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetCourseByName(string name) + public async Task GetCourseByNameAsync(string name) { - string loweredName = name.ToLower(); - var entities = await _context.Courses - .Where(x => x.Name!.ToLower() == loweredName) + string loweredName = name.ToLower(System.Globalization.CultureInfo.CurrentCulture); + List entities = await _context.Courses + .Where(x => x.Name!.ToLower(System.Globalization.CultureInfo.CurrentCulture) == loweredName) .AsAsyncEnumerable() .ToListAsync(); return entities.FirstOrDefault(); } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/MainAreaRepository.cs b/src/Infrastructure/Persistence/Repositories/MainAreaRepository.cs index 6dadf2a3..22318d56 100644 --- a/src/Infrastructure/Persistence/Repositories/MainAreaRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/MainAreaRepository.cs @@ -4,55 +4,66 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class MainAreaRepository : IMainAreaRepository { #region Global Scope private readonly ApplicationDbContext _context; - public MainAreaRepository(ApplicationDbContext context) => _context = context; - #endregion + public MainAreaRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(MainArea model) + public async Task CreateAsync(MainArea model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetByCode(string? code) => await _context.MainAreas + public async Task GetByCodeAsync(string? code) + { + return await _context.MainAreas .Where(x => x.Code == code) .ToAsyncEnumerable() .FirstOrDefaultAsync(); + } - public async Task> GetAll(int skip, int take) => await _context.MainAreas + public async Task> GetAllAsync(int skip, int take) + { + return await _context.MainAreas + .OrderBy(x => x.Name) .Skip(skip) .Take(take) .AsAsyncEnumerable() - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.MainAreas + public async Task GetByIdAsync(Guid? id) + { + return await _context.MainAreas .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + MainArea model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(MainArea model) + public async Task UpdateAsync(MainArea model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/NoticeRepository.cs b/src/Infrastructure/Persistence/Repositories/NoticeRepository.cs index 359f4155..5c8508ee 100644 --- a/src/Infrastructure/Persistence/Repositories/NoticeRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/NoticeRepository.cs @@ -4,64 +4,81 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class NoticeRepository : INoticeRepository { #region Global Scope private readonly ApplicationDbContext _context; - public NoticeRepository(ApplicationDbContext context) => _context = context; - #endregion + public NoticeRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Notice model) + public async Task CreateAsync(Notice model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.Notices + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Notices + .OrderByDescending(x => x.RegistrationStartDate) .Skip(skip) .Take(take) .AsAsyncEnumerable() - .OrderByDescending(x => x.StartDate) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.Notices + public async Task GetByIdAsync(Guid? id) + { + return await _context.Notices .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Notice model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Notice model) + public async Task UpdateAsync(Notice model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetNoticeByPeriod(DateTime start, DateTime end) + public async Task GetNoticeByPeriodAsync(DateTime start, DateTime end) { - var startDate = start.ToUniversalTime(); - var finalDate = end.ToUniversalTime(); + DateTime startDate = start.ToUniversalTime(); + DateTime finalDate = end.ToUniversalTime(); - var entities = await _context.Notices - .Where(x => (x.StartDate <= startDate && x.FinalDate >= finalDate) - || (x.StartDate <= finalDate && x.FinalDate >= finalDate) - || (x.StartDate <= startDate && x.FinalDate >= startDate)) + List entities = await _context.Notices + .Where(x => (x.RegistrationStartDate <= startDate && x.RegistrationEndDate >= finalDate) + || (x.RegistrationStartDate <= finalDate && x.RegistrationEndDate >= finalDate) + || (x.RegistrationStartDate <= startDate && x.RegistrationEndDate >= startDate)) .AsAsyncEnumerable() .ToListAsync(); return entities.FirstOrDefault(); } - #endregion + + public async Task GetNoticeEndingAsync() + { + return await _context.Notices + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => + x.FinalReportDeadline.HasValue && + x.FinalReportDeadline.Value.ToUniversalTime().Date == DateTime.UtcNow.Date.AddDays(-1)); + } + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProfessorRepository.cs b/src/Infrastructure/Persistence/Repositories/ProfessorRepository.cs index 2fba22f0..4b2a622c 100644 --- a/src/Infrastructure/Persistence/Repositories/ProfessorRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/ProfessorRepository.cs @@ -3,52 +3,78 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class ProfessorRepository : IProfessorRepository { #region Global Scope private readonly ApplicationDbContext _context; - public ProfessorRepository(ApplicationDbContext context) => _context = context; - #endregion + public ProfessorRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Professor model) + public async Task CreateAsync(Professor model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.Professors + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Professors .Include(x => x.User) + .OrderBy(x => x.User!.Name) + .AsAsyncEnumerable() .Skip(skip) .Take(take) - .AsAsyncEnumerable() - .OrderBy(x => x.User?.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.Professors + public async Task GetByIdAsync(Guid? id) + { + return await _context.Professors .Include(x => x.User) .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Professor model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Professor model) + public async Task UpdateAsync(Professor model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - #endregion + + public async Task> GetAllActiveProfessorsAsync() + { + return await _context.Professors + .Include(x => x.User) + .AsAsyncEnumerable() + .Where(x => x.SuspensionEndDate < DateTime.UtcNow || x.SuspensionEndDate == null) + .ToListAsync(); + } + + public async Task GetByUserIdAsync(Guid? userId) + { + return await _context.Professors + .Include(x => x.User) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.UserId == userId); + } + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProgramTypeRepository.cs b/src/Infrastructure/Persistence/Repositories/ProgramTypeRepository.cs index fee1ac86..ec0b781a 100644 --- a/src/Infrastructure/Persistence/Repositories/ProgramTypeRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/ProgramTypeRepository.cs @@ -4,60 +4,68 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class ProgramTypeRepository : IProgramTypeRepository { #region Global Scope private readonly ApplicationDbContext _context; - public ProgramTypeRepository(ApplicationDbContext context) => _context = context; - #endregion + public ProgramTypeRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(ProgramType model) + public async Task CreateAsync(ProgramType model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.ProgramTypes + public async Task> GetAllAsync(int skip, int take) + { + return await _context.ProgramTypes + .OrderBy(x => x.Name) .Skip(skip) .Take(take) .AsAsyncEnumerable() - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.ProgramTypes + public async Task GetByIdAsync(Guid? id) + { + return await _context.ProgramTypes .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + ProgramType model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(ProgramType model) + public async Task UpdateAsync(ProgramType model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetProgramTypeByName(string name) + public async Task GetProgramTypeByNameAsync(string name) { - string loweredName = name.ToLower(); - var entities = await _context.ProgramTypes - .Where(x => x.Name!.ToLower() == loweredName) + string loweredName = name.ToLower(System.Globalization.CultureInfo.CurrentCulture); + List entities = await _context.ProgramTypes + .Where(x => x.Name!.ToLower(System.Globalization.CultureInfo.CurrentCulture) == loweredName) .AsAsyncEnumerable() .ToListAsync(); return entities.FirstOrDefault(); } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProjectActivityRepository.cs b/src/Infrastructure/Persistence/Repositories/ProjectActivityRepository.cs new file mode 100644 index 00000000..5ad2bfaa --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ProjectActivityRepository.cs @@ -0,0 +1,63 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ProjectActivityRepository : IProjectActivityRepository + { + private readonly ApplicationDbContext _context; + public ProjectActivityRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(ProjectActivity model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.ProjectActivities + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.ProjectActivities + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task DeleteAsync(Guid? id) + { + ProjectActivity model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task UpdateAsync(ProjectActivity model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task> GetByProjectIdAsync(Guid? projectId) + { + return await _context.ProjectActivities + .AsAsyncEnumerable() + .Where(x => x.ProjectId == projectId) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProjectEvaluationRepository.cs b/src/Infrastructure/Persistence/Repositories/ProjectEvaluationRepository.cs new file mode 100644 index 00000000..bd75b3d8 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ProjectEvaluationRepository.cs @@ -0,0 +1,56 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ProjectEvaluationRepository : IProjectEvaluationRepository + { + private readonly ApplicationDbContext _context; + public ProjectEvaluationRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(ProjectEvaluation model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.ProjectEvaluations + .Include(x => x.Project) + .Include(x => x.SubmissionEvaluator) + .Include(x => x.AppealEvaluator) + .Include(x => x.DocumentsEvaluator) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id) + ?? throw new Exception($"Nenhuma avaliação encontrada para o id {id}"); + } + + public async Task GetByProjectIdAsync(Guid? projectId) + { + return await _context.ProjectEvaluations + .Include(x => x.Project) + .Include(x => x.SubmissionEvaluator) + .Include(x => x.AppealEvaluator) + .Include(x => x.DocumentsEvaluator) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.ProjectId == projectId) + ?? throw new Exception($"Nenhuma avaliação encontrada para o ProjectId {projectId}"); + } + + public async Task UpdateAsync(ProjectEvaluation model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProjectFinalReportRepository.cs b/src/Infrastructure/Persistence/Repositories/ProjectFinalReportRepository.cs new file mode 100644 index 00000000..74bfc5de --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ProjectFinalReportRepository.cs @@ -0,0 +1,65 @@ +using System.Data.Entity; +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ProjectFinalReportRepository : IProjectFinalReportRepository + { + private readonly ApplicationDbContext _context; + public ProjectFinalReportRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(ProjectFinalReport model) + { + _context.Add(model); + await _context.SaveChangesAsync(); + return model; + } + + public async Task DeleteAsync(Guid? id) + { + var model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.ProjectFinalReports + .OrderByDescending(x => x.SendDate) + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.ProjectFinalReports + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task GetByProjectIdAsync(Guid? projectId) + { + return await _context.ProjectFinalReports + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.ProjectId == projectId); + } + + public async Task UpdateAsync(ProjectFinalReport model) + { + _context.Update(model); + await _context.SaveChangesAsync(); + return model; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProjectPartialReportRepository.cs b/src/Infrastructure/Persistence/Repositories/ProjectPartialReportRepository.cs new file mode 100644 index 00000000..ee0b30e7 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/ProjectPartialReportRepository.cs @@ -0,0 +1,65 @@ +using System.Data.Entity; +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class ProjectPartialReportRepository : IProjectPartialReportRepository + { + private readonly ApplicationDbContext _context; + public ProjectPartialReportRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(ProjectPartialReport model) + { + _context.Add(model); + await _context.SaveChangesAsync(); + return model; + } + + public async Task DeleteAsync(Guid? id) + { + var model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.ProjectPartialReports + .OrderBy(x => x.ProjectId) + .Skip(skip) + .Take(take) + .AsAsyncEnumerable() + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.ProjectPartialReports + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task GetByProjectIdAsync(Guid? projectId) + { + return await _context.ProjectPartialReports + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.ProjectId == projectId); + } + + public async Task UpdateAsync(ProjectPartialReport model) + { + _context.Update(model); + await _context.SaveChangesAsync(); + return model; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/ProjectRepository.cs b/src/Infrastructure/Persistence/Repositories/ProjectRepository.cs index fa4894bc..7c870b2c 100644 --- a/src/Infrastructure/Persistence/Repositories/ProjectRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/ProjectRepository.cs @@ -4,90 +4,90 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class ProjectRepository : IProjectRepository { private readonly ApplicationDbContext _context; - public ProjectRepository(ApplicationDbContext context) => _context = context; + public ProjectRepository(ApplicationDbContext context) + { + _context = context; + } - public async Task Create(Project model) + public async Task CreateAsync(Project project) { - _context.Add(model); + _context.Add(project); await _context.SaveChangesAsync(); - return model; + return project; } - public async Task Update(Project model) + public async Task UpdateAsync(Project project) { - _context.Update(model); + _context.Update(project); await _context.SaveChangesAsync(); - return model; + return project; + } + + public async Task UpdateManyAsync(IList projects) + { + _context.Update(projects); + return await _context.SaveChangesAsync(); } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Project project = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); - model.DeactivateEntity(); - return await Update(model); + project.DeactivateEntity(); + return await UpdateAsync(project); } - public async Task GetById(Guid? id) + public async Task GetByIdAsync(Guid? id) { return await _context.Projects + .Include(x => x.Notice) + .Include(x => x.SubArea) .Include(x => x.Student) .Include(x => x.Professor) - .Include(x => x.SubArea) .Include(x => x.ProgramType) - .Include(x => x.Notice) + .Include(x => x.Professor!.User) .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id) ?? throw new Exception($"Nenhum Projeto encontrado para o id {id}"); } - public async Task> GetProfessorProjects(int skip, int take, Guid? id, bool isClosed = false) + public async Task> GetProfessorProjectsAsync(int skip, int take, Guid? id, bool isClosed = false) { - if (isClosed) - { - return await _context.Projects - .Include(x => x.Student) - .Include(x => x.Professor) - .Include(x => x.SubArea) - .Include(x => x.ProgramType) - .Include(x => x.Notice) - .IgnoreQueryFilters() - .AsAsyncEnumerable() - .Where(x => x.StudentId == id - && (x.Status == EProjectStatus.Closed - || x.Status == EProjectStatus.Canceled)) - .Skip(skip) - .Take(take) - .ToListAsync(); - } - return await _context.Projects .Include(x => x.Student) + .Include(x => x.Student!.User) .Include(x => x.Professor) + .Include(x => x.Professor!.User) .Include(x => x.SubArea) .Include(x => x.ProgramType) .Include(x => x.Notice) .IgnoreQueryFilters() .AsAsyncEnumerable() - .Where(x => x.StudentId == id - && x.Status != EProjectStatus.Closed - && x.Status != EProjectStatus.Canceled) + .Where(x => WhereClause(id, x)) + .OrderByDescending(x => (x.Notice?.RegistrationStartDate)) // Traz os projetos mais recentes primeiro. + .ThenBy(x => x.Title) // Traz os projetos em ordem alfabética. .Skip(skip) .Take(take) .ToListAsync(); + + bool WhereClause(Guid? id, Project p) + { + return isClosed + ? p.ProfessorId == id && (p.Status == EProjectStatus.Closed || p.Status == EProjectStatus.Canceled) + : p.ProfessorId == id && p.Status != EProjectStatus.Closed && p.Status != EProjectStatus.Canceled; + } } - public async Task> GetProjects(int skip, int take, bool isClosed = false) + public async Task> GetProjectsAsync(int skip, int take, bool isClosed = false) { - if (isClosed) - { - return await _context.Projects + return isClosed + ? await _context.Projects .Include(x => x.Student) .Include(x => x.Professor) .Include(x => x.SubArea) @@ -95,13 +95,13 @@ public async Task> GetProjects(int skip, int take, bool isC .Include(x => x.Notice) .IgnoreQueryFilters() .AsAsyncEnumerable() - .Where(x => x.Status == EProjectStatus.Closed || x.Status == EProjectStatus.Canceled) + .Where(x => x.Status is EProjectStatus.Closed or EProjectStatus.Canceled) + .OrderByDescending(x => x.Notice?.RegistrationStartDate) // Traz os projetos mais recentes primeiro. + .ThenBy(x => x.Title) // Traz os projetos em ordem alfabética. .Skip(skip) .Take(take) - .ToListAsync(); - } - - return await _context.Projects + .ToListAsync() + : (IEnumerable)await _context.Projects .Include(x => x.Student) .Include(x => x.Professor) .Include(x => x.SubArea) @@ -109,46 +109,119 @@ public async Task> GetProjects(int skip, int take, bool isC .Include(x => x.Notice) .IgnoreQueryFilters() .AsAsyncEnumerable() - .Where(x => x.Status != EProjectStatus.Closed && x.Status != EProjectStatus.Canceled) + .Where(x => x.Status is not EProjectStatus.Closed and not EProjectStatus.Canceled) + .OrderByDescending(x => x.Notice?.RegistrationStartDate) // Traz os projetos mais recentes primeiro. + .ThenBy(x => x.Title) // Traz os projetos em ordem alfabética. .Skip(skip) .Take(take) .ToListAsync(); } - public async Task> GetStudentProjects(int skip, int take, Guid? id, bool isClosed = false) + public async Task> GetStudentProjectsAsync(int skip, int take, Guid? id, bool isClosed = false) { - if (isClosed) + return await _context.Projects + .Include(x => x.Student) + .Include(x => x.Student!.User) + .Include(x => x.Professor) + .Include(x => x.Professor!.User) + .Include(x => x.SubArea) + .Include(x => x.ProgramType) + .Include(x => x.Notice) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .Where(x => WhereClause(id, x)) + .OrderByDescending(x => x.Notice?.RegistrationStartDate) // Traz os projetos mais recentes primeiro. + .ThenBy(x => x.Title) // Traz os projetos em ordem alfabética. + .Skip(skip) + .Take(take) + .ToListAsync(); + + bool WhereClause(Guid? id, Project p) { - return await _context.Projects - .Include(x => x.Student) - .Include(x => x.Professor) - .Include(x => x.SubArea) - .Include(x => x.ProgramType) - .Include(x => x.Notice) - .IgnoreQueryFilters() - .AsAsyncEnumerable() - .Where(x => x.ProfessorId == id - && (x.Status == EProjectStatus.Closed - || x.Status == EProjectStatus.Canceled)) - .Skip(skip) - .Take(take) - .ToListAsync(); + return isClosed + ? p.StudentId == id && (p.Status == EProjectStatus.Closed || p.Status == EProjectStatus.Canceled) + : p.StudentId == id && p.Status != EProjectStatus.Closed && p.Status != EProjectStatus.Canceled; } + } + public async Task> GetProjectByNoticeAsync(Guid? noticeId) + { return await _context.Projects .Include(x => x.Student) + .Include(x => x.Student!.User) .Include(x => x.Professor) + .Include(x => x.Professor!.User) .Include(x => x.SubArea) .Include(x => x.ProgramType) .Include(x => x.Notice) .IgnoreQueryFilters() .AsAsyncEnumerable() - .Where(x => x.StudentId == id - && x.Status != EProjectStatus.Closed - && x.Status != EProjectStatus.Canceled) + .Where(x => x.NoticeId == noticeId) + .ToListAsync(); + } + + public async Task> GetProjectsToEvaluateAsync(int skip, int take, Guid? professorId) + { + return await _context.Projects + .Include(x => x.Student) + .Include(x => x.Professor) + .Include(x => x.SubArea) + .Include(x => x.ProgramType) + .Include(x => x.Notice) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .Where(x => x.Status is EProjectStatus.Submitted or EProjectStatus.Evaluation or EProjectStatus.DocumentAnalysis + && x.ProfessorId != professorId) + .OrderByDescending(x => x.Notice?.RegistrationStartDate) // Traz os projetos mais recentes primeiro. + .ThenBy(x => x.Title) // Traz os projetos em ordem alfabética. .Skip(skip) .Take(take) .ToListAsync(); } + + public async Task> GetProjectsWithCloseReportDueDateAsync() + { + DateTime nextMonth = DateTime.UtcNow.AddMonths(1); + DateTime nextWeek = DateTime.UtcNow.AddDays(7); + return await _context.Projects + .Include(x => x.Professor) + .Include(x => x.Notice) + .AsAsyncEnumerable() + .Where(x => + // Contabiliza apenas projetos que estejam no status Iniciado + x.Status is EProjectStatus.Started + && ( + // Data de entrega do relatório parcial deverá ocorrer dentro de 1 mês + (x.Notice!.PartialReportDeadline.HasValue && x.Notice.PartialReportDeadline.Value.Date == nextMonth.Date) || + // Data de entrega do relatório final deverá ocorrer dentro de 1 mês + (x.Notice!.FinalReportDeadline.HasValue && x.Notice.FinalReportDeadline.Value.Date == nextMonth.Date) || + // Data de entrega do relatório parcial deverá ocorrer dentro de 7 dias + (x.Notice!.PartialReportDeadline.HasValue && x.Notice.PartialReportDeadline.Value.Date == nextWeek.Date) || + // Data de entrega do relatório final deverá ocorrer dentro de 7 dias + (x.Notice!.FinalReportDeadline.HasValue && x.Notice.FinalReportDeadline.Value.Date == nextWeek.Date) + )) + .ToListAsync(); + } + + public async Task> GetPendingAndOverdueProjectsAsync() + { + // Obtém data atual em UTC para comparação + DateTime currentDate = DateTime.UtcNow; + + // Obtém projetos pendentes e com o prazo de resolução vencido + return await _context.Projects + .Include(x => x.Notice) + .AsAsyncEnumerable() + .Where(x => + // Projetos abertos e com o prazo de submissão vencido + (x.Status is EProjectStatus.Opened && x.Notice!.RegistrationEndDate < currentDate) || + // Projetos rejeitados e com o prazo de recurso vencido + (x.Status is EProjectStatus.Rejected && x.Notice!.AppealEndDate < currentDate) || + // Projetos aprovados e com entrega de documentação do aluno vencida + (x.Status is EProjectStatus.Accepted && x.Notice!.SendingDocsEndDate < currentDate) || + // Projetos pendentes de documentação e com o prazo de entrega vencido + (x.Status is EProjectStatus.Pending && x.Notice!.SendingDocsEndDate < currentDate)) + .ToListAsync(); + } } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/StudentDocumentsRepository.cs b/src/Infrastructure/Persistence/Repositories/StudentDocumentsRepository.cs new file mode 100644 index 00000000..0abf2259 --- /dev/null +++ b/src/Infrastructure/Persistence/Repositories/StudentDocumentsRepository.cs @@ -0,0 +1,82 @@ +using Domain.Entities; +using Domain.Interfaces.Repositories; +using Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories +{ + public class StudentDocumentsRepository : IStudentDocumentsRepository + { + private readonly ApplicationDbContext _context; + + public StudentDocumentsRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task CreateAsync(StudentDocuments model) + { + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); + return model; + } + + public async Task DeleteAsync(Guid? id) + { + StudentDocuments model = await GetByIdAsync(id) + ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); + model.DeactivateEntity(); + return await UpdateAsync(model); + } + + public async Task> GetAllAsync(int skip, int take) + { + return await _context.StudentDocuments + .Include(x => x.Project) + .OrderBy(x => x.ProjectId) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .Skip(skip) + .Take(take) + .ToListAsync(); + } + + public async Task GetByIdAsync(Guid? id) + { + return await _context.StudentDocuments + .Include(x => x.Project) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Id == id) + ?? throw new Exception($"Nenhum Documento encontrado para o id {id}"); + } + + public async Task GetByProjectIdAsync(Guid? projectId) + { + return await _context.StudentDocuments + .Include(x => x.Project) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.ProjectId == projectId) + ?? throw new Exception($"Nenhum Documento encontrado para o projectId {projectId}"); + } + + public async Task GetByStudentIdAsync(Guid? studentId) + { + return await _context.StudentDocuments + .Include(x => x.Project) + .Include(x => x.Project!.Student) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.Project?.StudentId == studentId) + ?? throw new Exception($"Nenhum Documento encontrado para o studentId {studentId}"); + } + + public async Task UpdateAsync(StudentDocuments model) + { + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); + return model; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/StudentRepository.cs b/src/Infrastructure/Persistence/Repositories/StudentRepository.cs index 450503d2..e3a95fa0 100644 --- a/src/Infrastructure/Persistence/Repositories/StudentRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/StudentRepository.cs @@ -1,57 +1,84 @@ -// using System.Data.Entity; -using Domain.Entities; +using Domain.Entities; using Domain.Interfaces.Repositories; using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class StudentRepository : IStudentRepository { #region Global Scope private readonly ApplicationDbContext _context; - public StudentRepository(ApplicationDbContext context) => _context = context; - #endregion + public StudentRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(Student model) + public async Task CreateAsync(Student model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task> GetAll(int skip, int take) => await _context.Students + public async Task> GetAllAsync(int skip, int take) + { + return await _context.Students .Include(x => x.User) - .Skip(skip) - .Take(take) .AsAsyncEnumerable() .OrderBy(x => x.User?.Name) + .Skip(skip) + .Take(take) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.Students + public async Task GetByIdAsync(Guid? id) + { + return await _context.Students .Include(x => x.User) .Include(x => x.Campus) .Include(x => x.Course) .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + Student model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(Student model) + public async Task UpdateAsync(Student model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - #endregion + + public async Task GetByRegistrationCodeAsync(string registrationCode) + { + return await _context.Students + .Include(x => x.User) + .Include(x => x.Campus) + .Include(x => x.Course) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.RegistrationCode == registrationCode.ToUpper()); + } + + public async Task GetByUserIdAsync(Guid? userId) + { + return await _context.Students + .Include(x => x.User) + .IgnoreQueryFilters() + .AsAsyncEnumerable() + .FirstOrDefaultAsync(x => x.UserId == userId); + } + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/SubAreaRepository.cs b/src/Infrastructure/Persistence/Repositories/SubAreaRepository.cs index edc1b8fc..a850b2a1 100644 --- a/src/Infrastructure/Persistence/Repositories/SubAreaRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/SubAreaRepository.cs @@ -3,67 +3,78 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class SubAreaRepository : ISubAreaRepository { #region Global Scope private readonly ApplicationDbContext _context; - public SubAreaRepository(ApplicationDbContext context) => _context = context; - #endregion + public SubAreaRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region Public Methods - public async Task Create(SubArea model) + public async Task CreateAsync(SubArea model) { - _context.Add(model); - await _context.SaveChangesAsync(); + _ = _context.Add(model); + _ = await _context.SaveChangesAsync(); return model; } - public async Task GetByCode(string? code) => await _context.SubAreas + public async Task GetByCodeAsync(string? code) + { + return await _context.SubAreas .Where(x => x.Code == code) .Include(x => x.Area) .Include(x => x.Area != null ? x.Area.MainArea : null) .ToAsyncEnumerable() .FirstOrDefaultAsync(); + } - public async Task> GetSubAreasByArea(Guid? areaId, int skip, int take) => await _context.SubAreas + public async Task> GetSubAreasByAreaAsync(Guid? areaId, int skip, int take) + { + return await _context.SubAreas .Where(x => x.AreaId == areaId) - .Skip(skip) - .Take(take) .Include(x => x.Area) .Include(x => x.Area != null ? x.Area.MainArea : null) - .AsAsyncEnumerable() .OrderBy(x => x.Name) + .AsAsyncEnumerable() + .Skip(skip) + .Take(take) .ToListAsync(); + } - public async Task GetById(Guid? id) => - await _context.SubAreas + public async Task GetByIdAsync(Guid? id) + { + return await _context.SubAreas .Include(x => x.Area) .Include(x => x.Area != null ? x.Area.MainArea : null) .IgnoreQueryFilters() .AsAsyncEnumerable() .FirstOrDefaultAsync(x => x.Id == id); + } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + SubArea model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - public async Task Update(SubArea model) + public async Task UpdateAsync(SubArea model) { - _context.Update(model); - await _context.SaveChangesAsync(); + _ = _context.Update(model); + _ = await _context.SaveChangesAsync(); return model; } - public Task> GetAll(int skip, int take) + public Task> GetAllAsync(int skip, int take) { throw new NotImplementedException(); } - #endregion + #endregion Public Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/TypeAssistanceRepository.cs b/src/Infrastructure/Persistence/Repositories/TypeAssistanceRepository.cs deleted file mode 100644 index baace1bd..00000000 --- a/src/Infrastructure/Persistence/Repositories/TypeAssistanceRepository.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Domain.Entities; -using Domain.Interfaces.Repositories; -using Infrastructure.Persistence.Context; -using Microsoft.EntityFrameworkCore; - -namespace Infrastructure.Persistence.Repositories -{ - public class TypeAssistanceRepository : ITypeAssistanceRepository - { - #region Global Scope - private readonly ApplicationDbContext _context; - public TypeAssistanceRepository(ApplicationDbContext context) => _context = context; - #endregion - - #region Public Methods - public async Task Create(TypeAssistance model) - { - _context.Add(model); - await _context.SaveChangesAsync(); - return model; - } - - public async Task> GetAll(int skip, int take) => await _context.TypeAssistances - .Skip(skip) - .Take(take) - .AsAsyncEnumerable() - .OrderBy(x => x.Name) - .ToListAsync(); - - public async Task GetById(Guid? id) => - await _context.TypeAssistances - .IgnoreQueryFilters() - .AsAsyncEnumerable() - .FirstOrDefaultAsync(x => x.Id == id); - - public async Task Delete(Guid? id) - { - var model = await GetById(id) - ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); - model.DeactivateEntity(); - return await Update(model); - } - - public async Task Update(TypeAssistance model) - { - _context.Update(model); - await _context.SaveChangesAsync(); - return model; - } - - public async Task GetTypeAssistanceByName(string name) - { - string loweredName = name.ToLower(); - var entities = await _context.TypeAssistances - .Where(x => x.Name!.ToLower() == loweredName) - .AsAsyncEnumerable() - .ToListAsync(); - return entities.FirstOrDefault(); - } - #endregion - } -} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Repositories/UserRepository.cs b/src/Infrastructure/Persistence/Repositories/UserRepository.cs index d0f2b46e..e1b1a4dc 100644 --- a/src/Infrastructure/Persistence/Repositories/UserRepository.cs +++ b/src/Infrastructure/Persistence/Repositories/UserRepository.cs @@ -3,61 +3,84 @@ using Infrastructure.Persistence.Context; using Microsoft.EntityFrameworkCore; -namespace Infrastructure.Persistence.Repositories +namespace Persistence.Repositories { public class UserRepository : IUserRepository { #region Global Scope private readonly ApplicationDbContext _context; - public UserRepository(ApplicationDbContext context) => _context = context; - #endregion + public UserRepository(ApplicationDbContext context) + { + _context = context; + } + #endregion Global Scope #region CRUD Methods - public async Task GetById(Guid? id) => await _context.Users + public async Task GetByIdAsync(Guid? id) + { + return await _context.Users .FindAsync(id); + } - public async Task> GetActiveUsers(int skip, int take) => await _context.Users + public async Task> GetActiveUsersAsync(int skip, int take) + { + return await _context.Users + .OrderBy(x => x.Name) .Skip(skip) .Take(take) - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task> GetInactiveUsers(int skip, int take) => await _context.Users + public async Task> GetInactiveUsersAsync(int skip, int take) + { + return await _context.Users .IgnoreQueryFilters() .AsAsyncEnumerable() .Where(x => x.DeletedAt != null) + .OrderBy(x => x.Name) .Skip(skip) .Take(take) - .OrderBy(x => x.Name) .ToListAsync(); + } - public async Task Update(User user) + public async Task UpdateAsync(User user) { - _context.Update(user); - await _context.SaveChangesAsync(); + _ = _context.Update(user); + _ = await _context.SaveChangesAsync(); return user; } - public async Task Create(User user) + public async Task CreateAsync(User user) { - _context.Add(user); - await _context.SaveChangesAsync(); + _ = _context.Add(user); + _ = await _context.SaveChangesAsync(); return user; } - public async Task Delete(Guid? id) + public async Task DeleteAsync(Guid? id) { - var model = await GetById(id) + User model = await GetByIdAsync(id) ?? throw new Exception($"Nenhum registro encontrado para o id ({id}) informado."); model.DeactivateEntity(); - return await Update(model); + return await UpdateAsync(model); } - #endregion + #endregion CRUD Methods #region Auth Methods - public async Task GetUserByEmail(string? email) => await _context.Users.FirstOrDefaultAsync(x => x.Email == email); + public async Task GetUserByEmailAsync(string? email) + { + return await _context.Users.FirstOrDefaultAsync(x => x.Email == email); + } - public async Task GetUserByCPF(string? cpf) => await _context.Users.FirstOrDefaultAsync(x => x.CPF == cpf); - #endregion + public async Task GetUserByCPFAsync(string? cpf) + { + return await _context.Users.FirstOrDefaultAsync(x => x.CPF == cpf); + } + + public async Task GetCoordinatorAsync() + { + return await _context.Users.FirstOrDefaultAsync(x => x.IsCoordinator); + } + #endregion Auth Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/ActivitiesSeeder.cs b/src/Infrastructure/Persistence/Seeds/ActivitiesSeeder.cs new file mode 100644 index 00000000..9a92da0d --- /dev/null +++ b/src/Infrastructure/Persistence/Seeds/ActivitiesSeeder.cs @@ -0,0 +1,128 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Persistence.Seeds +{ + public static class ActivitiesSeeder + { + public static void Seed(MigrationBuilder builder) + { + // Carrega tipos de atividades + string activityTypePath = Path.Combine(AppContext.BaseDirectory, "Seeds", "Data", "activity-types.txt"); + string[] activityTypeLines = File.ReadAllLines(activityTypePath); + + // Carrega atividades + string activitiesPath = Path.Combine(AppContext.BaseDirectory, "Seeds", "Data", "activities.txt"); + string[] activitiesLines = File.ReadAllLines(activitiesPath); + + // Cria edital base no banco + var noticeId = AddDefaultNotice(builder); + + // Cria tipos de atividades e atividades + foreach (string activityTypeLine in activityTypeLines) + { + // Carrega dados do tipo de atividade + var codeAT = activityTypeLine.Split(";")[0].Trim(); + var nameAT = activityTypeLine.Split(";")[1].Trim(); + var unityAT = activityTypeLine.Split(";")[2].Trim(); + + // Cria tipo de atividade no banco + var newActivityTypeId = AddActivityType(builder, nameAT, unityAT, noticeId); + + // Cria atividades + foreach (var activity in activitiesLines.Where(a => a.Split(";")[0] == codeAT)) + { + // Carrega dados da atividade + var codeA = activity.Split(";")[0].Trim(); + var nameA = activity.Split(";")[1].Trim(); + var pointsA = activity.Split(";")[2].Trim(); + var limitsA = activity.Split(";")[3].Trim(); + + // Trata valores nulos + if (limitsA == "NULL") + limitsA = null; + + // Cria atividade no banco + AddActivity(builder, newActivityTypeId, nameA, pointsA, limitsA); + } + } + } + + private static Guid AddDefaultNotice(MigrationBuilder builder) + { + var newNoticeId = Guid.NewGuid(); + builder.InsertData( + table: "Notices", + columns: new[] + { + "Id", "DeletedAt", + "RegistrationStartDate", "RegistrationEndDate", + "EvaluationStartDate", "EvaluationEndDate", + "AppealStartDate", "AppealEndDate", + "SendingDocsStartDate", "SendingDocsEndDate", + "PartialReportDeadline", "FinalReportDeadline", + "SuspensionYears", "DocUrl", "CreatedAt", "Description" + }, + values: new object[,] + { + { + newNoticeId, // Id + null!, // Deleted At + new DateTime(1990, 8, 1).ToUniversalTime(), // Registration Start Date + new DateTime(1990, 8, 31).ToUniversalTime(), // Registration End Date + new DateTime(1990, 9, 15).ToUniversalTime(), // Evaluation Start Date + new DateTime(1990, 9, 30).ToUniversalTime(), // Evaluation End Date + new DateTime(1990, 10, 10).ToUniversalTime(), // Appeal Start Date + new DateTime(1990, 10, 15).ToUniversalTime(), // Appeal End Date + new DateTime(1990, 11, 1).ToUniversalTime(), // Sending Docs Start Date + new DateTime(1990, 11, 15).ToUniversalTime(), // Sending Docs End Date + new DateTime(1991, 1, 15).ToUniversalTime(), // Partial Report Deadline + new DateTime(1991, 3, 15).ToUniversalTime(), // Final Report Deadline + 1, // Suspension Years + null!, // Doc URL + DateTime.UtcNow, // Created At + "Edital de Inicialização", // Description + }, + }, + schema: "public"); + return newNoticeId; + } + + private static Guid AddActivityType(MigrationBuilder builder, string name, string unity, Guid noticeId) + { + var newActivityTypeId = Guid.NewGuid(); + + builder.InsertData( + table: "ActivityTypes", + columns: new[] { "Id", "DeletedAt", "Name", "Unity", "NoticeId" }, + values: new object[,] + { + { newActivityTypeId, null!, name, unity, noticeId }, + }, + schema: "public"); + + return newActivityTypeId; + } + + private static void AddActivity(MigrationBuilder builder, Guid newActivityTypeId, string name, string points, string? limits) + { + builder.InsertData( + table: "Activities", + columns: new[] + { + "Id", "DeletedAt", "Name", "Points", "Limits", "ActivityTypeId" + }, + values: new object[,] + { + { + Guid.NewGuid(), + null!, + name, + points, + limits!, + newActivityTypeId + }, + }, + schema: "public"); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/AssistanceTypeSeeder.cs b/src/Infrastructure/Persistence/Seeds/AssistanceTypeSeeder.cs new file mode 100644 index 00000000..444b6b54 --- /dev/null +++ b/src/Infrastructure/Persistence/Seeds/AssistanceTypeSeeder.cs @@ -0,0 +1,33 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Persistence.Seeds +{ + public static class AssistanceTypeSeeder + { + public static void Seed(MigrationBuilder builder) + { + string file = Path.Combine(AppContext.BaseDirectory, "Seeds", "Data", "assistance-scolarship.txt"); + string[] parts; + foreach (string lines in File.ReadAllLines(file)) + { + parts = lines.Split(';'); + AddAssistanceType(builder, new AssistanceType(Guid.NewGuid(), parts[0], parts[1])); + } + } + + private static void AddAssistanceType(MigrationBuilder builder, AssistanceType sas) + { + if (sas?.Id == null || sas?.Name == null) + return; + builder.InsertData( + table: "AssistanceTypes", + columns: new[] { "Id", "DeletedAt", "Name", "Description" }, + values: new object[,] + { + { sas.Id, null!, sas.Name, sas.Description ?? "" }, + }, + schema: "public"); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/Data/activities.txt b/src/Infrastructure/Persistence/Seeds/Data/activities.txt new file mode 100644 index 00000000..c9df7aef --- /dev/null +++ b/src/Infrastructure/Persistence/Seeds/Data/activities.txt @@ -0,0 +1,15 @@ +001;Periódicos indexados nas bases do tipo 1 ou constrantes na base QUALIS A1 a A4;10;NULL +001;Periódicos indexados nas bases do tipo 2 ou constrantes na base QUALIS B1 e B2;6;30 +001;Periódicos constantes na base QUALIS B3 e B4;4;20 +001;Anais de Congressos;3;12 +001;Livros Completo;12;NULL +001;Livros Organizado;6;36 +001;Livros Capítulo;5;30 +001;Livros Tradução;4;24 +001;Participação em comissão editorial de editoras e instituições acadêmicas;2;8 +002;Autoria, coautoria, curadoria, direção de produções artísticas e culturais demostradas publicamente por meios típicos e característicos das áreas de artes visuais, dança, música, teatro, fotografia, cinema e afins - participação completa em todo trabalho;8;NULL +002;Autoria, coautoria, curadoria, direção de produções artísticas e culturais demostradas publicamente por meios típicos e característicos das áreas de artes visuais, dança, música, teatro, fotografia, cinema e afins - participação parcial no trabalho;1.5;4.5 +002;Composição ou Apresentação individual ou coletiva;2;12 +003;Carta patente com titularidade do CEFET/RJ;15;NULL +003;Depósito de patente com titularidade do CEFET/RJ;7.5;NULL +003;Registro de Software;1;2 \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/Data/activity-types.txt b/src/Infrastructure/Persistence/Seeds/Data/activity-types.txt new file mode 100644 index 00000000..bd819513 --- /dev/null +++ b/src/Infrastructure/Persistence/Seeds/Data/activity-types.txt @@ -0,0 +1,3 @@ +001;Produção Científica; Trabalhos Publicados +002;Produção Artística e Cultural; Produção Apresentada +003;Produção Técnica; Produtos Registrados \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/Seeder.cs b/src/Infrastructure/Persistence/Seeds/Seeder.cs index b75bbfc3..f99f80ba 100644 --- a/src/Infrastructure/Persistence/Seeds/Seeder.cs +++ b/src/Infrastructure/Persistence/Seeds/Seeder.cs @@ -4,13 +4,14 @@ namespace Persistence.Seeds { static public class Seeder { - public static void Seed(MigrationBuilder migrationBuilder) + public static void Execute(MigrationBuilder migrationBuilder) { + ActivitiesSeeder.Seed(migrationBuilder); AreasSeeder.Seed(migrationBuilder); + AssistanceTypeSeeder.Seed(migrationBuilder); CampusesSeeder.Seed(migrationBuilder); CoursesSeeder.Seed(migrationBuilder); ProgramTypesSeeder.Seed(migrationBuilder); - TypeAssistanceSeeder.Seed(migrationBuilder); UserSeeder.Seed(migrationBuilder); } } diff --git a/src/Infrastructure/Persistence/Seeds/TypeAssistanceSeeder.cs b/src/Infrastructure/Persistence/Seeds/TypeAssistanceSeeder.cs deleted file mode 100644 index 71bf4ceb..00000000 --- a/src/Infrastructure/Persistence/Seeds/TypeAssistanceSeeder.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Domain.Entities; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Persistence.Seeds -{ - public static class TypeAssistanceSeeder - { - public static void Seed(MigrationBuilder builder) - { - string file = Path.Combine(AppContext.BaseDirectory, "Seeds", "Data", "assistance-scolarship.txt"); - string[] parts; - foreach (string lines in File.ReadAllLines(file)) - { - parts = lines.Split(';'); - AddTypeAssistance(builder, new TypeAssistance(Guid.NewGuid(), parts[0], parts[1])); - } - } - - private static void AddTypeAssistance(MigrationBuilder builder, TypeAssistance sas) - { - if (sas?.Id == null || sas?.Name == null) - return; - builder.InsertData( - table: "TypeAssistances", - columns: new[] { "Id", "DeletedAt", "Name", "Description" }, - values: new object[,] - { - { sas.Id, null!, sas.Name, sas.Description ?? "" }, - }, - schema: "public"); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Seeds/UserSeeder.cs b/src/Infrastructure/Persistence/Seeds/UserSeeder.cs index 0f8a3dc6..b695b847 100644 --- a/src/Infrastructure/Persistence/Seeds/UserSeeder.cs +++ b/src/Infrastructure/Persistence/Seeds/UserSeeder.cs @@ -6,23 +6,42 @@ public static class UserSeeder { public static void Seed(MigrationBuilder builder) { + var userId = Guid.NewGuid(); + builder.InsertData( table: "Users", - columns: new[] { "Id", "DeletedAt", "Name", "Email", "Password", "CPF", "IsConfirmed", "Role" }, + columns: new[] { "Id", "DeletedAt", "Name", "Email", "Password", "CPF", "IsConfirmed", "IsCoordinator", "Role" }, values: new object[,] { { - Guid.NewGuid(), + userId, null!, "Root Admin", "edu-paes@hotmail.com", "ieSgcgP4w2Am80FsWXlCqg==.4F9CiQ8v2t4Mu62R/DVJILBtQrl8mPh73MlbogRMXkw=", "50806176083", true, + true, 0 }, }, schema: "public"); + + builder.InsertData( + table: "Professors", + columns: new[] { "Id", "DeletedAt", "SuspensionEndDate", "IdentifyLattes", "SIAPEEnrollment", "UserId" }, + values: new object[,] + { + { + Guid.NewGuid(), + null!, + null!, + "1234567", + 1234567, + userId + }, + }, + schema: "public"); } } } \ No newline at end of file diff --git a/src/Infrastructure/Services/AzureStorageService.cs b/src/Infrastructure/Services/AzureStorageService.cs index 2fd7e0eb..7089da67 100644 --- a/src/Infrastructure/Services/AzureStorageService.cs +++ b/src/Infrastructure/Services/AzureStorageService.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; -namespace Infrastructure.Services +namespace Services { public class AzureStorageService : IStorageFileService { @@ -24,61 +24,88 @@ public AzureStorageService(IConfiguration configuration, IDotEnvSecrets dotEnvSe ?? throw new Exception("A string de conexão não foi configurada."); // Verifica se as extensões de arquivos permitidas foram configuradas - var allowedExtensions = configuration.GetSection("StorageFile:AllowedExtensions") + IConfigurationSection allowedExtensions = configuration.GetSection("StorageFile:AllowedExtensions") ?? throw new Exception("As extensões de arquivos permitidas não foram configuradas."); _allowedExtensions = allowedExtensions.GetChildren().Select(x => x.Value).ToArray(); // Verifica se o tamanho máximo de arquivo foi configurado - if (long.TryParse(configuration["StorageFile:MaxFileSizeInBytes"], out long maxFileSizeInBytes)) - _maxFileSizeInBytes = maxFileSizeInBytes; - else - throw new Exception("O tamanho máximo de arquivo não foi configurado."); + _maxFileSizeInBytes = long.TryParse(configuration["StorageFile:MaxFileSizeInBytes"], out long maxFileSizeInBytes) + ? maxFileSizeInBytes + : throw new Exception("O tamanho máximo de arquivo não foi configurado."); } - #endregion + #endregion Global Scope - public async Task DeleteFile(string url) + public async Task DeleteFileAsync(string filePath) { // Cria o cliente do blob - var fileName = url.Split("/").LastOrDefault(); + string? fileName = filePath.Split("/").LastOrDefault(); // Cria o cliente do blob - var blobClient = new BlobClient(_connectionString, _container, fileName); + BlobClient blobClient = new(_connectionString, _container, fileName); // Deleta o arquivo - await blobClient.DeleteAsync(); + _ = await blobClient.DeleteAsync(); } - public async Task UploadFileAsync(IFormFile file, string? fileName = null) + public async Task UploadFileAsync(IFormFile file, string? filePath = null) { // Remove o arquivo anterior caso já exista - if (!string.IsNullOrEmpty(fileName)) + if (!string.IsNullOrEmpty(filePath)) { // Deleta o arquivo - await DeleteFile(fileName); + await DeleteFileAsync(filePath); // Utiliza o mesmo nome do arquivo anterior para o arquivo atual - fileName = fileName.Split("/").LastOrDefault(); + filePath = filePath.Split("/").LastOrDefault(); } // Gera um nome único para o arquivo else { - fileName = GenerateFileName(file); + filePath = GenerateFileName(file); } // Cria o cliente do blob - var blobClient = new BlobClient(_connectionString, _container, fileName); + BlobClient blobClient = new(_connectionString, _container, filePath); // Converte o arquivo para um array de bytes byte[] fileBytes; - using (var ms = new MemoryStream()) + using (MemoryStream ms = new()) { file.CopyTo(ms); fileBytes = ms.ToArray(); } // Salva o arquivo - using (var stream = new MemoryStream(fileBytes)) - await blobClient.UploadAsync(stream); + using (MemoryStream stream = new(fileBytes)) + { + _ = await blobClient.UploadAsync(stream); + } + + // Retorna o caminho do arquivo + return blobClient.Uri.AbsoluteUri; + } + + public async Task UploadFileAsync(byte[] file, string? filePath) + { + // Remove o arquivo anterior caso já exista + if (!string.IsNullOrEmpty(filePath)) + { + // Utiliza o mesmo nome do arquivo anterior para o arquivo atual + filePath = filePath.Split("/").LastOrDefault(); + } + else + { + throw new Exception("O caminho do arquivo não foi informado."); + } + + // Cria o cliente do blob + BlobClient blobClient = new(_connectionString, _container, filePath); + + // Salva o arquivo + using (MemoryStream stream = new(file)) + { + _ = await blobClient.UploadAsync(stream); + } // Retorna o caminho do arquivo return blobClient.Uri.AbsoluteUri; @@ -88,17 +115,21 @@ public async Task UploadFileAsync(IFormFile file, string? fileName = nul private string GenerateFileName(IFormFile file, bool onlyPdf = false) { // Verifica se a extensão do arquivo é permitida - var extension = Path.GetExtension(file.FileName); - if ((onlyPdf && extension != ".pdf") || (!_allowedExtensions.Contains(extension))) + string extension = Path.GetExtension(file.FileName); + if ((onlyPdf && extension != ".pdf") || !_allowedExtensions.Contains(extension)) + { throw new Exception($"A extensão ({extension}) do arquivo não é permitida."); + } // Verifica o tamanho do arquivo if (file.Length > _maxFileSizeInBytes) + { throw new Exception($"O tamanho do arquivo excede o máximo de {_maxFileSizeInBytes} bytes."); + } // Gera um nome único para o arquivo return $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; } - #endregion + #endregion Private Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Configs/SmtpConfiguration.cs b/src/Infrastructure/Services/Email/Configs/SmtpConfiguration.cs index e263add4..c1875b04 100644 --- a/src/Infrastructure/Services/Email/Configs/SmtpConfiguration.cs +++ b/src/Infrastructure/Services/Email/Configs/SmtpConfiguration.cs @@ -1,8 +1,10 @@ -namespace Infrastructure.Services.Email.Configs; -public class SmtpConfiguration +namespace Services.Email.Configs { - public int Port { get; set; } - public string? Server { get; set; } - public string? Username { get; set; } - public string? Password { get; set; } + public class SmtpConfiguration + { + public int Port { get; set; } + public string? Server { get; set; } + public string? Username { get; set; } + public string? Password { get; set; } + } } \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/EmailService.cs b/src/Infrastructure/Services/Email/EmailService.cs index 5d04793b..cac1bd1c 100644 --- a/src/Infrastructure/Services/Email/EmailService.cs +++ b/src/Infrastructure/Services/Email/EmailService.cs @@ -2,103 +2,229 @@ using System.Net.Mail; using Domain.Interfaces.Services; -namespace Infrastructure.Services.Email; -public class EmailService : IEmailService +namespace Services.Email { - #region Global Scope - private readonly string? _smtpServer; - private readonly int _smtpPort; - private readonly string? _smtpUsername; - private readonly string? _smtpPassword; - - public EmailService(string? smtpServer, int smtpPort, string? smtpUsername, string? smtpPassword) + public class EmailService : IEmailService { - _smtpServer = smtpServer; - _smtpPort = smtpPort; - _smtpUsername = smtpUsername; - _smtpPassword = smtpPassword; - } - #endregion + #region Global Scope + private readonly string? _smtpServer; + private readonly int _smtpPort; + private readonly string? _smtpUsername; + private readonly string? _smtpPassword; + private readonly string? _currentDirectory; + private readonly string? _siteUrl; + private readonly string? _logoGpic; - public async Task SendConfirmationEmail(string? email, string? name, string? token) - { - // Verifica se os parâmetros são nulos ou vazios - if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(token)) - throw new Exception("Parâmetros inválidos. Email, nome e token são obrigatórios."); + public EmailService(string? smtpServer, int smtpPort, string? smtpUsername, string? smtpPassword, string frontEndUrl) + { + _smtpServer = smtpServer; + _smtpPort = smtpPort; + _smtpUsername = smtpUsername; + _smtpPassword = smtpPassword; + _currentDirectory = AppContext.BaseDirectory; + _siteUrl = frontEndUrl; + _logoGpic = Convert.ToBase64String(File.ReadAllBytes(Path.Combine(_currentDirectory, "Email/Templates/Imgs/logo-gpic-original.svg"))); + } + #endregion Global Scope - // Lê mensagem do template em html salvo localmente - string? currentDirectory = Path.GetDirectoryName(typeof(EmailService).Assembly.Location) ?? throw new Exception("Não foi possível encontrar o diretório atual do projeto."); + public async Task SendConfirmationEmailAsync(string? email, string? name, string? token) + { + try + { + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(token)) + { + throw new Exception("Parâmetros inválidos. Email, nome e token são obrigatórios."); + } - // Lê mensagem do template em html salvo localmente - string template = await File.ReadAllTextAsync(Path.Combine(currentDirectory!, "Email/Templates/ConfirmEmail.html")); + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/ConfirmEmail.html")); - // Gera mensagem de envio - const string subject = "Confirmação de Cadastro"; - string body = template.Replace("#USER_NAME#", name).Replace("#USER_TOKEN#", token); + // Gera mensagem de envio + const string subject = "Confirmação de Cadastro"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#USER_NAME#", name) + .Replace("#USER_TOKEN#", token); - // Tentativa de envio de email - try - { - await SendEmailAsync(email, subject, body); - return true; + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } + catch (Exception ex) + { + throw new Exception($"Não foi possível enviar o email de confirmação de e-mail. {ex.Message}"); + } } - catch (Exception ex) + + public async Task SendResetPasswordEmailAsync(string? email, string? name, string? token) { - throw new Exception($"Não foi possível enviar o email de confirmação. {ex.Message}"); + try + { + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(token)) + { + throw new Exception("Parâmetros inválidos. Email, nome e token são obrigatórios."); + } + + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/ResetPassword.html")); + + // Gera mensagem de envio + const string subject = "Recuperação de Senha"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#USER_NAME#", name) + .Replace("#USER_TOKEN#", token); + + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } + catch (Exception ex) + { + throw new Exception($"Não foi possível enviar o email de atualização de senha. {ex.Message}"); + } } - } - public async Task SendResetPasswordEmail(string? email, string? name, string? token) - { - // Verifica se os parâmetros são nulos ou vazios - if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(token)) - throw new Exception("Parâmetros inválidos. Email, nome e token são obrigatórios."); + public async Task SendNoticeEmailAsync(string? email, string? name, DateTime? registrationStartDate, DateTime? registrationEndDate, string? noticeUrl) + { + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || registrationStartDate == null || registrationEndDate == null || string.IsNullOrEmpty(noticeUrl)) + { + throw new Exception("Parâmetros inválidos. Email, nome, data de início e fim das inscrições e url do edital são obrigatórios."); + } - // Lê mensagem do template em html salvo localmente - string? currentDirectory = Path.GetDirectoryName(typeof(EmailService).Assembly.Location) ?? throw new Exception("Não foi possível encontrar o diretório atual do projeto."); + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/NewEdital.html")); - // Lê mensagem do template em html salvo localmente - string template = await File.ReadAllTextAsync(Path.Combine(currentDirectory!, "Email/Templates/ResetPassword.html")); + // Gera mensagem de envio + const string subject = "Novo Edital"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#PROFESSOR_NAME#", name) + .Replace("#START_DATE#", registrationStartDate.Value.ToString("dd/MM/yyyy")) + .Replace("#END_DATE#", registrationEndDate.Value.ToString("dd/MM/yyyy")) + .Replace("#NOTICE_URL#", noticeUrl); - // Gera mensagem de envio - const string subject = "Recuperação de Senha"; - string body = template.Replace("#USER_NAME#", name).Replace("#USER_TOKEN#", token); + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } - // Tentativa de envio de email - try + public async Task SendProjectNotificationEmailAsync(string? email, string? name, string? projectTitle, string? status, string? description) { - await SendEmailAsync(email, subject, body); - return true; + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(projectTitle) || string.IsNullOrEmpty(status) || string.IsNullOrEmpty(description)) + { + throw new Exception("Parâmetros inválidos. Email, título do projeto, status e descrição são obrigatórios."); + } + + try + { + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/ProjectStatusChange.html")); + + // Gera mensagem de envio + const string subject = "Alteração de Status de Projeto"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#PROFESSOR_NAME#", name) + .Replace("#PROJECT_TITLE#", projectTitle) + .Replace("#PROJECT_STATUS#", status) + .Replace("#PROJECT_DESCRIPTION#", description); + + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } + catch (Exception ex) + { + throw new Exception($"Não foi possível enviar o email de notificação de modificação do projeto. {ex.Message}"); + } } - catch (Exception ex) + + public async Task SendRequestStudentRegisterEmailAsync(string? email) { - throw new Exception($"Não foi possível enviar o email de recuperação de senha. {ex.Message}"); + try + { + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email)) + { + throw new Exception("Parâmetros inválidos. Email é obrigatório."); + } + + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/RequestStudentRegister.html")); + + // Gera mensagem de envio + const string subject = "Solicitação de Registro"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#REGISTRATION_LINK#", _siteUrl); + + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } + catch (Exception ex) + { + throw new Exception($"Não foi possível enviar o email de solicitação de cadastro do estudante. {ex.Message}"); + } } - } - #region Private Methods - public async Task SendEmailAsync(string email, string subject, string message) - { - // Verifica se os parâmetros são nulos ou vazios - if (_smtpServer == null || _smtpUsername == null || _smtpPassword == null) - throw new Exception("Parâmetros de configuração de email não foram encontrados."); + public async Task SendNotificationOfReportDeadlineEmailAsync(string? email, string? name, string? projectTitle, string? reportType, DateTime? reportDeadline) + { + try + { + // Verifica se os parâmetros são nulos ou vazios + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(projectTitle) || string.IsNullOrEmpty(reportType) || !reportDeadline.HasValue) + { + throw new Exception("Parâmetros inválidos. Email, nome, título do projeto, tipo de relatório e prazo de entrega são obrigatórios."); + } + + // Lê mensagem do template em html salvo localmente + string template = await File.ReadAllTextAsync(Path.Combine(_currentDirectory!, "Email/Templates/NotifyOfReportDeadline.html")); + + // Gera mensagem de envio + const string subject = "Entrega de Relatório Próxima"; + string body = template + .Replace("#LOGO_GPIC#", _logoGpic) + .Replace("#PROFESSOR_NAME#", name) + .Replace("#PROJECT_TITLE#", projectTitle) + .Replace("#REPORT_TYPE#", reportType) + .Replace("#REPORT_DEADLINE#", reportDeadline.Value.ToString("dd/MM/yyyy")); + + // Tentativa de envio de email + await SendEmailAsync(email, subject, body); + } + catch (Exception ex) + { + throw new Exception($"Não foi possível enviar o email de notificação de prazo de entrega de relatório. {ex.Message}"); + } + } - // Cria objeto de mensagem - var mc = new MailMessage(_smtpUsername, email) + #region Private Methods + public async Task SendEmailAsync(string email, string subject, string message) { - Subject = subject, - Body = message, - IsBodyHtml = true - }; - - // Envia mensagem - using var smtpClient = new SmtpClient(_smtpServer, _smtpPort); - smtpClient.Timeout = 1000000; - smtpClient.EnableSsl = true; - smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network; - smtpClient.UseDefaultCredentials = false; - smtpClient.Credentials = new NetworkCredential(_smtpUsername, _smtpPassword); - await smtpClient.SendMailAsync(mc); + // Verifica se os parâmetros são nulos ou vazios + if (_smtpServer == null || _smtpUsername == null || _smtpPassword == null) + { + throw new Exception("Parâmetros de configuração de email não foram encontrados."); + } + + // Cria objeto de mensagem + MailMessage mc = new(_smtpUsername, email) + { + Subject = subject, + Body = message, + IsBodyHtml = true + }; + + // Envia mensagem + using SmtpClient smtpClient = new(_smtpServer, _smtpPort); + smtpClient.Timeout = 1000000; + smtpClient.EnableSsl = true; + smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network; + smtpClient.UseDefaultCredentials = false; + smtpClient.Credentials = new NetworkCredential(_smtpUsername, _smtpPassword); + await smtpClient.SendMailAsync(mc); + } + #endregion Private Methods } - #endregion } \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Factories/EmailServiceFactory.cs b/src/Infrastructure/Services/Email/Factories/EmailServiceFactory.cs index 69158979..572f6073 100644 --- a/src/Infrastructure/Services/Email/Factories/EmailServiceFactory.cs +++ b/src/Infrastructure/Services/Email/Factories/EmailServiceFactory.cs @@ -1,12 +1,18 @@ using Domain.Interfaces.Services; -using Infrastructure.Services.Email.Configs; +using Services.Email.Configs; -namespace Infrastructure.Services.Email.Factories; -public class EmailServiceFactory : IEmailServiceFactory +namespace Services.Email.Factories { - public IEmailService Create(SmtpConfiguration configuration) => new EmailService( - configuration.Server, - configuration.Port, - configuration.Username, - configuration.Password); + public class EmailServiceFactory : IEmailServiceFactory + { + public IEmailService Create(SmtpConfiguration settings, string frontEndUrl) + { + return new EmailService( + settings.Server, + settings.Port, + settings.Username, + settings.Password, + frontEndUrl); + } + } } \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Factories/IEmailServiceFactory.cs b/src/Infrastructure/Services/Email/Factories/IEmailServiceFactory.cs index 99649b55..ba686c90 100644 --- a/src/Infrastructure/Services/Email/Factories/IEmailServiceFactory.cs +++ b/src/Infrastructure/Services/Email/Factories/IEmailServiceFactory.cs @@ -1,10 +1,10 @@ using Domain.Interfaces.Services; -using Infrastructure.Services.Email.Configs; +using Services.Email.Configs; -namespace Infrastructure.Services.Email.Factories +namespace Services.Email.Factories { public interface IEmailServiceFactory { - IEmailService Create(SmtpConfiguration configuration); + IEmailService Create(SmtpConfiguration settings, string frontEndUrl); } } \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Templates/ConfirmEmail.html b/src/Infrastructure/Services/Email/Templates/ConfirmEmail.html index bfbf637f..a0d143f9 100644 --- a/src/Infrastructure/Services/Email/Templates/ConfirmEmail.html +++ b/src/Infrastructure/Services/Email/Templates/ConfirmEmail.html @@ -63,7 +63,11 @@

Confirmação de Cadastro

#USER_TOKEN#

-

Atenciosamente,
G-PIC

+ +
+
Atenciosamente,
+ +

Este e-mail foi enviado automaticamente. Por favor, não responda. diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.png b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.png new file mode 100644 index 00000000..9eb86110 Binary files /dev/null and b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.png differ diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.svg b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.svg new file mode 100644 index 00000000..1420029e --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-original.svg @@ -0,0 +1,150 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.png b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.png new file mode 100644 index 00000000..d2c4fae9 Binary files /dev/null and b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.png differ diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.svg b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.svg new file mode 100644 index 00000000..b94a5b05 --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/Imgs/icon-gpic-white.svg @@ -0,0 +1,98 @@ + + + + + \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.png b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.png new file mode 100644 index 00000000..8d4057a7 Binary files /dev/null and b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.png differ diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.svg b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.svg new file mode 100644 index 00000000..f6d3f05a --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-original.svg @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.png b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.png new file mode 100644 index 00000000..31dc4d60 Binary files /dev/null and b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.png differ diff --git a/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.svg b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.svg new file mode 100644 index 00000000..2a451d18 --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/Imgs/logo-gpic-white.svg @@ -0,0 +1,249 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Infrastructure/Services/Email/Templates/NewEdital.html b/src/Infrastructure/Services/Email/Templates/NewEdital.html new file mode 100644 index 00000000..65e969c8 --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/NewEdital.html @@ -0,0 +1,91 @@ + + + + + + Novo Edital Disponível + + + +

+

Novo Edital Disponível

+

Olá #PROFESSOR_NAME#,

+

Informamos que um novo edital está disponível em nossa plataforma.

+

+ O período para registrar seu projeto de pesquisa é de + #START_DATE# até #END_DATE#. +

+

+ Para mais informações acesse a plataforma do GPIC ou consulte as + informações do Edital através do link abaixo. +

+ + +
+
Atenciosamente,
+ + +

+ Este e-mail foi enviado automaticamente. Por favor, não responda. + +
Gerenciador de Projetos de Iniciação Científica, CEFET +
+

+
+ + diff --git a/src/Infrastructure/Services/Email/Templates/NotifyOfReportDeadline.html b/src/Infrastructure/Services/Email/Templates/NotifyOfReportDeadline.html new file mode 100644 index 00000000..fc7c45dc --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/NotifyOfReportDeadline.html @@ -0,0 +1,87 @@ + + + + + + Entrega de Relatório Próxima + + + +
+

Entrega de Relatório Próxima

+

Olá #PROFESSOR_NAME#,

+

+ Informamos que o #REPORT_TYPE# do seu projeto com o + título "#PROJECT_TITLE#" deverá ser entregue até o dia + #REPORT_DEADLINE#. +

+

+ Acesse a plataforma pelo link abaixo e realize o envio do relatório. +

+ + +
+
Atenciosamente,
+ + +

+ Este e-mail foi enviado automaticamente. Por favor, não responda. + +
Gerenciador de Projetos de Iniciação Científica, CEFET +
+

+
+ + diff --git a/src/Infrastructure/Services/Email/Templates/ProjectStatusChange.html b/src/Infrastructure/Services/Email/Templates/ProjectStatusChange.html new file mode 100644 index 00000000..fd7fb4be --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/ProjectStatusChange.html @@ -0,0 +1,68 @@ + + + + + + Status do Projeto Alterado + + + +
+

Status do Projeto Alterado

+

Olá #PROFESSOR_NAME#,

+

+ Informamos que o status do seu projeto com o título + "#PROJECT_TITLE#" foi alterado pelo avaliador. +

+

Novo Status: #PROJECT_STATUS#

+

Descrição do Status: #PROJECT_DESCRIPTION#

+

+ Para mais detalhes, você pode acessar o projeto na nossa plataforma. +

+ +
+
Atenciosamente,
+ + +

+ Este e-mail foi enviado automaticamente. Por favor, não responda. + +
Gerenciador de Projetos de Iniciação Científica, CEFET +
+

+
+ + diff --git a/src/Infrastructure/Services/Email/Templates/RequestStudentRegister.html b/src/Infrastructure/Services/Email/Templates/RequestStudentRegister.html new file mode 100644 index 00000000..28a81d92 --- /dev/null +++ b/src/Infrastructure/Services/Email/Templates/RequestStudentRegister.html @@ -0,0 +1,87 @@ + + + + + + Solicitação de Cadastro + + + +
+

Solicitação de Cadastro

+

Olá,

+

+ Um professor do CEFET solicitou o seu cadastro para a sua inclusão em um + processo de Iniciação Científica. +

+

+ Para prosseguir, clique no link abaixo e complete seu cadastro em nossa + plataforma. +

+ + +
+
Atenciosamente,
+ + +

+ Este e-mail foi enviado automaticamente. Por favor, não responda. + +
Gerenciador de Projetos de Iniciação Científica, CEFET +
+

+
+ + diff --git a/src/Infrastructure/Services/Email/Templates/ResetPassword.html b/src/Infrastructure/Services/Email/Templates/ResetPassword.html index d03d6e74..953044bb 100644 --- a/src/Infrastructure/Services/Email/Templates/ResetPassword.html +++ b/src/Infrastructure/Services/Email/Templates/ResetPassword.html @@ -61,7 +61,11 @@

Recuperação de Senha

Se você não solicitou a recuperação de senha, por favor ignore este e-mail.

-

Atenciosamente,
G-PIC

+ +
+
Atenciosamente,
+ +

Este e-mail foi enviado automaticamente. Por favor, não responda. diff --git a/src/Infrastructure/Services/HashService.cs b/src/Infrastructure/Services/HashService.cs index 6a881ea1..1fd9c2ac 100644 --- a/src/Infrastructure/Services/HashService.cs +++ b/src/Infrastructure/Services/HashService.cs @@ -3,52 +3,60 @@ using Domain.Interfaces.Services; using DZen.Security.Cryptography; -namespace Infrastructure.Services; -public class HashService : IHashService +namespace Services { - public string HashPassword(string password) + public class HashService : IHashService { - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - byte[] salt = new byte[16]; - using (var rng = RandomNumberGenerator.Create()) + public string HashPassword(string password) { - rng.GetBytes(salt); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] salt = new byte[16]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + byte[] hashBytes = HashPasswordWithSHA3(passwordBytes, salt); + string saltString = Convert.ToBase64String(salt); + string hashString = Convert.ToBase64String(hashBytes); + return $"{saltString}.{hashString}"; } - byte[] hashBytes = HashPasswordWithSHA3(passwordBytes, salt); - string saltString = Convert.ToBase64String(salt); - string hashString = Convert.ToBase64String(hashBytes); - return $"{saltString}.{hashString}"; - } - private static byte[] HashPasswordWithSHA3(byte[] password, byte[] salt) - { - byte[] saltedPassword = new byte[password.Length + salt.Length]; - Buffer.BlockCopy(password, 0, saltedPassword, 0, password.Length); - Buffer.BlockCopy(salt, 0, saltedPassword, password.Length, salt.Length); - using var hasher = new SHA3256Managed(); - return hasher.ComputeHash(saltedPassword); - } + private static byte[] HashPasswordWithSHA3(byte[] password, byte[] salt) + { + byte[] saltedPassword = new byte[password.Length + salt.Length]; + Buffer.BlockCopy(password, 0, saltedPassword, 0, password.Length); + Buffer.BlockCopy(salt, 0, saltedPassword, password.Length, salt.Length); + using SHA3256Managed hasher = new(); + return hasher.ComputeHash(saltedPassword); + } - public bool VerifyPassword(string password, string? hashedPassword) - { - // Verifica se o hash da senha é vazio - if (string.IsNullOrEmpty(hashedPassword)) - return false; + public bool VerifyPassword(string password, string? hashedPassword) + { + // Verifica se o hash da senha é vazio + if (string.IsNullOrEmpty(hashedPassword)) + { + return false; + } - // Verifica se a senha é vazia - if (string.IsNullOrEmpty(password)) - return false; + // Verifica se a senha é vazia + if (string.IsNullOrEmpty(password)) + { + return false; + } - // Verifica o formato do hash da senha - string[] parts = hashedPassword.Split('.'); - if (parts.Length != 2) - return false; + // Verifica o formato do hash da senha + string[] parts = hashedPassword.Split('.'); + if (parts.Length != 2) + { + return false; + } - // Verifica se o hash da senha é válido - byte[] salt = Convert.FromBase64String(parts[0]); - byte[] hashBytes = Convert.FromBase64String(parts[1]); - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - byte[] hashedPasswordBytes = HashPasswordWithSHA3(passwordBytes, salt); - return hashedPasswordBytes.SequenceEqual(hashBytes); + // Verifica se o hash da senha é válido + byte[] salt = Convert.FromBase64String(parts[0]); + byte[] hashBytes = Convert.FromBase64String(parts[1]); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] hashedPasswordBytes = HashPasswordWithSHA3(passwordBytes, salt); + return hashedPasswordBytes.SequenceEqual(hashBytes); + } } } \ No newline at end of file diff --git a/src/Infrastructure/Services/IDotEnvSecrets.cs b/src/Infrastructure/Services/IDotEnvSecrets.cs index 2c869c86..c5f5b3ef 100644 --- a/src/Infrastructure/Services/IDotEnvSecrets.cs +++ b/src/Infrastructure/Services/IDotEnvSecrets.cs @@ -1,4 +1,4 @@ -namespace Infrastructure.Services +namespace Services { public interface IDotEnvSecrets { diff --git a/src/Infrastructure/Services/ReportService.cs b/src/Infrastructure/Services/ReportService.cs new file mode 100644 index 00000000..be591147 --- /dev/null +++ b/src/Infrastructure/Services/ReportService.cs @@ -0,0 +1,177 @@ +using System.Globalization; +using Domain.Entities; +using Domain.Interfaces.Services; +using Microsoft.Extensions.Configuration; +using PuppeteerSharp; +using PuppeteerSharp.Media; + +namespace Services +{ + public class ReportService : IReportService + { + private readonly string? _outputPath; + private readonly CultureInfo _cultureInfo; + private readonly Dictionary _dayOfWeekMap; + private string? _certificateTemplate; + public ReportService(IConfiguration configuration) + { + // Obtém cultura brasileira para formatação de datas + _cultureInfo = new CultureInfo("pt-BR"); + + // Obtém o caminho do diretório temporário + _outputPath = configuration["TempPath"]; + + // Mapeamento dos dias da semana em inglês para português + _dayOfWeekMap = new() + { + { DayOfWeek.Sunday, "Domingo" }, + { DayOfWeek.Monday, "Segunda-feira" }, + { DayOfWeek.Tuesday, "Terça-feira" }, + { DayOfWeek.Wednesday, "Quarta-feira" }, + { DayOfWeek.Thursday, "Quinta-feira" }, + { DayOfWeek.Friday, "Sexta-feira" }, + { DayOfWeek.Saturday, "Sábado" } + }; + + // Verifica se o caminho temporário existe, não existindo cria + if (!Directory.Exists(_outputPath)) + Directory.CreateDirectory(_outputPath); + } + + public async Task GenerateCertificateAsync(Project project, string cordinatorName, string fileName) + { + // Caminho temporário onde será salvo o arquivo + string outputPath = Path.Combine(_outputPath!, fileName); + + // Obtém o semestre do edital + string noticeDate; + if (project!.Notice!.RegistrationStartDate!.Value.Month > 6) + noticeDate = $"{project.Notice.RegistrationStartDate.Value.Year} / 2"; + else + noticeDate = $"{project.Notice.RegistrationStartDate.Value.Year} / 1"; + + // Obtém a situação do aluno (Bolsista ou Voluntário) + var studentSituation = project.IsScholarshipCandidate ? "Término do Período de Bolsa" : "Término do Período de Voluntariado"; + + // Preenche o template do certificado + if (string.IsNullOrEmpty(_certificateTemplate)) + { + string basePath = AppContext.BaseDirectory; + + // Obtém conteúdo dos arquivos HTML, CSS e SVG + _certificateTemplate = await File.ReadAllTextAsync(Path.Combine(basePath, "Reports/certificate.html")); + string bootstrapCSS = await File.ReadAllTextAsync(Path.Combine(basePath, "Reports/bootstrap.min.css")); + string logoCefet = Convert.ToBase64String(File.ReadAllBytes(Path.Combine(basePath, "Reports/logo-cefet.svg"))); + string carimboCefet = Convert.ToBase64String(File.ReadAllBytes(Path.Combine(basePath, "Reports/carimbo.svg"))); + + // Substitui as variáveis do HTML pelos valores fixos do template + _certificateTemplate = _certificateTemplate + .Replace("#BOOTSTRAP_CSS#", bootstrapCSS) + .Replace("#LOGO_CEFET#", logoCefet) + .Replace("#CARIMBO_CEFET#", carimboCefet); + } + + // Substitui as variáveis do HTML pelos valores do projeto + string template = _certificateTemplate + .Replace("#NOME_ORIENTADOR#", project?.Professor?.User?.Name) + .Replace("#SUBAREA_PROJETO#", project?.SubArea?.Name) + .Replace("#NOME_ORIENTADO#", project?.Student?.User?.Name) + .Replace("#DATA_EDITAL#", noticeDate) + .Replace("#PIBIC_TIPO#", $"{project?.ProgramType?.Name} / CEFET") + .Replace("#INIP_EDITAL#", project?.Notice?.SendingDocsEndDate?.ToString("dd/MM/yyyy")) + .Replace("#FIMP_EDITAL#", project?.Notice?.FinalReportDeadline?.ToString("dd/MM/yyyy")) + .Replace("#SITP_EDITAL#", studentSituation) + .Replace("#TITULO_PROJETO_ALUNO#", project?.Title) + .Replace("#DIA_SEMANA#", _dayOfWeekMap[DateTime.Now.DayOfWeek]) + .Replace("#DIA_DATA#", DateTime.Now.Day.ToString(_cultureInfo)) + .Replace("#MES_DATA#", DateTime.Now.ToString("MMMM", _cultureInfo)) + .Replace("#ANO_DATA#", DateTime.Now.Year.ToString(_cultureInfo)) + .Replace("#NOME_COORDENADOR#", cordinatorName); + + // Transforma HTML em PDF + await new BrowserFetcher().DownloadAsync(); + var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); + var page = await browser.NewPageAsync(); + await page.SetContentAsync(template); + await page.PdfAsync(outputPath, new PdfOptions + { + Format = PaperFormat.A4, + PrintBackground = true, + MarginOptions = new MarginOptions + { + Top = "20px", + Bottom = "20px", + Left = "20px", + Right = "20px" + } + }); + await browser.CloseAsync(); + + // Retorna caminho onde foi salvo o arquivo + return outputPath; + } + + public async Task GenerateCertificateTestAsync(string fileName) + { + // Caminho temporário onde será salvo o arquivo + string outputPath = Path.Combine(_outputPath!, fileName); + + // Preenche o template do certificado + if (string.IsNullOrEmpty(_certificateTemplate)) + { + string basePath = AppContext.BaseDirectory; + + // Obtém conteúdo dos arquivos HTML, CSS e SVG + _certificateTemplate = await File.ReadAllTextAsync(Path.Combine(basePath, "Reports/certificate.html")); + string bootstrapCSS = await File.ReadAllTextAsync(Path.Combine(basePath, "Reports/bootstrap.min.css")); + string logoCefet = Convert.ToBase64String(File.ReadAllBytes(Path.Combine(basePath, "Reports/logo-cefet.svg"))); + string carimboCefet = Convert.ToBase64String(File.ReadAllBytes(Path.Combine(basePath, "Reports/carimbo.svg"))); + + // Substitui as variáveis do HTML pelos valores fixos do template + _certificateTemplate = _certificateTemplate + .Replace("#BOOTSTRAP_CSS#", bootstrapCSS) + .Replace("#LOGO_CEFET#", logoCefet) + .Replace("#CARIMBO_CEFET#", carimboCefet); + } + + // Substitui as variáveis do HTML pelos valores do projeto + string template = _certificateTemplate + .Replace("#NOME_ORIENTADOR#", "Luciana Faletti") + .Replace("#SUBAREA_PROJETO#", "Engenharia Elétrica") + .Replace("#NOME_ORIENTADO#", "Eduardo Paes") + .Replace("#DATA_EDITAL#", "2023 / 2") + .Replace("#PIBIC_TIPO#", "PIBIC / CEFET") + .Replace("#INIP_EDITAL#", "01/01/2023") + .Replace("#FIMP_EDITAL#", "01/03/2023") + .Replace("#SITP_EDITAL#", "Término do Período de Bolsa") + .Replace("#TITULO_PROJETO_ALUNO#", "Teste de Projeto") + .Replace("#DIA_SEMANA#", _dayOfWeekMap[DateTime.Now.DayOfWeek]) + .Replace("#DIA_DATA#", DateTime.Now.Day.ToString(_cultureInfo)) + .Replace("#MES_DATA#", DateTime.Now.ToString("MMMM", _cultureInfo)) + .Replace("#ANO_DATA#", DateTime.Now.Year.ToString(_cultureInfo)) + .Replace("#NOME_COORDENADOR#", "Diego Haddad"); + + // Transforma HTML em PDF + await new BrowserFetcher().DownloadAsync(); + var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); + var page = await browser.NewPageAsync(); + await page.SetContentAsync(template); + await page.PdfAsync(outputPath, new PdfOptions + { + Format = PaperFormat.A4, + PrintBackground = true, + MarginOptions = new MarginOptions + { + Top = "20px", + Bottom = "20px", + Left = "20px", + Right = "20px" + } + }); + await browser.CloseAsync(); + + // Retorna caminho onde foi salvo o arquivo + return outputPath; + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/Services/Reports/bootstrap.min.css b/src/Infrastructure/Services/Reports/bootstrap.min.css new file mode 100644 index 00000000..88387ee9 --- /dev/null +++ b/src/Infrastructure/Services/Reports/bootstrap.min.css @@ -0,0 +1,11741 @@ +@charset "UTF-8"; +:root, +[data-bs-theme="light"] { + --bs-blue: #0d6efd; + --bs-indigo: #6610f2; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #dc3545; + --bs-orange: #fd7e14; + --bs-yellow: #ffc107; + --bs-green: #198754; + --bs-teal: #20c997; + --bs-cyan: #0dcaf0; + --bs-black: #000; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #0d6efd; + --bs-secondary: #6c757d; + --bs-success: #198754; + --bs-info: #0dcaf0; + --bs-warning: #ffc107; + --bs-danger: #dc3545; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 13, 110, 253; + --bs-secondary-rgb: 108, 117, 125; + --bs-success-rgb: 25, 135, 84; + --bs-info-rgb: 13, 202, 240; + --bs-warning-rgb: 255, 193, 7; + --bs-danger-rgb: 220, 53, 69; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-primary-text-emphasis: #052c65; + --bs-secondary-text-emphasis: #2b2f32; + --bs-success-text-emphasis: #0a3622; + --bs-info-text-emphasis: #055160; + --bs-warning-text-emphasis: #664d03; + --bs-danger-text-emphasis: #58151c; + --bs-light-text-emphasis: #495057; + --bs-dark-text-emphasis: #495057; + --bs-primary-bg-subtle: #cfe2ff; + --bs-secondary-bg-subtle: #e2e3e5; + --bs-success-bg-subtle: #d1e7dd; + --bs-info-bg-subtle: #cff4fc; + --bs-warning-bg-subtle: #fff3cd; + --bs-danger-bg-subtle: #f8d7da; + --bs-light-bg-subtle: #fcfcfd; + --bs-dark-bg-subtle: #ced4da; + --bs-primary-border-subtle: #9ec5fe; + --bs-secondary-border-subtle: #c4c8cb; + --bs-success-border-subtle: #a3cfbb; + --bs-info-border-subtle: #9eeaf9; + --bs-warning-border-subtle: #ffe69c; + --bs-danger-border-subtle: #f1aeb5; + --bs-light-border-subtle: #e9ecef; + --bs-dark-border-subtle: #adb5bd; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, + "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.15), + rgba(255, 255, 255, 0) + ); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 1rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #212529; + --bs-body-color-rgb: 33, 37, 41; + --bs-body-bg: #fff; + --bs-body-bg-rgb: 255, 255, 255; + --bs-emphasis-color: #000; + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-secondary-color: rgba(33, 37, 41, 0.75); + --bs-secondary-color-rgb: 33, 37, 41; + --bs-secondary-bg: #e9ecef; + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-tertiary-color: rgba(33, 37, 41, 0.5); + --bs-tertiary-color-rgb: 33, 37, 41; + --bs-tertiary-bg: #f8f9fa; + --bs-tertiary-bg-rgb: 248, 249, 250; + --bs-heading-color: inherit; + --bs-link-color: #0d6efd; + --bs-link-color-rgb: 13, 110, 253; + --bs-link-decoration: underline; + --bs-link-hover-color: #0a58ca; + --bs-link-hover-color-rgb: 10, 88, 202; + --bs-code-color: #d63384; + --bs-highlight-bg: #fff3cd; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0.375rem; + --bs-border-radius-sm: 0.25rem; + --bs-border-radius-lg: 0.5rem; + --bs-border-radius-xl: 1rem; + --bs-border-radius-xxl: 2rem; + --bs-border-radius-2xl: var(--bs-border-radius-xxl); + --bs-border-radius-pill: 50rem; + --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); + --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-opacity: 0.25; + --bs-focus-ring-color: rgba(13, 110, 253, 0.25); + --bs-form-valid-color: #198754; + --bs-form-valid-border-color: #198754; + --bs-form-invalid-color: #dc3545; + --bs-form-invalid-border-color: #dc3545; +} +[data-bs-theme="dark"] { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-color-rgb: 222, 226, 230; + --bs-secondary-bg: #343a40; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-color-rgb: 222, 226, 230; + --bs-tertiary-bg: #2b3035; + --bs-tertiary-bg-rgb: 43, 48, 53; + --bs-primary-text-emphasis: #6ea8fe; + --bs-secondary-text-emphasis: #a7acb1; + --bs-success-text-emphasis: #75b798; + --bs-info-text-emphasis: #6edff6; + --bs-warning-text-emphasis: #ffda6a; + --bs-danger-text-emphasis: #ea868f; + --bs-light-text-emphasis: #f8f9fa; + --bs-dark-text-emphasis: #dee2e6; + --bs-primary-bg-subtle: #031633; + --bs-secondary-bg-subtle: #161719; + --bs-success-bg-subtle: #051b11; + --bs-info-bg-subtle: #032830; + --bs-warning-bg-subtle: #332701; + --bs-danger-bg-subtle: #2c0b0e; + --bs-light-bg-subtle: #343a40; + --bs-dark-bg-subtle: #1a1d20; + --bs-primary-border-subtle: #084298; + --bs-secondary-border-subtle: #41464b; + --bs-success-border-subtle: #0f5132; + --bs-info-border-subtle: #087990; + --bs-warning-border-subtle: #997404; + --bs-danger-border-subtle: #842029; + --bs-light-border-subtle: #495057; + --bs-dark-border-subtle: #343a40; + --bs-heading-color: inherit; + --bs-link-color: #6ea8fe; + --bs-link-hover-color: #8bb9fe; + --bs-link-color-rgb: 110, 168, 254; + --bs-link-hover-color-rgb: 139, 185, 254; + --bs-code-color: #e685b5; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + --bs-form-valid-color: #75b798; + --bs-form-valid-border-color: #75b798; + --bs-form-invalid-color: #ea868f; + --bs-form-invalid-border-color: #ea868f; +} +*, +::after, +::before { + box-sizing: border-box; +} +@media (prefers-reduced-motion: no-preference) { + :root { + scroll-behavior: smooth; + } +} +body { + margin: 0; + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size); + font-weight: var(--bs-body-font-weight); + line-height: var(--bs-body-line-height); + color: var(--bs-body-color); + text-align: var(--bs-body-text-align); + background-color: var(--bs-body-bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} +hr { + margin: 1rem 0; + color: inherit; + border: 0; + border-top: var(--bs-border-width) solid; + opacity: 0.25; +} +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; + color: var(--bs-heading-color); +} +.h1, +h1 { + font-size: calc(1.375rem + 1.5vw); +} +@media (min-width: 1200px) { + .h1, + h1 { + font-size: 2.5rem; + } +} +.h2, +h2 { + font-size: calc(1.325rem + 0.9vw); +} +@media (min-width: 1200px) { + .h2, + h2 { + font-size: 2rem; + } +} +.h3, +h3 { + font-size: calc(1.3rem + 0.6vw); +} +@media (min-width: 1200px) { + .h3, + h3 { + font-size: 1.75rem; + } +} +.h4, +h4 { + font-size: calc(1.275rem + 0.3vw); +} +@media (min-width: 1200px) { + .h4, + h4 { + font-size: 1.5rem; + } +} +.h5, +h5 { + font-size: 1.25rem; +} +.h6, +h6 { + font-size: 1rem; +} +p { + margin-top: 0; + margin-bottom: 1rem; +} +abbr[title] { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} +ol, +ul { + padding-left: 2rem; +} +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0; +} +dt { + font-weight: 700; +} +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} +blockquote { + margin: 0 0 1rem; +} +b, +strong { + font-weight: bolder; +} +.small, +small { + font-size: 0.875em; +} +.mark, +mark { + padding: 0.1875em; + background-color: var(--bs-highlight-bg); +} +sub, +sup { + position: relative; + font-size: 0.75em; + line-height: 0; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +a { + color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); + text-decoration: underline; +} +a:hover { + --bs-link-color-rgb: var(--bs-link-hover-color-rgb); +} +a:not([href]):not([class]), +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} +code, +kbd, +pre, +samp { + font-family: var(--bs-font-monospace); + font-size: 1em; +} +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + font-size: 0.875em; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} +code { + font-size: 0.875em; + color: var(--bs-code-color); + word-wrap: break-word; +} +a > code { + color: inherit; +} +kbd { + padding: 0.1875rem 0.375rem; + font-size: 0.875em; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); + border-radius: 0.25rem; +} +kbd kbd { + padding: 0; + font-size: 1em; +} +figure { + margin: 0 0 1rem; +} +img, +svg { + vertical-align: middle; +} +table { + caption-side: bottom; + border-collapse: collapse; +} +caption { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-secondary-color); + text-align: left; +} +th { + text-align: inherit; + text-align: -webkit-match-parent; +} +tbody, +td, +tfoot, +th, +thead, +tr { + border-color: inherit; + border-style: solid; + border-width: 0; +} +label { + display: inline-block; +} +button { + border-radius: 0; +} +button:focus:not(:focus-visible) { + outline: 0; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +button, +select { + text-transform: none; +} +[role="button"] { + cursor: pointer; +} +select { + word-wrap: normal; +} +select:disabled { + opacity: 1; +} +[list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not( + [type="week"] + ):not([type="time"])::-webkit-calendar-picker-indicator { + display: none !important; +} +[type="button"], +[type="reset"], +[type="submit"], +button { + -webkit-appearance: button; +} +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled), +button:not(:disabled) { + cursor: pointer; +} +::-moz-focus-inner { + padding: 0; + border-style: none; +} +textarea { + resize: vertical; +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + float: left; + width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: calc(1.275rem + 0.3vw); + line-height: inherit; +} +@media (min-width: 1200px) { + legend { + font-size: 1.5rem; + } +} +legend + * { + clear: left; +} +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-year-field { + padding: 0; +} +::-webkit-inner-spin-button { + height: auto; +} +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} +::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-color-swatch-wrapper { + padding: 0; +} +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} +::file-selector-button { + font: inherit; + -webkit-appearance: button; +} +output { + display: inline-block; +} +iframe { + border: 0; +} +summary { + display: list-item; + cursor: pointer; +} +progress { + vertical-align: baseline; +} +[hidden] { + display: none !important; +} +.lead { + font-size: 1.25rem; + font-weight: 300; +} +.display-1 { + font-size: calc(1.625rem + 4.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-1 { + font-size: 5rem; + } +} +.display-2 { + font-size: calc(1.575rem + 3.9vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-2 { + font-size: 4.5rem; + } +} +.display-3 { + font-size: calc(1.525rem + 3.3vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-3 { + font-size: 4rem; + } +} +.display-4 { + font-size: calc(1.475rem + 2.7vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-4 { + font-size: 3.5rem; + } +} +.display-5 { + font-size: calc(1.425rem + 2.1vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-5 { + font-size: 3rem; + } +} +.display-6 { + font-size: calc(1.375rem + 1.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-6 { + font-size: 2.5rem; + } +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; +} +.list-inline-item { + display: inline-block; +} +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} +.initialism { + font-size: 0.875em; + text-transform: uppercase; +} +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} +.blockquote > :last-child { + margin-bottom: 0; +} +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: 0.875em; + color: #6c757d; +} +.blockquote-footer::before { + content: "— "; +} +.img-fluid { + max-width: 100%; + height: auto; +} +.img-thumbnail { + padding: 0.25rem; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + max-width: 100%; + height: auto; +} +.figure { + display: inline-block; +} +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} +.figure-caption { + font-size: 0.875em; + color: var(--bs-secondary-color); +} +.container, +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl, +.container-xxl { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-right: auto; + margin-left: auto; +} +@media (min-width: 576px) { + .container, + .container-sm { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container, + .container-md, + .container-sm { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container, + .container-lg, + .container-md, + .container-sm { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl { + max-width: 1140px; + } +} +@media (min-width: 1400px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl, + .container-xxl { + max-width: 1320px; + } +} +:root { + --bs-breakpoint-xs: 0; + --bs-breakpoint-sm: 576px; + --bs-breakpoint-md: 768px; + --bs-breakpoint-lg: 992px; + --bs-breakpoint-xl: 1200px; + --bs-breakpoint-xxl: 1400px; +} +.row { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(-1 * var(--bs-gutter-y)); + margin-right: calc(-0.5 * var(--bs-gutter-x)); + margin-left: calc(-0.5 * var(--bs-gutter-x)); +} +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-top: var(--bs-gutter-y); +} +.col { + flex: 1 0 0%; +} +.row-cols-auto > * { + flex: 0 0 auto; + width: auto; +} +.row-cols-1 > * { + flex: 0 0 auto; + width: 100%; +} +.row-cols-2 > * { + flex: 0 0 auto; + width: 50%; +} +.row-cols-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; +} +.row-cols-4 > * { + flex: 0 0 auto; + width: 25%; +} +.row-cols-5 > * { + flex: 0 0 auto; + width: 20%; +} +.row-cols-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; +} +.col-auto { + flex: 0 0 auto; + width: auto; +} +.col-1 { + flex: 0 0 auto; + width: 8.33333333%; +} +.col-2 { + flex: 0 0 auto; + width: 16.66666667%; +} +.col-3 { + flex: 0 0 auto; + width: 25%; +} +.col-4 { + flex: 0 0 auto; + width: 33.33333333%; +} +.col-5 { + flex: 0 0 auto; + width: 41.66666667%; +} +.col-6 { + flex: 0 0 auto; + width: 50%; +} +.col-7 { + flex: 0 0 auto; + width: 58.33333333%; +} +.col-8 { + flex: 0 0 auto; + width: 66.66666667%; +} +.col-9 { + flex: 0 0 auto; + width: 75%; +} +.col-10 { + flex: 0 0 auto; + width: 83.33333333%; +} +.col-11 { + flex: 0 0 auto; + width: 91.66666667%; +} +.col-12 { + flex: 0 0 auto; + width: 100%; +} +.offset-1 { + margin-left: 8.33333333%; +} +.offset-2 { + margin-left: 16.66666667%; +} +.offset-3 { + margin-left: 25%; +} +.offset-4 { + margin-left: 33.33333333%; +} +.offset-5 { + margin-left: 41.66666667%; +} +.offset-6 { + margin-left: 50%; +} +.offset-7 { + margin-left: 58.33333333%; +} +.offset-8 { + margin-left: 66.66666667%; +} +.offset-9 { + margin-left: 75%; +} +.offset-10 { + margin-left: 83.33333333%; +} +.offset-11 { + margin-left: 91.66666667%; +} +.g-0, +.gx-0 { + --bs-gutter-x: 0; +} +.g-0, +.gy-0 { + --bs-gutter-y: 0; +} +.g-1, +.gx-1 { + --bs-gutter-x: 0.25rem; +} +.g-1, +.gy-1 { + --bs-gutter-y: 0.25rem; +} +.g-2, +.gx-2 { + --bs-gutter-x: 0.5rem; +} +.g-2, +.gy-2 { + --bs-gutter-y: 0.5rem; +} +.g-3, +.gx-3 { + --bs-gutter-x: 1rem; +} +.g-3, +.gy-3 { + --bs-gutter-y: 1rem; +} +.g-4, +.gx-4 { + --bs-gutter-x: 1.5rem; +} +.g-4, +.gy-4 { + --bs-gutter-y: 1.5rem; +} +.g-5, +.gx-5 { + --bs-gutter-x: 3rem; +} +.g-5, +.gy-5 { + --bs-gutter-y: 3rem; +} +@media (min-width: 576px) { + .col-sm { + flex: 1 0 0%; + } + .row-cols-sm-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-sm-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-sm-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-sm-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + .row-cols-sm-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-sm-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-sm-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + .col-sm-auto { + flex: 0 0 auto; + width: auto; + } + .col-sm-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-sm-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-3 { + flex: 0 0 auto; + width: 25%; + } + .col-sm-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-sm-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-sm-6 { + flex: 0 0 auto; + width: 50%; + } + .col-sm-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-sm-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-sm-9 { + flex: 0 0 auto; + width: 75%; + } + .col-sm-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-sm-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.33333333%; + } + .offset-sm-2 { + margin-left: 16.66666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.33333333%; + } + .offset-sm-5 { + margin-left: 41.66666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.33333333%; + } + .offset-sm-8 { + margin-left: 66.66666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.33333333%; + } + .offset-sm-11 { + margin-left: 91.66666667%; + } + .g-sm-0, + .gx-sm-0 { + --bs-gutter-x: 0; + } + .g-sm-0, + .gy-sm-0 { + --bs-gutter-y: 0; + } + .g-sm-1, + .gx-sm-1 { + --bs-gutter-x: 0.25rem; + } + .g-sm-1, + .gy-sm-1 { + --bs-gutter-y: 0.25rem; + } + .g-sm-2, + .gx-sm-2 { + --bs-gutter-x: 0.5rem; + } + .g-sm-2, + .gy-sm-2 { + --bs-gutter-y: 0.5rem; + } + .g-sm-3, + .gx-sm-3 { + --bs-gutter-x: 1rem; + } + .g-sm-3, + .gy-sm-3 { + --bs-gutter-y: 1rem; + } + .g-sm-4, + .gx-sm-4 { + --bs-gutter-x: 1.5rem; + } + .g-sm-4, + .gy-sm-4 { + --bs-gutter-y: 1.5rem; + } + .g-sm-5, + .gx-sm-5 { + --bs-gutter-x: 3rem; + } + .g-sm-5, + .gy-sm-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 768px) { + .col-md { + flex: 1 0 0%; + } + .row-cols-md-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-md-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-md-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-md-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + .row-cols-md-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-md-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-md-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + .col-md-auto { + flex: 0 0 auto; + width: auto; + } + .col-md-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-md-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-3 { + flex: 0 0 auto; + width: 25%; + } + .col-md-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-md-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-md-6 { + flex: 0 0 auto; + width: 50%; + } + .col-md-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-md-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-md-9 { + flex: 0 0 auto; + width: 75%; + } + .col-md-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-md-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-md-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.33333333%; + } + .offset-md-2 { + margin-left: 16.66666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.33333333%; + } + .offset-md-5 { + margin-left: 41.66666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.33333333%; + } + .offset-md-8 { + margin-left: 66.66666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.33333333%; + } + .offset-md-11 { + margin-left: 91.66666667%; + } + .g-md-0, + .gx-md-0 { + --bs-gutter-x: 0; + } + .g-md-0, + .gy-md-0 { + --bs-gutter-y: 0; + } + .g-md-1, + .gx-md-1 { + --bs-gutter-x: 0.25rem; + } + .g-md-1, + .gy-md-1 { + --bs-gutter-y: 0.25rem; + } + .g-md-2, + .gx-md-2 { + --bs-gutter-x: 0.5rem; + } + .g-md-2, + .gy-md-2 { + --bs-gutter-y: 0.5rem; + } + .g-md-3, + .gx-md-3 { + --bs-gutter-x: 1rem; + } + .g-md-3, + .gy-md-3 { + --bs-gutter-y: 1rem; + } + .g-md-4, + .gx-md-4 { + --bs-gutter-x: 1.5rem; + } + .g-md-4, + .gy-md-4 { + --bs-gutter-y: 1.5rem; + } + .g-md-5, + .gx-md-5 { + --bs-gutter-x: 3rem; + } + .g-md-5, + .gy-md-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 992px) { + .col-lg { + flex: 1 0 0%; + } + .row-cols-lg-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-lg-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-lg-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-lg-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + .row-cols-lg-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-lg-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-lg-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + .col-lg-auto { + flex: 0 0 auto; + width: auto; + } + .col-lg-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-lg-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-3 { + flex: 0 0 auto; + width: 25%; + } + .col-lg-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-lg-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-lg-6 { + flex: 0 0 auto; + width: 50%; + } + .col-lg-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-lg-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-lg-9 { + flex: 0 0 auto; + width: 75%; + } + .col-lg-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-lg-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-lg-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.33333333%; + } + .offset-lg-2 { + margin-left: 16.66666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.33333333%; + } + .offset-lg-5 { + margin-left: 41.66666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.33333333%; + } + .offset-lg-8 { + margin-left: 66.66666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.33333333%; + } + .offset-lg-11 { + margin-left: 91.66666667%; + } + .g-lg-0, + .gx-lg-0 { + --bs-gutter-x: 0; + } + .g-lg-0, + .gy-lg-0 { + --bs-gutter-y: 0; + } + .g-lg-1, + .gx-lg-1 { + --bs-gutter-x: 0.25rem; + } + .g-lg-1, + .gy-lg-1 { + --bs-gutter-y: 0.25rem; + } + .g-lg-2, + .gx-lg-2 { + --bs-gutter-x: 0.5rem; + } + .g-lg-2, + .gy-lg-2 { + --bs-gutter-y: 0.5rem; + } + .g-lg-3, + .gx-lg-3 { + --bs-gutter-x: 1rem; + } + .g-lg-3, + .gy-lg-3 { + --bs-gutter-y: 1rem; + } + .g-lg-4, + .gx-lg-4 { + --bs-gutter-x: 1.5rem; + } + .g-lg-4, + .gy-lg-4 { + --bs-gutter-y: 1.5rem; + } + .g-lg-5, + .gx-lg-5 { + --bs-gutter-x: 3rem; + } + .g-lg-5, + .gy-lg-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1200px) { + .col-xl { + flex: 1 0 0%; + } + .row-cols-xl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xl-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + .row-cols-xl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xl-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + .col-xl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.33333333%; + } + .offset-xl-2 { + margin-left: 16.66666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.33333333%; + } + .offset-xl-5 { + margin-left: 41.66666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.33333333%; + } + .offset-xl-8 { + margin-left: 66.66666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.33333333%; + } + .offset-xl-11 { + margin-left: 91.66666667%; + } + .g-xl-0, + .gx-xl-0 { + --bs-gutter-x: 0; + } + .g-xl-0, + .gy-xl-0 { + --bs-gutter-y: 0; + } + .g-xl-1, + .gx-xl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xl-1, + .gy-xl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xl-2, + .gx-xl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xl-2, + .gy-xl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xl-3, + .gx-xl-3 { + --bs-gutter-x: 1rem; + } + .g-xl-3, + .gy-xl-3 { + --bs-gutter-y: 1rem; + } + .g-xl-4, + .gx-xl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xl-4, + .gy-xl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xl-5, + .gx-xl-5 { + --bs-gutter-x: 3rem; + } + .g-xl-5, + .gy-xl-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1400px) { + .col-xxl { + flex: 1 0 0%; + } + .row-cols-xxl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xxl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xxl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xxl-3 > * { + flex: 0 0 auto; + width: 33.3333333333%; + } + .row-cols-xxl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xxl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xxl-6 > * { + flex: 0 0 auto; + width: 16.6666666667%; + } + .col-xxl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xxl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xxl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xxl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xxl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xxl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xxl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xxl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xxl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xxl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xxl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xxl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xxl-0 { + margin-left: 0; + } + .offset-xxl-1 { + margin-left: 8.33333333%; + } + .offset-xxl-2 { + margin-left: 16.66666667%; + } + .offset-xxl-3 { + margin-left: 25%; + } + .offset-xxl-4 { + margin-left: 33.33333333%; + } + .offset-xxl-5 { + margin-left: 41.66666667%; + } + .offset-xxl-6 { + margin-left: 50%; + } + .offset-xxl-7 { + margin-left: 58.33333333%; + } + .offset-xxl-8 { + margin-left: 66.66666667%; + } + .offset-xxl-9 { + margin-left: 75%; + } + .offset-xxl-10 { + margin-left: 83.33333333%; + } + .offset-xxl-11 { + margin-left: 91.66666667%; + } + .g-xxl-0, + .gx-xxl-0 { + --bs-gutter-x: 0; + } + .g-xxl-0, + .gy-xxl-0 { + --bs-gutter-y: 0; + } + .g-xxl-1, + .gx-xxl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xxl-1, + .gy-xxl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xxl-2, + .gx-xxl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xxl-2, + .gy-xxl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xxl-3, + .gx-xxl-3 { + --bs-gutter-x: 1rem; + } + .g-xxl-3, + .gy-xxl-3 { + --bs-gutter-y: 1rem; + } + .g-xxl-4, + .gx-xxl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xxl-4, + .gy-xxl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xxl-5, + .gx-xxl-5 { + --bs-gutter-x: 3rem; + } + .g-xxl-5, + .gy-xxl-5 { + --bs-gutter-y: 3rem; + } +} +.table { + --bs-table-color-type: initial; + --bs-table-bg-type: initial; + --bs-table-color-state: initial; + --bs-table-bg-state: initial; + --bs-table-color: var(--bs-body-color); + --bs-table-bg: var(--bs-body-bg); + --bs-table-border-color: var(--bs-border-color); + --bs-table-accent-bg: transparent; + --bs-table-striped-color: var(--bs-body-color); + --bs-table-striped-bg: rgba(0, 0, 0, 0.05); + --bs-table-active-color: var(--bs-body-color); + --bs-table-active-bg: rgba(0, 0, 0, 0.1); + --bs-table-hover-color: var(--bs-body-color); + --bs-table-hover-bg: rgba(0, 0, 0, 0.075); + width: 100%; + margin-bottom: 1rem; + vertical-align: top; + border-color: var(--bs-table-border-color); +} +.table > :not(caption) > * > * { + padding: 0.5rem 0.5rem; + color: var( + --bs-table-color-state, + var(--bs-table-color-type, var(--bs-table-color)) + ); + background-color: var(--bs-table-bg); + border-bottom-width: var(--bs-border-width); + box-shadow: inset 0 0 0 9999px + var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg))); +} +.table > tbody { + vertical-align: inherit; +} +.table > thead { + vertical-align: bottom; +} +.table-group-divider { + border-top: calc(var(--bs-border-width) * 2) solid currentcolor; +} +.caption-top { + caption-side: top; +} +.table-sm > :not(caption) > * > * { + padding: 0.25rem 0.25rem; +} +.table-bordered > :not(caption) > * { + border-width: var(--bs-border-width) 0; +} +.table-bordered > :not(caption) > * > * { + border-width: 0 var(--bs-border-width); +} +.table-borderless > :not(caption) > * > * { + border-bottom-width: 0; +} +.table-borderless > :not(:first-child) { + border-top-width: 0; +} +.table-striped > tbody > tr:nth-of-type(odd) > * { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} +.table-striped-columns > :not(caption) > tr > :nth-child(2n) { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} +.table-active { + --bs-table-color-state: var(--bs-table-active-color); + --bs-table-bg-state: var(--bs-table-active-bg); +} +.table-hover > tbody > tr:hover > * { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg); +} +.table-primary { + --bs-table-color: #000; + --bs-table-bg: #cfe2ff; + --bs-table-border-color: #bacbe6; + --bs-table-striped-bg: #c5d7f2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bacbe6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfd1ec; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-secondary { + --bs-table-color: #000; + --bs-table-bg: #e2e3e5; + --bs-table-border-color: #cbccce; + --bs-table-striped-bg: #d7d8da; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cbccce; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d1d2d4; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-success { + --bs-table-color: #000; + --bs-table-bg: #d1e7dd; + --bs-table-border-color: #bcd0c7; + --bs-table-striped-bg: #c7dbd2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bcd0c7; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c1d6cc; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-info { + --bs-table-color: #000; + --bs-table-bg: #cff4fc; + --bs-table-border-color: #badce3; + --bs-table-striped-bg: #c5e8ef; + --bs-table-striped-color: #000; + --bs-table-active-bg: #badce3; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfe2e9; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-warning { + --bs-table-color: #000; + --bs-table-bg: #fff3cd; + --bs-table-border-color: #e6dbb9; + --bs-table-striped-bg: #f2e7c3; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6dbb9; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ece1be; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-danger { + --bs-table-color: #000; + --bs-table-bg: #f8d7da; + --bs-table-border-color: #dfc2c4; + --bs-table-striped-bg: #eccccf; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfc2c4; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5c7ca; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-light { + --bs-table-color: #000; + --bs-table-bg: #f8f9fa; + --bs-table-border-color: #dfe0e1; + --bs-table-striped-bg: #ecedee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfe0e1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5e6e7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-dark { + --bs-table-color: #fff; + --bs-table-bg: #212529; + --bs-table-border-color: #373b3e; + --bs-table-striped-bg: #2c3034; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #373b3e; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #323539; + --bs-table-hover-color: #fff; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +@media (max-width: 575.98px) { + .table-responsive-sm { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1399.98px) { + .table-responsive-xxl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +.form-label { + margin-bottom: 0.5rem; +} +.col-form-label { + padding-top: calc(0.375rem + var(--bs-border-width)); + padding-bottom: calc(0.375rem + var(--bs-border-width)); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; +} +.col-form-label-lg { + padding-top: calc(0.5rem + var(--bs-border-width)); + padding-bottom: calc(0.5rem + var(--bs-border-width)); + font-size: 1.25rem; +} +.col-form-label-sm { + padding-top: calc(0.25rem + var(--bs-border-width)); + padding-bottom: calc(0.25rem + var(--bs-border-width)); + font-size: 0.875rem; +} +.form-text { + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-secondary-color); +} +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-clip: padding-box; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} +.form-control[type="file"] { + overflow: hidden; +} +.form-control[type="file"]:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control:focus { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-control::-webkit-date-and-time-value { + min-width: 85px; + height: 1.5em; + margin: 0; +} +.form-control::-webkit-datetime-edit { + display: block; + padding: 0; +} +.form-control::-moz-placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control::placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control:disabled { + background-color: var(--bs-secondary-bg); + opacity: 1; +} +.form-control::-webkit-file-upload-button { + padding: 0.375rem 0.75rem; + margin: -0.375rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + -webkit-transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +.form-control::file-selector-button { + padding: 0.375rem 0.75rem; + margin: -0.375rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; + } + .form-control::file-selector-button { + transition: none; + } +} +.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { + background-color: var(--bs-secondary-bg); +} +.form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: var(--bs-secondary-bg); +} +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.375rem 0; + margin-bottom: 0; + line-height: 1.5; + color: var(--bs-body-color); + background-color: transparent; + border: solid transparent; + border-width: var(--bs-border-width) 0; +} +.form-control-plaintext:focus { + outline: 0; +} +.form-control-plaintext.form-control-lg, +.form-control-plaintext.form-control-sm { + padding-right: 0; + padding-left: 0; +} +.form-control-sm { + min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.form-control-sm::-webkit-file-upload-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} +.form-control-sm::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} +.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +.form-control-lg::-webkit-file-upload-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} +.form-control-lg::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} +textarea.form-control { + min-height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-sm { + min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} +.form-control-color { + width: 3rem; + height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2)); + padding: 0.375rem; +} +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control-color::-moz-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color::-webkit-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color.form-control-sm { + height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +.form-control-color.form-control-lg { + height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} +.form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + display: block; + width: 100%; + padding: 0.375rem 2.25rem 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-image: var(--bs-form-select-bg-img), + var(--bs-form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 16px 12px; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-select { + transition: none; + } +} +.form-select:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-select[multiple], +.form-select[size]:not([size="1"]) { + padding-right: 0.75rem; + background-image: none; +} +.form-select:disabled { + background-color: var(--bs-secondary-bg); +} +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 var(--bs-body-color); +} +.form-select-sm { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.form-select-lg { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +[data-bs-theme="dark"] .form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); +} +.form-check { + display: block; + min-height: 1.5rem; + padding-left: 1.5em; + margin-bottom: 0.125rem; +} +.form-check .form-check-input { + float: left; + margin-left: -1.5em; +} +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right; +} +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0; +} +.form-check-input { + --bs-form-check-bg: var(--bs-body-bg); + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + color-adjust: exact; + print-color-adjust: exact; +} +.form-check-input[type="checkbox"] { + border-radius: 0.25em; +} +.form-check-input[type="radio"] { + border-radius: 50%; +} +.form-check-input:active { + filter: brightness(90%); +} +.form-check-input:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-check-input:checked { + background-color: #0d6efd; + border-color: #0d6efd; +} +.form-check-input:checked[type="checkbox"] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); +} +.form-check-input:checked[type="radio"] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-check-input[type="checkbox"]:indeterminate { + background-color: #0d6efd; + border-color: #0d6efd; + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; +} +.form-check-input:disabled ~ .form-check-label, +.form-check-input[disabled] ~ .form-check-label { + cursor: default; + opacity: 0.5; +} +.form-switch { + padding-left: 2.5em; +} +.form-switch .form-check-input { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + width: 2em; + margin-left: -2.5em; + background-image: var(--bs-form-switch-bg); + background-position: left center; + border-radius: 2em; + transition: background-position 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-switch .form-check-input { + transition: none; + } +} +.form-switch .form-check-input:focus { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e"); +} +.form-switch .form-check-input:checked { + background-position: right center; + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0; +} +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0; +} +.form-check-inline { + display: inline-block; + margin-right: 1rem; +} +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.btn-check:disabled + .btn, +.btn-check[disabled] + .btn { + pointer-events: none; + filter: none; + opacity: 0.65; +} +[data-bs-theme="dark"] + .form-switch + .form-check-input:not(:checked):not(:focus) { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e"); +} +.form-range { + width: 100%; + height: 1.5rem; + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; +} +.form-range:focus { + outline: 0; +} +.form-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-range::-moz-focus-outer { + border: 0; +} +.form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + -webkit-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } +} +.form-range::-webkit-slider-thumb:active { + background-color: #b6d4fe; +} +.form-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-tertiary-bg); + border-color: transparent; + border-radius: 1rem; +} +.form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + -moz-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -moz-transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } +} +.form-range::-moz-range-thumb:active { + background-color: #b6d4fe; +} +.form-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-tertiary-bg); + border-color: transparent; + border-radius: 1rem; +} +.form-range:disabled { + pointer-events: none; +} +.form-range:disabled::-webkit-slider-thumb { + background-color: var(--bs-secondary-color); +} +.form-range:disabled::-moz-range-thumb { + background-color: var(--bs-secondary-color); +} +.form-floating { + position: relative; +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext, +.form-floating > .form-select { + height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + min-height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + line-height: 1.25; +} +.form-floating > label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + height: 100%; + padding: 1rem 0.75rem; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: var(--bs-border-width) solid transparent; + transform-origin: 0 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-floating > label { + transition: none; + } +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext { + padding: 1rem 0.75rem; +} +.form-floating > .form-control-plaintext::-moz-placeholder, +.form-floating > .form-control::-moz-placeholder { + color: transparent; +} +.form-floating > .form-control-plaintext::placeholder, +.form-floating > .form-control::placeholder { + color: transparent; +} +.form-floating > .form-control-plaintext:not(:-moz-placeholder-shown), +.form-floating > .form-control:not(:-moz-placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control-plaintext:focus, +.form-floating > .form-control-plaintext:not(:placeholder-shown), +.form-floating > .form-control:focus, +.form-floating > .form-control:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control-plaintext:-webkit-autofill, +.form-floating > .form-control:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-select { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label, +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-select ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after { + position: absolute; + inset: 1rem 0.375rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control-plaintext ~ label::after, +.form-floating > .form-control:focus ~ label::after, +.form-floating > .form-control:not(:placeholder-shown) ~ label::after, +.form-floating > .form-select ~ label::after { + position: absolute; + inset: 1rem 0.375rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control:-webkit-autofill ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label { + border-width: var(--bs-border-width) 0; +} +.form-floating > .form-control:disabled ~ label, +.form-floating > :disabled ~ label { + color: #6c757d; +} +.form-floating > .form-control:disabled ~ label::after, +.form-floating > :disabled ~ label::after { + background-color: var(--bs-secondary-bg); +} +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .form-floating, +.input-group > .form-select { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} +.input-group > .form-control:focus, +.input-group > .form-floating:focus-within, +.input-group > .form-select:focus { + z-index: 5; +} +.input-group .btn { + position: relative; + z-index: 2; +} +.input-group .btn:focus { + z-index: 5; +} +.input-group-text { + display: flex; + align-items: center; + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-tertiary-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); +} +.input-group-lg > .btn, +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text { + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +.input-group-sm > .btn, +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: 3rem; +} +.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n + 3), +.input-group:not(.has-validation) + > .form-floating:not(:last-child) + > .form-control, +.input-group:not(.has-validation) + > .form-floating:not(:last-child) + > .form-select, +.input-group:not(.has-validation) + > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not( + .form-floating + ) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group.has-validation > .dropdown-toggle:nth-last-child(n + 4), +.input-group.has-validation + > .form-floating:nth-last-child(n + 3) + > .form-control, +.input-group.has-validation + > .form-floating:nth-last-child(n + 3) + > .form-select, +.input-group.has-validation + > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not( + .form-floating + ) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group + > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not( + .valid-feedback + ):not(.invalid-tooltip):not(.invalid-feedback) { + margin-left: calc(var(--bs-border-width) * -1); + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .form-floating:not(:first-child) > .form-control, +.input-group > .form-floating:not(:first-child) > .form-select { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-valid-color); +} +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-success); + border-radius: var(--bs-border-radius); +} +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip, +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip { + display: block; +} +.form-control.is-valid, +.was-validated .form-control:valid { + border-color: var(--bs-form-valid-border-color); + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-control.is-valid:focus, +.was-validated .form-control:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.was-validated textarea.form-control:valid, +textarea.form-control.is-valid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); +} +.form-select.is-valid, +.was-validated .form-select:valid { + border-color: var(--bs-form-valid-border-color); +} +.form-select.is-valid:not([multiple]):not([size]), +.form-select.is-valid:not([multiple])[size="1"], +.was-validated .form-select:valid:not([multiple]):not([size]), +.was-validated .form-select:valid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right 0.75rem center, center right 2.25rem; + background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-select.is-valid:focus, +.was-validated .form-select:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.form-control-color.is-valid, +.was-validated .form-control-color:valid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} +.form-check-input.is-valid, +.was-validated .form-check-input:valid { + border-color: var(--bs-form-valid-border-color); +} +.form-check-input.is-valid:checked, +.was-validated .form-check-input:valid:checked { + background-color: var(--bs-form-valid-color); +} +.form-check-input.is-valid:focus, +.was-validated .form-check-input:valid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.form-check-input.is-valid ~ .form-check-label, +.was-validated .form-check-input:valid ~ .form-check-label { + color: var(--bs-form-valid-color); +} +.form-check-inline .form-check-input ~ .valid-feedback { + margin-left: 0.5em; +} +.input-group > .form-control:not(:focus).is-valid, +.input-group > .form-floating:not(:focus-within).is-valid, +.input-group > .form-select:not(:focus).is-valid, +.was-validated .input-group > .form-control:not(:focus):valid, +.was-validated .input-group > .form-floating:not(:focus-within):valid, +.was-validated .input-group > .form-select:not(:focus):valid { + z-index: 3; +} +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-invalid-color); +} +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-danger); + border-radius: var(--bs-border-radius); +} +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip, +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip { + display: block; +} +.form-control.is-invalid, +.was-validated .form-control:invalid { + border-color: var(--bs-form-invalid-border-color); + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-control.is-invalid:focus, +.was-validated .form-control:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.was-validated textarea.form-control:invalid, +textarea.form-control.is-invalid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); +} +.form-select.is-invalid, +.was-validated .form-select:invalid { + border-color: var(--bs-form-invalid-border-color); +} +.form-select.is-invalid:not([multiple]):not([size]), +.form-select.is-invalid:not([multiple])[size="1"], +.was-validated .form-select:invalid:not([multiple]):not([size]), +.was-validated .form-select:invalid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right 0.75rem center, center right 2.25rem; + background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-select.is-invalid:focus, +.was-validated .form-select:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.form-control-color.is-invalid, +.was-validated .form-control-color:invalid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} +.form-check-input.is-invalid, +.was-validated .form-check-input:invalid { + border-color: var(--bs-form-invalid-border-color); +} +.form-check-input.is-invalid:checked, +.was-validated .form-check-input:invalid:checked { + background-color: var(--bs-form-invalid-color); +} +.form-check-input.is-invalid:focus, +.was-validated .form-check-input:invalid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.form-check-input.is-invalid ~ .form-check-label, +.was-validated .form-check-input:invalid ~ .form-check-label { + color: var(--bs-form-invalid-color); +} +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: 0.5em; +} +.input-group > .form-control:not(:focus).is-invalid, +.input-group > .form-floating:not(:focus-within).is-invalid, +.input-group > .form-select:not(:focus).is-invalid, +.was-validated .input-group > .form-control:not(:focus):invalid, +.was-validated .input-group > .form-floating:not(:focus-within):invalid, +.was-validated .input-group > .form-select:not(:focus):invalid { + z-index: 4; +} +.btn { + --bs-btn-padding-x: 0.75rem; + --bs-btn-padding-y: 0.375rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 1rem; + --bs-btn-font-weight: 400; + --bs-btn-line-height: 1.5; + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: transparent; + --bs-btn-border-width: var(--bs-border-width); + --bs-btn-border-color: transparent; + --bs-btn-border-radius: var(--bs-border-radius); + --bs-btn-hover-border-color: transparent; + --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), + 0 1px 1px rgba(0, 0, 0, 0.075); + --bs-btn-disabled-opacity: 0.65; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem + rgba(var(--bs-btn-focus-shadow-rgb), 0.5); + display: inline-block; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); + border-radius: var(--bs-btn-border-radius); + background-color: var(--bs-btn-bg); + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} +.btn:hover { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); +} +.btn-check + .btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color); +} +.btn:focus-visible { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:focus-visible + .btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, +.btn.active, +.btn.show, +.btn:first-child:active, +:not(.btn-check) + .btn:active { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color); +} +.btn-check:checked + .btn:focus-visible, +.btn.active:focus-visible, +.btn.show:focus-visible, +.btn:first-child:active:focus-visible, +:not(.btn-check) + .btn:active:focus-visible { + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn.disabled, +.btn:disabled, +fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); + pointer-events: none; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity); +} +.btn-primary { + --bs-btn-color: #fff; + --bs-btn-bg: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0b5ed7; + --bs-btn-hover-border-color: #0a58ca; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0a58ca; + --bs-btn-active-border-color: #0a53be; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #0d6efd; + --bs-btn-disabled-border-color: #0d6efd; +} +.btn-secondary { + --bs-btn-color: #fff; + --bs-btn-bg: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #5c636a; + --bs-btn-hover-border-color: #565e64; + --bs-btn-focus-shadow-rgb: 130, 138, 145; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #565e64; + --bs-btn-active-border-color: #51585e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #6c757d; + --bs-btn-disabled-border-color: #6c757d; +} +.btn-success { + --bs-btn-color: #fff; + --bs-btn-bg: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #157347; + --bs-btn-hover-border-color: #146c43; + --bs-btn-focus-shadow-rgb: 60, 153, 110; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #146c43; + --bs-btn-active-border-color: #13653f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #198754; + --bs-btn-disabled-border-color: #198754; +} +.btn-info { + --bs-btn-color: #000; + --bs-btn-bg: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #31d2f2; + --bs-btn-hover-border-color: #25cff2; + --bs-btn-focus-shadow-rgb: 11, 172, 204; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #3dd5f3; + --bs-btn-active-border-color: #25cff2; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #0dcaf0; + --bs-btn-disabled-border-color: #0dcaf0; +} +.btn-warning { + --bs-btn-color: #000; + --bs-btn-bg: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffca2c; + --bs-btn-hover-border-color: #ffc720; + --bs-btn-focus-shadow-rgb: 217, 164, 6; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffcd39; + --bs-btn-active-border-color: #ffc720; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ffc107; + --bs-btn-disabled-border-color: #ffc107; +} +.btn-danger { + --bs-btn-color: #fff; + --bs-btn-bg: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #bb2d3b; + --bs-btn-hover-border-color: #b02a37; + --bs-btn-focus-shadow-rgb: 225, 83, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #b02a37; + --bs-btn-active-border-color: #a52834; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #dc3545; + --bs-btn-disabled-border-color: #dc3545; +} +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #d3d4d5; + --bs-btn-hover-border-color: #c6c7c8; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #c6c7c8; + --bs-btn-active-border-color: #babbbc; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa; +} +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #424649; + --bs-btn-hover-border-color: #373b3e; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #4d5154; + --bs-btn-active-border-color: #373b3e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529; +} +.btn-outline-primary { + --bs-btn-color: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0d6efd; + --bs-btn-hover-border-color: #0d6efd; + --bs-btn-focus-shadow-rgb: 13, 110, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0d6efd; + --bs-btn-active-border-color: #0d6efd; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0d6efd; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0d6efd; + --bs-gradient: none; +} +.btn-outline-secondary { + --bs-btn-color: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #6c757d; + --bs-btn-hover-border-color: #6c757d; + --bs-btn-focus-shadow-rgb: 108, 117, 125; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #6c757d; + --bs-btn-active-border-color: #6c757d; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #6c757d; + --bs-gradient: none; +} +.btn-outline-success { + --bs-btn-color: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #198754; + --bs-btn-hover-border-color: #198754; + --bs-btn-focus-shadow-rgb: 25, 135, 84; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #198754; + --bs-btn-active-border-color: #198754; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #198754; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #198754; + --bs-gradient: none; +} +.btn-outline-info { + --bs-btn-color: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #0dcaf0; + --bs-btn-hover-border-color: #0dcaf0; + --bs-btn-focus-shadow-rgb: 13, 202, 240; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #0dcaf0; + --bs-btn-active-border-color: #0dcaf0; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0dcaf0; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0dcaf0; + --bs-gradient: none; +} +.btn-outline-warning { + --bs-btn-color: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffc107; + --bs-btn-hover-border-color: #ffc107; + --bs-btn-focus-shadow-rgb: 255, 193, 7; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffc107; + --bs-btn-active-border-color: #ffc107; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #ffc107; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #ffc107; + --bs-gradient: none; +} +.btn-outline-danger { + --bs-btn-color: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #dc3545; + --bs-btn-hover-border-color: #dc3545; + --bs-btn-focus-shadow-rgb: 220, 53, 69; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #dc3545; + --bs-btn-active-border-color: #dc3545; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #dc3545; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #dc3545; + --bs-gradient: none; +} +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f8f9fa; + --bs-gradient: none; +} +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #212529; + --bs-gradient: none; +} +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-color: var(--bs-link-hover-color); + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: 0 0 0 #000; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + text-decoration: underline; +} +.btn-link:focus-visible { + color: var(--bs-btn-color); +} +.btn-link:hover { + color: var(--bs-btn-hover-color); +} +.btn-group-lg > .btn, +.btn-lg { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 1.25rem; + --bs-btn-border-radius: var(--bs-border-radius-lg); +} +.btn-group-sm > .btn, +.btn-sm { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-sm); +} +.fade { + transition: opacity 0.15s linear; +} +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } +} +.fade:not(.show) { + opacity: 0; +} +.collapse:not(.show) { + display: none; +} +.collapsing { + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } +} +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing.collapse-horizontal { + transition: none; + } +} +.dropdown, +.dropdown-center, +.dropend, +.dropstart, +.dropup, +.dropup-center { + position: relative; +} +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + --bs-dropdown-inner-border-radius: calc( + var(--bs-border-radius) - var(--bs-border-width) + ); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-dropdown-link-color: var(--bs-body-color); + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); +} +.dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--bs-dropdown-spacer); +} +.dropdown-menu-start { + --bs-position: start; +} +.dropdown-menu-start[data-bs-popper] { + right: auto; + left: 0; +} +.dropdown-menu-end { + --bs-position: end; +} +.dropdown-menu-end[data-bs-popper] { + right: 0; + left: auto; +} +@media (min-width: 576px) { + .dropdown-menu-sm-start { + --bs-position: start; + } + .dropdown-menu-sm-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-sm-end { + --bs-position: end; + } + .dropdown-menu-sm-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 768px) { + .dropdown-menu-md-start { + --bs-position: start; + } + .dropdown-menu-md-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-md-end { + --bs-position: end; + } + .dropdown-menu-md-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 992px) { + .dropdown-menu-lg-start { + --bs-position: start; + } + .dropdown-menu-lg-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-lg-end { + --bs-position: end; + } + .dropdown-menu-lg-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1200px) { + .dropdown-menu-xl-start { + --bs-position: start; + } + .dropdown-menu-xl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xl-end { + --bs-position: end; + } + .dropdown-menu-xl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1400px) { + .dropdown-menu-xxl-start { + --bs-position: start; + } + .dropdown-menu-xxl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xxl-end { + --bs-position: end; + } + .dropdown-menu-xxl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +.dropup .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--bs-dropdown-spacer); +} +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--bs-dropdown-spacer); +} +.dropend .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropend .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-toggle::after { + vertical-align: 0; +} +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--bs-dropdown-spacer); +} +.dropstart .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropstart .dropdown-toggle::after { + display: none; +} +.dropstart .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropstart .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropstart .dropdown-toggle::before { + vertical-align: 0; +} +.dropdown-divider { + height: 0; + margin: var(--bs-dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1; +} +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; + border-radius: var(--bs-dropdown-item-border-radius, 0); +} +.dropdown-item:focus, +.dropdown-item:hover { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); +} +.dropdown-item.active, +.dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg); +} +.dropdown-item.disabled, +.dropdown-item:disabled { + color: var(--bs-dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent; +} +.dropdown-menu.show { + display: block; +} +.dropdown-header { + display: block; + padding: var(--bs-dropdown-header-padding-y) + var(--bs-dropdown-header-padding-x); + margin-bottom: 0; + font-size: 0.875rem; + color: var(--bs-dropdown-header-color); + white-space: nowrap; +} +.dropdown-item-text { + display: block; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color); +} +.dropdown-menu-dark { + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd; +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; +} +.btn-group-vertical > .btn, +.btn-group > .btn { + position: relative; + flex: 1 1 auto; +} +.btn-group-vertical > .btn-check:checked + .btn, +.btn-group-vertical > .btn-check:focus + .btn, +.btn-group-vertical > .btn.active, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:hover, +.btn-group > .btn-check:checked + .btn, +.btn-group > .btn-check:focus + .btn, +.btn-group > .btn.active, +.btn-group > .btn:active, +.btn-group > .btn:focus, +.btn-group > .btn:hover { + z-index: 1; +} +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} +.btn-group { + border-radius: var(--bs-border-radius); +} +.btn-group > .btn-group:not(:first-child), +.btn-group > :not(.btn-check:first-child) + .btn { + margin-left: calc(var(--bs-border-width) * -1); +} +.btn-group > .btn-group:not(:last-child) > .btn, +.btn-group > .btn.dropdown-toggle-split:first-child, +.btn-group > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:not(:first-child) > .btn, +.btn-group > .btn:nth-child(n + 3), +.btn-group > :not(.btn-check) + .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} +.dropdown-toggle-split::after, +.dropend .dropdown-toggle-split::after, +.dropup .dropdown-toggle-split::after { + margin-left: 0; +} +.dropstart .dropdown-toggle-split::before { + margin-right: 0; +} +.btn-group-sm > .btn + .dropdown-toggle-split, +.btn-sm + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} +.btn-group-lg > .btn + .dropdown-toggle-split, +.btn-lg + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} +.btn-group-vertical > .btn-group:not(:first-child), +.btn-group-vertical > .btn:not(:first-child) { + margin-top: calc(var(--bs-border-width) * -1); +} +.btn-group-vertical > .btn-group:not(:last-child) > .btn, +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child) > .btn, +.btn-group-vertical > .btn ~ .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.nav { + --bs-nav-link-padding-x: 1rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-link-color); + --bs-nav-link-hover-color: var(--bs-link-hover-color); + --bs-nav-link-disabled-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: 0 0; + border: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .nav-link { + transition: none; + } +} +.nav-link:focus, +.nav-link:hover { + color: var(--bs-nav-link-hover-color); +} +.nav-link:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.nav-link.disabled, +.nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + pointer-events: none; + cursor: default; +} +.nav-tabs { + --bs-nav-tabs-border-width: var(--bs-border-width); + --bs-nav-tabs-border-color: var(--bs-border-color); + --bs-nav-tabs-border-radius: var(--bs-border-radius); + --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) + var(--bs-secondary-bg) var(--bs-border-color); + --bs-nav-tabs-link-active-color: var(--bs-emphasis-color); + --bs-nav-tabs-link-active-bg: var(--bs-body-bg); + --bs-nav-tabs-link-active-border-color: var(--bs-border-color) + var(--bs-border-color) var(--bs-body-bg); + border-bottom: var(--bs-nav-tabs-border-width) solid + var(--bs-nav-tabs-border-color); +} +.nav-tabs .nav-link { + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); + border: var(--bs-nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--bs-nav-tabs-border-radius); + border-top-right-radius: var(--bs-nav-tabs-border-radius); +} +.nav-tabs .nav-link:focus, +.nav-tabs .nav-link:hover { + isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color); +} +.nav-tabs .nav-item.show .nav-link, +.nav-tabs .nav-link.active { + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color); +} +.nav-tabs .dropdown-menu { + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.nav-pills { + --bs-nav-pills-border-radius: var(--bs-border-radius); + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #0d6efd; +} +.nav-pills .nav-link { + border-radius: var(--bs-nav-pills-border-radius); +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg); +} +.nav-underline { + --bs-nav-underline-gap: 1rem; + --bs-nav-underline-border-width: 0.125rem; + --bs-nav-underline-link-active-color: var(--bs-emphasis-color); + gap: var(--bs-nav-underline-gap); +} +.nav-underline .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--bs-nav-underline-border-width) solid transparent; +} +.nav-underline .nav-link:focus, +.nav-underline .nav-link:hover { + border-bottom-color: currentcolor; +} +.nav-underline .nav-link.active, +.nav-underline .show > .nav-link { + font-weight: 700; + color: var(--bs-nav-underline-link-active-color); + border-bottom-color: currentcolor; +} +.nav-fill .nav-item, +.nav-fill > .nav-link { + flex: 1 1 auto; + text-align: center; +} +.nav-justified .nav-item, +.nav-justified > .nav-link { + flex-basis: 0; + flex-grow: 1; + text-align: center; +} +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100%; +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); + --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8); + --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3); + --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15); + --bs-navbar-toggler-border-radius: var(--bs-border-radius); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); +} +.navbar > .container, +.navbar > .container-fluid, +.navbar > .container-lg, +.navbar > .container-md, +.navbar > .container-sm, +.navbar > .container-xl, +.navbar > .container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; +} +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap; +} +.navbar-brand:focus, +.navbar-brand:hover { + color: var(--bs-navbar-brand-hover-color); +} +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.navbar-nav .nav-link.active, +.navbar-nav .nav-link.show { + color: var(--bs-navbar-active-color); +} +.navbar-nav .dropdown-menu { + position: static; +} +.navbar-text { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-navbar-color); +} +.navbar-text a, +.navbar-text a:focus, +.navbar-text a:hover { + color: var(--bs-navbar-active-color); +} +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; +} +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition); +} +@media (prefers-reduced-motion: reduce) { + .navbar-toggler { + transition: none; + } +} +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); +} +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} +.navbar-nav-scroll { + max-height: var(--bs-scroll-height, 75vh); + overflow-y: auto; +} +@media (min-width: 576px) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-sm .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-md .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-md .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-lg .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1400px) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xxl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xxl .navbar-toggler { + display: none; + } + .navbar-expand-xxl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +.navbar-expand { + flex-wrap: nowrap; + justify-content: flex-start; +} +.navbar-expand .navbar-nav { + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); +} +.navbar-expand .navbar-nav-scroll { + overflow: visible; +} +.navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-expand .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; +} +.navbar-expand .offcanvas .offcanvas-header { + display: none; +} +.navbar-expand .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; +} +.navbar-dark, +.navbar[data-bs-theme="dark"] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +[data-bs-theme="dark"] .navbar-toggler-icon { + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +.card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-title-color: ; + --bs-card-subtitle-color: ; + --bs-card-border-width: var(--bs-border-width); + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: var(--bs-border-radius); + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc( + var(--bs-border-radius) - (var(--bs-border-width)) + ); + --bs-card-cap-padding-y: 0.5rem; + --bs-card-cap-padding-x: 1rem; + --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: var(--bs-body-bg); + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + height: var(--bs-card-height); + color: var(--bs-body-color); + word-wrap: break-word; + background-color: var(--bs-card-bg); + background-clip: border-box; + border: var(--bs-card-border-width) solid var(--bs-card-border-color); + border-radius: var(--bs-card-border-radius); +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group { + border-top: inherit; + border-bottom: inherit; +} +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} +.card > .card-header + .list-group, +.card > .list-group + .card-footer { + border-top: 0; +} +.card-body { + flex: 1 1 auto; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color); +} +.card-title { + margin-bottom: var(--bs-card-title-spacer-y); + color: var(--bs-card-title-color); +} +.card-subtitle { + margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); + margin-bottom: 0; + color: var(--bs-card-subtitle-color); +} +.card-text:last-child { + margin-bottom: 0; +} +.card-link + .card-link { + margin-left: var(--bs-card-spacer-x); +} +.card-header { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + margin-bottom: 0; + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-header:first-child { + border-radius: var(--bs-card-inner-border-radius) + var(--bs-card-inner-border-radius) 0 0; +} +.card-footer { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-footer:last-child { + border-radius: 0 0 var(--bs-card-inner-border-radius) + var(--bs-card-inner-border-radius); +} +.card-header-tabs { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); + border-bottom: 0; +} +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg); +} +.card-header-pills { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); +} +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--bs-card-img-overlay-padding); + border-radius: var(--bs-card-inner-border-radius); +} +.card-img, +.card-img-bottom, +.card-img-top { + width: 100%; +} +.card-img, +.card-img-top { + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} +.card-img, +.card-img-bottom { + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} +.card-group > .card { + margin-bottom: var(--bs-card-group-margin); +} +@media (min-width: 576px) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-header, + .card-group > .card:not(:last-child) .card-img-top { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-footer, + .card-group > .card:not(:last-child) .card-img-bottom { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-header, + .card-group > .card:not(:first-child) .card-img-top { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-footer, + .card-group > .card:not(:first-child) .card-img-bottom { + border-bottom-left-radius: 0; + } +} +.accordion { + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-bg: var(--bs-body-bg); + --bs-accordion-transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: var(--bs-border-width); + --bs-accordion-border-radius: var(--bs-border-radius); + --bs-accordion-inner-border-radius: calc( + var(--bs-border-radius) - (var(--bs-border-width)) + ); + --bs-accordion-btn-padding-x: 1.25rem; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 1.25rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-border-color: #86b7fe; + --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-accordion-body-padding-x: 1.25rem; + --bs-accordion-body-padding-y: 1rem; + --bs-accordion-active-color: var(--bs-primary-text-emphasis); + --bs-accordion-active-bg: var(--bs-primary-bg-subtle); +} +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); + font-size: 1rem; + color: var(--bs-accordion-btn-color); + text-align: left; + background-color: var(--bs-accordion-btn-bg); + border: 0; + border-radius: 0; + overflow-anchor: none; + transition: var(--bs-accordion-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button { + transition: none; + } +} +.accordion-button:not(.collapsed) { + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 + var(--bs-accordion-border-color); +} +.accordion-button:not(.collapsed)::after { + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform); +} +.accordion-button::after { + flex-shrink: 0; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--bs-accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--bs-accordion-btn-icon-width); + transition: var(--bs-accordion-btn-icon-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button::after { + transition: none; + } +} +.accordion-button:hover { + z-index: 2; +} +.accordion-button:focus { + z-index: 3; + border-color: var(--bs-accordion-btn-focus-border-color); + outline: 0; + box-shadow: var(--bs-accordion-btn-focus-box-shadow); +} +.accordion-header { + margin-bottom: 0; +} +.accordion-item { + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid + var(--bs-accordion-border-color); +} +.accordion-item:first-of-type { + border-top-left-radius: var(--bs-accordion-border-radius); + border-top-right-radius: var(--bs-accordion-border-radius); +} +.accordion-item:first-of-type .accordion-button { + border-top-left-radius: var(--bs-accordion-inner-border-radius); + border-top-right-radius: var(--bs-accordion-inner-border-radius); +} +.accordion-item:not(:first-of-type) { + border-top: 0; +} +.accordion-item:last-of-type { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: var(--bs-accordion-inner-border-radius); + border-bottom-left-radius: var(--bs-accordion-inner-border-radius); +} +.accordion-item:last-of-type .accordion-collapse { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} +.accordion-body { + padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x); +} +.accordion-flush .accordion-collapse { + border-width: 0; +} +.accordion-flush .accordion-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} +.accordion-flush .accordion-item:first-child { + border-top: 0; +} +.accordion-flush .accordion-item:last-child { + border-bottom: 0; +} +.accordion-flush .accordion-item .accordion-button, +.accordion-flush .accordion-item .accordion-button.collapsed { + border-radius: 0; +} +[data-bs-theme="dark"] .accordion-button::after { + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} +.breadcrumb { + --bs-breadcrumb-padding-x: 0; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: var(--bs-secondary-color); + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); + list-style: none; + background-color: var(--bs-breadcrumb-bg); + border-radius: var(--bs-breadcrumb-border-radius); +} +.breadcrumb-item + .breadcrumb-item { + padding-left: var(--bs-breadcrumb-item-padding-x); +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); + content: var(--bs-breadcrumb-divider, "/"); +} +.breadcrumb-item.active { + color: var(--bs-breadcrumb-item-active-color); +} +.pagination { + --bs-pagination-padding-x: 0.75rem; + --bs-pagination-padding-y: 0.375rem; + --bs-pagination-font-size: 1rem; + --bs-pagination-color: var(--bs-link-color); + --bs-pagination-bg: var(--bs-body-bg); + --bs-pagination-border-width: var(--bs-border-width); + --bs-pagination-border-color: var(--bs-border-color); + --bs-pagination-border-radius: var(--bs-border-radius); + --bs-pagination-hover-color: var(--bs-link-hover-color); + --bs-pagination-hover-bg: var(--bs-tertiary-bg); + --bs-pagination-hover-border-color: var(--bs-border-color); + --bs-pagination-focus-color: var(--bs-link-hover-color); + --bs-pagination-focus-bg: var(--bs-secondary-bg); + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #0d6efd; + --bs-pagination-active-border-color: #0d6efd; + --bs-pagination-disabled-color: var(--bs-secondary-color); + --bs-pagination-disabled-bg: var(--bs-secondary-bg); + --bs-pagination-disabled-border-color: var(--bs-border-color); + display: flex; + padding-left: 0; + list-style: none; +} +.page-link { + position: relative; + display: block; + padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); + text-decoration: none; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid + var(--bs-pagination-border-color); + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .page-link { + transition: none; + } +} +.page-link:hover { + z-index: 2; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color); +} +.page-link:focus { + z-index: 3; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); + outline: 0; + box-shadow: var(--bs-pagination-focus-box-shadow); +} +.active > .page-link, +.page-link.active { + z-index: 3; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color); +} +.disabled > .page-link, +.page-link.disabled { + color: var(--bs-pagination-disabled-color); + pointer-events: none; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color); +} +.page-item:not(:first-child) .page-link { + margin-left: calc(var(--bs-border-width) * -1); +} +.page-item:first-child .page-link { + border-top-left-radius: var(--bs-pagination-border-radius); + border-bottom-left-radius: var(--bs-pagination-border-radius); +} +.page-item:last-child .page-link { + border-top-right-radius: var(--bs-pagination-border-radius); + border-bottom-right-radius: var(--bs-pagination-border-radius); +} +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 1.25rem; + --bs-pagination-border-radius: var(--bs-border-radius-lg); +} +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: var(--bs-border-radius-sm); +} +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: var(--bs-badge-border-radius); +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color); + --bs-alert-border-radius: var(--bs-border-radius); + --bs-alert-link-color: inherit; + position: relative; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + border-radius: var(--bs-alert-border-radius); +} +.alert-heading { + color: inherit; +} +.alert-link { + font-weight: 700; + color: var(--bs-alert-link-color); +} +.alert-dismissible { + padding-right: 3rem; +} +.alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 1.25rem 1rem; +} +.alert-primary { + --bs-alert-color: var(--bs-primary-text-emphasis); + --bs-alert-bg: var(--bs-primary-bg-subtle); + --bs-alert-border-color: var(--bs-primary-border-subtle); + --bs-alert-link-color: var(--bs-primary-text-emphasis); +} +.alert-secondary { + --bs-alert-color: var(--bs-secondary-text-emphasis); + --bs-alert-bg: var(--bs-secondary-bg-subtle); + --bs-alert-border-color: var(--bs-secondary-border-subtle); + --bs-alert-link-color: var(--bs-secondary-text-emphasis); +} +.alert-success { + --bs-alert-color: var(--bs-success-text-emphasis); + --bs-alert-bg: var(--bs-success-bg-subtle); + --bs-alert-border-color: var(--bs-success-border-subtle); + --bs-alert-link-color: var(--bs-success-text-emphasis); +} +.alert-info { + --bs-alert-color: var(--bs-info-text-emphasis); + --bs-alert-bg: var(--bs-info-bg-subtle); + --bs-alert-border-color: var(--bs-info-border-subtle); + --bs-alert-link-color: var(--bs-info-text-emphasis); +} +.alert-warning { + --bs-alert-color: var(--bs-warning-text-emphasis); + --bs-alert-bg: var(--bs-warning-bg-subtle); + --bs-alert-border-color: var(--bs-warning-border-subtle); + --bs-alert-link-color: var(--bs-warning-text-emphasis); +} +.alert-danger { + --bs-alert-color: var(--bs-danger-text-emphasis); + --bs-alert-bg: var(--bs-danger-bg-subtle); + --bs-alert-border-color: var(--bs-danger-border-subtle); + --bs-alert-link-color: var(--bs-danger-text-emphasis); +} +.alert-light { + --bs-alert-color: var(--bs-light-text-emphasis); + --bs-alert-bg: var(--bs-light-bg-subtle); + --bs-alert-border-color: var(--bs-light-border-subtle); + --bs-alert-link-color: var(--bs-light-text-emphasis); +} +.alert-dark { + --bs-alert-color: var(--bs-dark-text-emphasis); + --bs-alert-bg: var(--bs-dark-bg-subtle); + --bs-alert-border-color: var(--bs-dark-border-subtle); + --bs-alert-link-color: var(--bs-dark-text-emphasis); +} +@keyframes progress-bar-stripes { + 0% { + background-position-x: 1rem; + } +} +.progress, +.progress-stacked { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.75rem; + --bs-progress-bg: var(--bs-secondary-bg); + --bs-progress-border-radius: var(--bs-border-radius); + --bs-progress-box-shadow: var(--bs-box-shadow-inset); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #0d6efd; + --bs-progress-bar-transition: width 0.6s ease; + display: flex; + height: var(--bs-progress-height); + overflow: hidden; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); + border-radius: var(--bs-progress-border-radius); +} +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--bs-progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-progress-bar-bg); + transition: var(--bs-progress-bar-transition); +} +@media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; + } +} +.progress-bar-striped { + background-image: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.15) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0.15) 75%, + transparent 75%, + transparent + ); + background-size: var(--bs-progress-height) var(--bs-progress-height); +} +.progress-stacked > .progress { + overflow: visible; +} +.progress-stacked > .progress > .progress-bar { + width: 100%; +} +.progress-bar-animated { + animation: 1s linear infinite progress-bar-stripes; +} +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + animation: none; + } +} +.list-group { + --bs-list-group-color: var(--bs-body-color); + --bs-list-group-bg: var(--bs-body-bg); + --bs-list-group-border-color: var(--bs-border-color); + --bs-list-group-border-width: var(--bs-border-width); + --bs-list-group-border-radius: var(--bs-border-radius); + --bs-list-group-item-padding-x: 1rem; + --bs-list-group-item-padding-y: 0.5rem; + --bs-list-group-action-color: var(--bs-secondary-color); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-tertiary-bg); + --bs-list-group-action-active-color: var(--bs-body-color); + --bs-list-group-action-active-bg: var(--bs-secondary-bg); + --bs-list-group-disabled-color: var(--bs-secondary-color); + --bs-list-group-disabled-bg: var(--bs-body-bg); + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #0d6efd; + --bs-list-group-active-border-color: #0d6efd; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: var(--bs-list-group-border-radius); +} +.list-group-numbered { + list-style-type: none; + counter-reset: section; +} +.list-group-numbered > .list-group-item::before { + content: counters(section, ".") ". "; + counter-increment: section; +} +.list-group-item-action { + width: 100%; + color: var(--bs-list-group-action-color); + text-align: inherit; +} +.list-group-item-action:focus, +.list-group-item-action:hover { + z-index: 1; + color: var(--bs-list-group-action-hover-color); + text-decoration: none; + background-color: var(--bs-list-group-action-hover-bg); +} +.list-group-item-action:active { + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg); +} +.list-group-item { + position: relative; + display: block; + padding: var(--bs-list-group-item-padding-y) + var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); + text-decoration: none; + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid + var(--bs-list-group-border-color); +} +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} +.list-group-item.disabled, +.list-group-item:disabled { + color: var(--bs-list-group-disabled-color); + pointer-events: none; + background-color: var(--bs-list-group-disabled-bg); +} +.list-group-item.active { + z-index: 2; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color); +} +.list-group-item + .list-group-item { + border-top-width: 0; +} +.list-group-item + .list-group-item.active { + margin-top: calc(-1 * var(--bs-list-group-border-width)); + border-top-width: var(--bs-list-group-border-width); +} +.list-group-horizontal { + flex-direction: row; +} +.list-group-horizontal > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; +} +.list-group-horizontal > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; +} +.list-group-horizontal > .list-group-item.active { + margin-top: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); +} +@media (min-width: 576px) { + .list-group-horizontal-sm { + flex-direction: row; + } + .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 768px) { + .list-group-horizontal-md { + flex-direction: row; + } + .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 992px) { + .list-group-horizontal-lg { + flex-direction: row; + } + .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1200px) { + .list-group-horizontal-xl { + flex-direction: row; + } + .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1400px) { + .list-group-horizontal-xxl { + flex-direction: row; + } + .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xxl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +.list-group-flush { + border-radius: 0; +} +.list-group-flush > .list-group-item { + border-width: 0 0 var(--bs-list-group-border-width); +} +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; +} +.list-group-item-primary { + --bs-list-group-color: var(--bs-primary-text-emphasis); + --bs-list-group-bg: var(--bs-primary-bg-subtle); + --bs-list-group-border-color: var(--bs-primary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-primary-border-subtle); + --bs-list-group-active-color: var(--bs-primary-bg-subtle); + --bs-list-group-active-bg: var(--bs-primary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-primary-text-emphasis); +} +.list-group-item-secondary { + --bs-list-group-color: var(--bs-secondary-text-emphasis); + --bs-list-group-bg: var(--bs-secondary-bg-subtle); + --bs-list-group-border-color: var(--bs-secondary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle); + --bs-list-group-active-color: var(--bs-secondary-bg-subtle); + --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis); +} +.list-group-item-success { + --bs-list-group-color: var(--bs-success-text-emphasis); + --bs-list-group-bg: var(--bs-success-bg-subtle); + --bs-list-group-border-color: var(--bs-success-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-success-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-success-border-subtle); + --bs-list-group-active-color: var(--bs-success-bg-subtle); + --bs-list-group-active-bg: var(--bs-success-text-emphasis); + --bs-list-group-active-border-color: var(--bs-success-text-emphasis); +} +.list-group-item-info { + --bs-list-group-color: var(--bs-info-text-emphasis); + --bs-list-group-bg: var(--bs-info-bg-subtle); + --bs-list-group-border-color: var(--bs-info-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-info-border-subtle); + --bs-list-group-active-color: var(--bs-info-bg-subtle); + --bs-list-group-active-bg: var(--bs-info-text-emphasis); + --bs-list-group-active-border-color: var(--bs-info-text-emphasis); +} +.list-group-item-warning { + --bs-list-group-color: var(--bs-warning-text-emphasis); + --bs-list-group-bg: var(--bs-warning-bg-subtle); + --bs-list-group-border-color: var(--bs-warning-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-warning-border-subtle); + --bs-list-group-active-color: var(--bs-warning-bg-subtle); + --bs-list-group-active-bg: var(--bs-warning-text-emphasis); + --bs-list-group-active-border-color: var(--bs-warning-text-emphasis); +} +.list-group-item-danger { + --bs-list-group-color: var(--bs-danger-text-emphasis); + --bs-list-group-bg: var(--bs-danger-bg-subtle); + --bs-list-group-border-color: var(--bs-danger-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-danger-border-subtle); + --bs-list-group-active-color: var(--bs-danger-bg-subtle); + --bs-list-group-active-bg: var(--bs-danger-text-emphasis); + --bs-list-group-active-border-color: var(--bs-danger-text-emphasis); +} +.list-group-item-light { + --bs-list-group-color: var(--bs-light-text-emphasis); + --bs-list-group-bg: var(--bs-light-bg-subtle); + --bs-list-group-border-color: var(--bs-light-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-light-border-subtle); + --bs-list-group-active-color: var(--bs-light-bg-subtle); + --bs-list-group-active-bg: var(--bs-light-text-emphasis); + --bs-list-group-active-border-color: var(--bs-light-text-emphasis); +} +.list-group-item-dark { + --bs-list-group-color: var(--bs-dark-text-emphasis); + --bs-list-group-bg: var(--bs-dark-bg-subtle); + --bs-list-group-border-color: var(--bs-dark-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); + --bs-list-group-active-color: var(--bs-dark-bg-subtle); + --bs-list-group-active-bg: var(--bs-dark-text-emphasis); + --bs-list-group-active-border-color: var(--bs-dark-text-emphasis); +} +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%); + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; + border: 0; + border-radius: 0.375rem; + opacity: var(--bs-btn-close-opacity); +} +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity); +} +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity); +} +.btn-close.disabled, +.btn-close:disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: var(--bs-btn-close-disabled-opacity); +} +.btn-close-white { + filter: var(--bs-btn-close-white-filter); +} +[data-bs-theme="dark"] .btn-close { + filter: var(--bs-btn-close-white-filter); +} +.toast { + --bs-toast-zindex: 1090; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-border-width: var(--bs-border-width); + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: var(--bs-border-radius); + --bs-toast-box-shadow: var(--bs-box-shadow); + --bs-toast-header-color: var(--bs-secondary-color); + --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-border-color: var(--bs-border-color-translucent); + width: var(--bs-toast-max-width); + max-width: 100%; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); + pointer-events: auto; + background-color: var(--bs-toast-bg); + background-clip: padding-box; + border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); + border-radius: var(--bs-toast-border-radius); +} +.toast.showing { + opacity: 0; +} +.toast:not(.show) { + display: none; +} +.toast-container { + --bs-toast-zindex: 1090; + position: absolute; + z-index: var(--bs-toast-zindex); + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none; +} +.toast-container > :not(:last-child) { + margin-bottom: var(--bs-toast-spacing); +} +.toast-header { + display: flex; + align-items: center; + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); + background-clip: padding-box; + border-bottom: var(--bs-toast-border-width) solid + var(--bs-toast-header-border-color); + border-top-left-radius: calc( + var(--bs-toast-border-radius) - var(--bs-toast-border-width) + ); + border-top-right-radius: calc( + var(--bs-toast-border-radius) - var(--bs-toast-border-width) + ); +} +.toast-header .btn-close { + margin-right: calc(-0.5 * var(--bs-toast-padding-x)); + margin-left: var(--bs-toast-padding-x); +} +.toast-body { + padding: var(--bs-toast-padding-x); + word-wrap: break-word; +} +.modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: ; + --bs-modal-bg: var(--bs-body-bg); + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: var(--bs-border-width); + --bs-modal-border-radius: var(--bs-border-radius-lg); + --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-modal-inner-border-radius: calc( + var(--bs-border-radius-lg) - (var(--bs-border-width)) + ); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: var(--bs-border-width); + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: var(--bs-border-width); + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; +} +.modal-dialog { + position: relative; + width: auto; + margin: var(--bs-modal-margin); + pointer-events: none; +} +.modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -50px); +} +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; + } +} +.modal.show .modal-dialog { + transform: none; +} +.modal.modal-static .modal-dialog { + transform: scale(1.02); +} +.modal-dialog-scrollable { + height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden; +} +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + color: var(--bs-modal-color); + pointer-events: auto; + background-color: var(--bs-modal-bg); + background-clip: padding-box; + border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); + border-radius: var(--bs-modal-border-radius); + outline: 0; +} +.modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-backdrop-zindex); + width: 100vw; + height: 100vh; + background-color: var(--bs-backdrop-bg); +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: var(--bs-backdrop-opacity); +} +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid + var(--bs-modal-header-border-color); + border-top-left-radius: var(--bs-modal-inner-border-radius); + border-top-right-radius: var(--bs-modal-inner-border-radius); +} +.modal-header .btn-close { + padding: calc(var(--bs-modal-header-padding-y) * 0.5) + calc(var(--bs-modal-header-padding-x) * 0.5); + margin: calc(-0.5 * var(--bs-modal-header-padding-y)) + calc(-0.5 * var(--bs-modal-header-padding-x)) + calc(-0.5 * var(--bs-modal-header-padding-y)) auto; +} +.modal-title { + margin-bottom: 0; + line-height: var(--bs-modal-title-line-height); +} +.modal-body { + position: relative; + flex: 1 1 auto; + padding: var(--bs-modal-padding); +} +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid + var(--bs-modal-footer-border-color); + border-bottom-right-radius: var(--bs-modal-inner-border-radius); + border-bottom-left-radius: var(--bs-modal-inner-border-radius); +} +.modal-footer > * { + margin: calc(var(--bs-modal-footer-gap) * 0.5); +} +@media (min-width: 576px) { + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + } + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto; + } + .modal-sm { + --bs-modal-width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + --bs-modal-width: 800px; + } +} +@media (min-width: 1200px) { + .modal-xl { + --bs-modal-width: 1140px; + } +} +.modal-fullscreen { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; +} +.modal-fullscreen .modal-content { + height: 100%; + border: 0; + border-radius: 0; +} +.modal-fullscreen .modal-footer, +.modal-fullscreen .modal-header { + border-radius: 0; +} +.modal-fullscreen .modal-body { + overflow-y: auto; +} +@media (max-width: 575.98px) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-footer, + .modal-fullscreen-sm-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 767.98px) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-md-down .modal-footer, + .modal-fullscreen-md-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 991.98px) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-footer, + .modal-fullscreen-lg-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1199.98px) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-footer, + .modal-fullscreen-xl-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1399.98px) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-footer, + .modal-fullscreen-xxl-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; + } +} +.tooltip { + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.875rem; + --bs-tooltip-color: var(--bs-body-bg); + --bs-tooltip-bg: var(--bs-emphasis-color); + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); + display: block; + margin: var(--bs-tooltip-margin); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-tooltip-font-size); + word-wrap: break-word; + opacity: 0; +} +.tooltip.show { + opacity: var(--bs-tooltip-opacity); +} +.tooltip .tooltip-arrow { + display: block; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height); +} +.tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} +.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow, +.bs-tooltip-top .tooltip-arrow { + bottom: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before, +.bs-tooltip-top .tooltip-arrow::before { + top: -1px; + border-width: var(--bs-tooltip-arrow-height) + calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-top-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow, +.bs-tooltip-end .tooltip-arrow { + left: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before, +.bs-tooltip-end .tooltip-arrow::before { + right: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) + var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-right-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow, +.bs-tooltip-bottom .tooltip-arrow { + top: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before, +.bs-tooltip-bottom .tooltip-arrow::before { + bottom: -1px; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) + var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow, +.bs-tooltip-start .tooltip-arrow { + right: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before, +.bs-tooltip-start .tooltip-arrow::before { + left: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 + calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg); +} +.tooltip-inner { + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); + text-align: center; + background-color: var(--bs-tooltip-bg); + border-radius: var(--bs-tooltip-border-radius); +} +.popover { + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.875rem; + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-border-width: var(--bs-border-width); + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: var(--bs-border-radius-lg); + --bs-popover-inner-border-radius: calc( + var(--bs-border-radius-lg) - var(--bs-border-width) + ); + --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 1rem; + --bs-popover-header-color: inherit; + --bs-popover-header-bg: var(--bs-secondary-bg); + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); + display: block; + max-width: var(--bs-popover-max-width); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-popover-font-size); + word-wrap: break-word; + background-color: var(--bs-popover-bg); + background-clip: padding-box; + border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-radius: var(--bs-popover-border-radius); +} +.popover .popover-arrow { + display: block; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height); +} +.popover .popover-arrow::after, +.popover .popover-arrow::before { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; +} +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow, +.bs-popover-top > .popover-arrow { + bottom: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); +} +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::before, +.bs-popover-top > .popover-arrow::after, +.bs-popover-top > .popover-arrow::before { + border-width: var(--bs-popover-arrow-height) + calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::before, +.bs-popover-top > .popover-arrow::before { + bottom: 0; + border-top-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::after, +.bs-popover-top > .popover-arrow::after { + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow, +.bs-popover-end > .popover-arrow { + left: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::before, +.bs-popover-end > .popover-arrow::after, +.bs-popover-end > .popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) + var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::before, +.bs-popover-end > .popover-arrow::before { + left: 0; + border-right-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::after, +.bs-popover-end > .popover-arrow::after { + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow, +.bs-popover-bottom > .popover-arrow { + top: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); +} +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::before, +.bs-popover-bottom > .popover-arrow::after, +.bs-popover-bottom > .popover-arrow::before { + border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) + var(--bs-popover-arrow-height); +} +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::before, +.bs-popover-bottom > .popover-arrow::before { + top: 0; + border-bottom-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::after, +.bs-popover-bottom > .popover-arrow::after { + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="bottom"] .popover-header::before, +.bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--bs-popover-arrow-width); + margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); + content: ""; + border-bottom: var(--bs-popover-border-width) solid + var(--bs-popover-header-bg); +} +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow, +.bs-popover-start > .popover-arrow { + right: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::before, +.bs-popover-start > .popover-arrow::after, +.bs-popover-start > .popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 + calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); +} +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::before, +.bs-popover-start > .popover-arrow::before { + right: 0; + border-left-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::after, +.bs-popover-start > .popover-arrow::after { + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg); +} +.popover-header { + padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); + margin-bottom: 0; + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid + var(--bs-popover-border-color); + border-top-left-radius: var(--bs-popover-inner-border-radius); + border-top-right-radius: var(--bs-popover-inner-border-radius); +} +.popover-header:empty { + display: none; +} +.popover-body { + padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color); +} +.carousel { + position: relative; +} +.carousel.pointer-event { + touch-action: pan-y; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner::after { + display: block; + clear: both; + content: ""; +} +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: transform 0.6s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; + } +} +.carousel-item-next, +.carousel-item-prev, +.carousel-item.active { + display: block; +} +.active.carousel-item-end, +.carousel-item-next:not(.carousel-item-start) { + transform: translateX(100%); +} +.active.carousel-item-start, +.carousel-item-prev:not(.carousel-item-end) { + transform: translateX(-100%); +} +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; +} +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end, +.carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1; +} +.carousel-fade .active.carousel-item-end, +.carousel-fade .active.carousel-item-start { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-end, + .carousel-fade .active.carousel-item-start { + transition: none; + } +} +.carousel-control-next, +.carousel-control-prev { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: 0 0; + border: 0; + opacity: 0.5; + transition: opacity 0.15s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-control-next, + .carousel-control-prev { + transition: none; + } +} +.carousel-control-next:focus, +.carousel-control-next:hover, +.carousel-control-prev:focus, +.carousel-control-prev:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} +.carousel-control-prev { + left: 0; +} +.carousel-control-next { + right: 0; +} +.carousel-control-next-icon, +.carousel-control-prev-icon { + display: inline-block; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e"); +} +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15%; +} +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; + transition: opacity 0.6s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-indicators [data-bs-target] { + transition: none; + } +} +.carousel-indicators .active { + opacity: 1; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 1.25rem; + left: 15%; + padding-top: 1.25rem; + padding-bottom: 1.25rem; + color: #fff; + text-align: center; +} +.carousel-dark .carousel-control-next-icon, +.carousel-dark .carousel-control-prev-icon { + filter: invert(1) grayscale(100); +} +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; +} +.carousel-dark .carousel-caption { + color: #000; +} +[data-bs-theme="dark"] .carousel .carousel-control-next-icon, +[data-bs-theme="dark"] .carousel .carousel-control-prev-icon, +[data-bs-theme="dark"].carousel .carousel-control-next-icon, +[data-bs-theme="dark"].carousel .carousel-control-prev-icon { + filter: invert(1) grayscale(100); +} +[data-bs-theme="dark"] .carousel .carousel-indicators [data-bs-target], +[data-bs-theme="dark"].carousel .carousel-indicators [data-bs-target] { + background-color: #000; +} +[data-bs-theme="dark"] .carousel .carousel-caption, +[data-bs-theme="dark"].carousel .carousel-caption { + color: #000; +} +.spinner-border, +.spinner-grow { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + animation: var(--bs-spinner-animation-speed) linear infinite + var(--bs-spinner-animation-name); +} +@keyframes spinner-border { + to { + transform: rotate(360deg); + } +} +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent; +} +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em; +} +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0; +} +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; +} +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + --bs-spinner-animation-speed: 1.5s; + } +} +.offcanvas, +.offcanvas-lg, +.offcanvas-md, +.offcanvas-sm, +.offcanvas-xl, +.offcanvas-xxl { + --bs-offcanvas-zindex: 1045; + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: var(--bs-body-color); + --bs-offcanvas-bg: var(--bs-body-bg); + --bs-offcanvas-border-width: var(--bs-border-width); + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-offcanvas-transition: transform 0.3s ease-in-out; + --bs-offcanvas-title-line-height: 1.5; +} +@media (max-width: 575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-sm { + transition: none; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-sm.show:not(.hiding), + .offcanvas-sm.showing { + transform: none; + } + .offcanvas-sm.hiding, + .offcanvas-sm.show, + .offcanvas-sm.showing { + visibility: visible; + } +} +@media (min-width: 576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-sm .offcanvas-header { + display: none; + } + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-md { + transition: none; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-md.show:not(.hiding), + .offcanvas-md.showing { + transform: none; + } + .offcanvas-md.hiding, + .offcanvas-md.show, + .offcanvas-md.showing { + visibility: visible; + } +} +@media (min-width: 768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-md .offcanvas-header { + display: none; + } + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-lg { + transition: none; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-lg.show:not(.hiding), + .offcanvas-lg.showing { + transform: none; + } + .offcanvas-lg.hiding, + .offcanvas-lg.show, + .offcanvas-lg.showing { + visibility: visible; + } +} +@media (min-width: 992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-lg .offcanvas-header { + display: none; + } + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xl { + transition: none; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xl.show:not(.hiding), + .offcanvas-xl.showing { + transform: none; + } + .offcanvas-xl.hiding, + .offcanvas-xl.show, + .offcanvas-xl.showing { + visibility: visible; + } +} +@media (min-width: 1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xl .offcanvas-header { + display: none; + } + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xxl { + transition: none; + } +} +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xxl.show:not(.hiding), + .offcanvas-xxl.showing { + transform: none; + } + .offcanvas-xxl.hiding, + .offcanvas-xxl.show, + .offcanvas-xxl.showing { + visibility: visible; + } +} +@media (min-width: 1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xxl .offcanvas-header { + display: none; + } + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +.offcanvas { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); +} +@media (prefers-reduced-motion: reduce) { + .offcanvas { + transition: none; + } +} +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); +} +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); +} +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); +} +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); +} +.offcanvas.show:not(.hiding), +.offcanvas.showing { + transform: none; +} +.offcanvas.hiding, +.offcanvas.show, +.offcanvas.showing { + visibility: visible; +} +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} +.offcanvas-backdrop.fade { + opacity: 0; +} +.offcanvas-backdrop.show { + opacity: 0.5; +} +.offcanvas-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); +} +.offcanvas-header .btn-close { + padding: calc(var(--bs-offcanvas-padding-y) * 0.5) + calc(var(--bs-offcanvas-padding-x) * 0.5); + margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y)); + margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x)); + margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y)); +} +.offcanvas-title { + margin-bottom: 0; + line-height: var(--bs-offcanvas-title-line-height); +} +.offcanvas-body { + flex-grow: 1; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); + overflow-y: auto; +} +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: 0.5; +} +.placeholder.btn::before { + display: inline-block; + content: ""; +} +.placeholder-xs { + min-height: 0.6em; +} +.placeholder-sm { + min-height: 0.8em; +} +.placeholder-lg { + min-height: 1.2em; +} +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite; +} +@keyframes placeholder-glow { + 50% { + opacity: 0.2; + } +} +.placeholder-wave { + -webkit-mask-image: linear-gradient( + 130deg, + #000 55%, + rgba(0, 0, 0, 0.8) 75%, + #000 95% + ); + mask-image: linear-gradient( + 130deg, + #000 55%, + rgba(0, 0, 0, 0.8) 75%, + #000 95% + ); + -webkit-mask-size: 200% 100%; + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite; +} +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; + } +} +.clearfix::after { + display: block; + clear: both; + content: ""; +} +.text-bg-primary { + color: #fff !important; + background-color: RGBA( + var(--bs-primary-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-secondary { + color: #fff !important; + background-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-success { + color: #fff !important; + background-color: RGBA( + var(--bs-success-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-info { + color: #000 !important; + background-color: RGBA( + var(--bs-info-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-warning { + color: #000 !important; + background-color: RGBA( + var(--bs-warning-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-danger { + color: #fff !important; + background-color: RGBA( + var(--bs-danger-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-light { + color: #000 !important; + background-color: RGBA( + var(--bs-light-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-dark { + color: #fff !important; + background-color: RGBA( + var(--bs-dark-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.link-primary { + color: RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-primary:focus, +.link-primary:hover { + color: RGBA(10, 88, 202, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 10, + 88, + 202, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 10, + 88, + 202, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-secondary { + color: RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-secondary:focus, +.link-secondary:hover { + color: RGBA(86, 94, 100, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 86, + 94, + 100, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 86, + 94, + 100, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-success { + color: RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-success-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-success-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-success:focus, +.link-success:hover { + color: RGBA(20, 108, 67, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 20, + 108, + 67, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 20, + 108, + 67, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-info { + color: RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-info-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-info-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-info:focus, +.link-info:hover { + color: RGBA(61, 213, 243, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 61, + 213, + 243, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 61, + 213, + 243, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-warning { + color: RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-warning:focus, +.link-warning:hover { + color: RGBA(255, 205, 57, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 255, + 205, + 57, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 255, + 205, + 57, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-danger { + color: RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-danger:focus, +.link-danger:hover { + color: RGBA(176, 42, 55, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 176, + 42, + 55, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 176, + 42, + 55, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-light { + color: RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-light-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-light-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-light:focus, +.link-light:hover { + color: RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 249, + 250, + 251, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 249, + 250, + 251, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-dark { + color: RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-dark:focus, +.link-dark:hover { + color: RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 26, + 30, + 33, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 26, + 30, + 33, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-body-emphasis { + color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-body-emphasis:focus, +.link-body-emphasis:hover { + color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-opacity, 0.75) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 0.75) + ) !important; + text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 0.75) + ) !important; +} +.focus-ring:focus { + outline: 0; + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) + var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) + var(--bs-focus-ring-color); +} +.icon-link { + display: inline-flex; + gap: 0.375rem; + align-items: center; + -webkit-text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-opacity, 0.5) + ); + text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-opacity, 0.5) + ); + text-underline-offset: 0.25em; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +.icon-link > .bi { + flex-shrink: 0; + width: 1em; + height: 1em; + fill: currentcolor; + transition: 0.2s ease-in-out transform; +} +@media (prefers-reduced-motion: reduce) { + .icon-link > .bi { + transition: none; + } +} +.icon-link-hover:focus-visible > .bi, +.icon-link-hover:hover > .bi { + transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); +} +.ratio { + position: relative; + width: 100%; +} +.ratio::before { + display: block; + padding-top: var(--bs-aspect-ratio); + content: ""; +} +.ratio > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ratio-1x1 { + --bs-aspect-ratio: 100%; +} +.ratio-4x3 { + --bs-aspect-ratio: 75%; +} +.ratio-16x9 { + --bs-aspect-ratio: 56.25%; +} +.ratio-21x9 { + --bs-aspect-ratio: 42.8571428571%; +} +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} +.sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; +} +.sticky-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; +} +@media (min-width: 576px) { + .sticky-sm-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-sm-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 768px) { + .sticky-md-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-md-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 992px) { + .sticky-lg-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-lg-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1200px) { + .sticky-xl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1400px) { + .sticky-xxl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xxl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; +} +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch; +} +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} +.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption), +.visually-hidden:not(caption) { + position: absolute !important; +} +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + content: ""; +} +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vr { + display: inline-block; + align-self: stretch; + width: var(--bs-border-width); + min-height: 1em; + background-color: currentcolor; + opacity: 0.25; +} +.align-baseline { + vertical-align: baseline !important; +} +.align-top { + vertical-align: top !important; +} +.align-middle { + vertical-align: middle !important; +} +.align-bottom { + vertical-align: bottom !important; +} +.align-text-bottom { + vertical-align: text-bottom !important; +} +.align-text-top { + vertical-align: text-top !important; +} +.float-start { + float: left !important; +} +.float-end { + float: right !important; +} +.float-none { + float: none !important; +} +.object-fit-contain { + -o-object-fit: contain !important; + object-fit: contain !important; +} +.object-fit-cover { + -o-object-fit: cover !important; + object-fit: cover !important; +} +.object-fit-fill { + -o-object-fit: fill !important; + object-fit: fill !important; +} +.object-fit-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; +} +.object-fit-none { + -o-object-fit: none !important; + object-fit: none !important; +} +.opacity-0 { + opacity: 0 !important; +} +.opacity-25 { + opacity: 0.25 !important; +} +.opacity-50 { + opacity: 0.5 !important; +} +.opacity-75 { + opacity: 0.75 !important; +} +.opacity-100 { + opacity: 1 !important; +} +.overflow-auto { + overflow: auto !important; +} +.overflow-hidden { + overflow: hidden !important; +} +.overflow-visible { + overflow: visible !important; +} +.overflow-scroll { + overflow: scroll !important; +} +.overflow-x-auto { + overflow-x: auto !important; +} +.overflow-x-hidden { + overflow-x: hidden !important; +} +.overflow-x-visible { + overflow-x: visible !important; +} +.overflow-x-scroll { + overflow-x: scroll !important; +} +.overflow-y-auto { + overflow-y: auto !important; +} +.overflow-y-hidden { + overflow-y: hidden !important; +} +.overflow-y-visible { + overflow-y: visible !important; +} +.overflow-y-scroll { + overflow-y: scroll !important; +} +.d-inline { + display: inline !important; +} +.d-inline-block { + display: inline-block !important; +} +.d-block { + display: block !important; +} +.d-grid { + display: grid !important; +} +.d-inline-grid { + display: inline-grid !important; +} +.d-table { + display: table !important; +} +.d-table-row { + display: table-row !important; +} +.d-table-cell { + display: table-cell !important; +} +.d-flex { + display: flex !important; +} +.d-inline-flex { + display: inline-flex !important; +} +.d-none { + display: none !important; +} +.shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; +} +.shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; +} +.shadow-none { + box-shadow: none !important; +} +.focus-ring-primary { + --bs-focus-ring-color: rgba( + var(--bs-primary-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-secondary { + --bs-focus-ring-color: rgba( + var(--bs-secondary-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-success { + --bs-focus-ring-color: rgba( + var(--bs-success-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-info { + --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity)); +} +.focus-ring-warning { + --bs-focus-ring-color: rgba( + var(--bs-warning-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-danger { + --bs-focus-ring-color: rgba( + var(--bs-danger-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-light { + --bs-focus-ring-color: rgba( + var(--bs-light-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-dark { + --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity)); +} +.position-static { + position: static !important; +} +.position-relative { + position: relative !important; +} +.position-absolute { + position: absolute !important; +} +.position-fixed { + position: fixed !important; +} +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} +.top-0 { + top: 0 !important; +} +.top-50 { + top: 50% !important; +} +.top-100 { + top: 100% !important; +} +.bottom-0 { + bottom: 0 !important; +} +.bottom-50 { + bottom: 50% !important; +} +.bottom-100 { + bottom: 100% !important; +} +.start-0 { + left: 0 !important; +} +.start-50 { + left: 50% !important; +} +.start-100 { + left: 100% !important; +} +.end-0 { + right: 0 !important; +} +.end-50 { + right: 50% !important; +} +.end-100 { + right: 100% !important; +} +.translate-middle { + transform: translate(-50%, -50%) !important; +} +.translate-middle-x { + transform: translateX(-50%) !important; +} +.translate-middle-y { + transform: translateY(-50%) !important; +} +.border { + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} +.border-0 { + border: 0 !important; +} +.border-top { + border-top: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-top-0 { + border-top: 0 !important; +} +.border-end { + border-right: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-end-0 { + border-right: 0 !important; +} +.border-bottom { + border-bottom: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-bottom-0 { + border-bottom: 0 !important; +} +.border-start { + border-left: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-start-0 { + border-left: 0 !important; +} +.border-primary { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-primary-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-secondary { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-secondary-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-success { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-success-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-info { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important; +} +.border-warning { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-warning-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-danger { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; +} +.border-light { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important; +} +.border-dark { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important; +} +.border-black { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important; +} +.border-white { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important; +} +.border-primary-subtle { + border-color: var(--bs-primary-border-subtle) !important; +} +.border-secondary-subtle { + border-color: var(--bs-secondary-border-subtle) !important; +} +.border-success-subtle { + border-color: var(--bs-success-border-subtle) !important; +} +.border-info-subtle { + border-color: var(--bs-info-border-subtle) !important; +} +.border-warning-subtle { + border-color: var(--bs-warning-border-subtle) !important; +} +.border-danger-subtle { + border-color: var(--bs-danger-border-subtle) !important; +} +.border-light-subtle { + border-color: var(--bs-light-border-subtle) !important; +} +.border-dark-subtle { + border-color: var(--bs-dark-border-subtle) !important; +} +.border-1 { + border-width: 1px !important; +} +.border-2 { + border-width: 2px !important; +} +.border-3 { + border-width: 3px !important; +} +.border-4 { + border-width: 4px !important; +} +.border-5 { + border-width: 5px !important; +} +.border-opacity-10 { + --bs-border-opacity: 0.1; +} +.border-opacity-25 { + --bs-border-opacity: 0.25; +} +.border-opacity-50 { + --bs-border-opacity: 0.5; +} +.border-opacity-75 { + --bs-border-opacity: 0.75; +} +.border-opacity-100 { + --bs-border-opacity: 1; +} +.w-25 { + width: 25% !important; +} +.w-50 { + width: 50% !important; +} +.w-75 { + width: 75% !important; +} +.w-100 { + width: 100% !important; +} +.w-auto { + width: auto !important; +} +.mw-100 { + max-width: 100% !important; +} +.vw-100 { + width: 100vw !important; +} +.min-vw-100 { + min-width: 100vw !important; +} +.h-25 { + height: 25% !important; +} +.h-50 { + height: 50% !important; +} +.h-75 { + height: 75% !important; +} +.h-100 { + height: 100% !important; +} +.h-auto { + height: auto !important; +} +.mh-100 { + max-height: 100% !important; +} +.vh-100 { + height: 100vh !important; +} +.min-vh-100 { + min-height: 100vh !important; +} +.flex-fill { + flex: 1 1 auto !important; +} +.flex-row { + flex-direction: row !important; +} +.flex-column { + flex-direction: column !important; +} +.flex-row-reverse { + flex-direction: row-reverse !important; +} +.flex-column-reverse { + flex-direction: column-reverse !important; +} +.flex-grow-0 { + flex-grow: 0 !important; +} +.flex-grow-1 { + flex-grow: 1 !important; +} +.flex-shrink-0 { + flex-shrink: 0 !important; +} +.flex-shrink-1 { + flex-shrink: 1 !important; +} +.flex-wrap { + flex-wrap: wrap !important; +} +.flex-nowrap { + flex-wrap: nowrap !important; +} +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; +} +.justify-content-start { + justify-content: flex-start !important; +} +.justify-content-end { + justify-content: flex-end !important; +} +.justify-content-center { + justify-content: center !important; +} +.justify-content-between { + justify-content: space-between !important; +} +.justify-content-around { + justify-content: space-around !important; +} +.justify-content-evenly { + justify-content: space-evenly !important; +} +.align-items-start { + align-items: flex-start !important; +} +.align-items-end { + align-items: flex-end !important; +} +.align-items-center { + align-items: center !important; +} +.align-items-baseline { + align-items: baseline !important; +} +.align-items-stretch { + align-items: stretch !important; +} +.align-content-start { + align-content: flex-start !important; +} +.align-content-end { + align-content: flex-end !important; +} +.align-content-center { + align-content: center !important; +} +.align-content-between { + align-content: space-between !important; +} +.align-content-around { + align-content: space-around !important; +} +.align-content-stretch { + align-content: stretch !important; +} +.align-self-auto { + align-self: auto !important; +} +.align-self-start { + align-self: flex-start !important; +} +.align-self-end { + align-self: flex-end !important; +} +.align-self-center { + align-self: center !important; +} +.align-self-baseline { + align-self: baseline !important; +} +.align-self-stretch { + align-self: stretch !important; +} +.order-first { + order: -1 !important; +} +.order-0 { + order: 0 !important; +} +.order-1 { + order: 1 !important; +} +.order-2 { + order: 2 !important; +} +.order-3 { + order: 3 !important; +} +.order-4 { + order: 4 !important; +} +.order-5 { + order: 5 !important; +} +.order-last { + order: 6 !important; +} +.m-0 { + margin: 0 !important; +} +.m-1 { + margin: 0.25rem !important; +} +.m-2 { + margin: 0.5rem !important; +} +.m-3 { + margin: 1rem !important; +} +.m-4 { + margin: 1.5rem !important; +} +.m-5 { + margin: 3rem !important; +} +.m-auto { + margin: auto !important; +} +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; +} +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; +} +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; +} +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; +} +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; +} +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; +} +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} +.mt-0 { + margin-top: 0 !important; +} +.mt-1 { + margin-top: 0.25rem !important; +} +.mt-2 { + margin-top: 0.5rem !important; +} +.mt-3 { + margin-top: 1rem !important; +} +.mt-4 { + margin-top: 1.5rem !important; +} +.mt-5 { + margin-top: 3rem !important; +} +.mt-auto { + margin-top: auto !important; +} +.me-0 { + margin-right: 0 !important; +} +.me-1 { + margin-right: 0.25rem !important; +} +.me-2 { + margin-right: 0.5rem !important; +} +.me-3 { + margin-right: 1rem !important; +} +.me-4 { + margin-right: 1.5rem !important; +} +.me-5 { + margin-right: 3rem !important; +} +.me-auto { + margin-right: auto !important; +} +.mb-0 { + margin-bottom: 0 !important; +} +.mb-1 { + margin-bottom: 0.25rem !important; +} +.mb-2 { + margin-bottom: 0.5rem !important; +} +.mb-3 { + margin-bottom: 1rem !important; +} +.mb-4 { + margin-bottom: 1.5rem !important; +} +.mb-5 { + margin-bottom: 3rem !important; +} +.mb-auto { + margin-bottom: auto !important; +} +.ms-0 { + margin-left: 0 !important; +} +.ms-1 { + margin-left: 0.25rem !important; +} +.ms-2 { + margin-left: 0.5rem !important; +} +.ms-3 { + margin-left: 1rem !important; +} +.ms-4 { + margin-left: 1.5rem !important; +} +.ms-5 { + margin-left: 3rem !important; +} +.ms-auto { + margin-left: auto !important; +} +.p-0 { + padding: 0 !important; +} +.p-1 { + padding: 0.25rem !important; +} +.p-2 { + padding: 0.5rem !important; +} +.p-3 { + padding: 1rem !important; +} +.p-4 { + padding: 1.5rem !important; +} +.p-5 { + padding: 3rem !important; +} +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; +} +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; +} +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; +} +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; +} +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; +} +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} +.pt-0 { + padding-top: 0 !important; +} +.pt-1 { + padding-top: 0.25rem !important; +} +.pt-2 { + padding-top: 0.5rem !important; +} +.pt-3 { + padding-top: 1rem !important; +} +.pt-4 { + padding-top: 1.5rem !important; +} +.pt-5 { + padding-top: 3rem !important; +} +.pe-0 { + padding-right: 0 !important; +} +.pe-1 { + padding-right: 0.25rem !important; +} +.pe-2 { + padding-right: 0.5rem !important; +} +.pe-3 { + padding-right: 1rem !important; +} +.pe-4 { + padding-right: 1.5rem !important; +} +.pe-5 { + padding-right: 3rem !important; +} +.pb-0 { + padding-bottom: 0 !important; +} +.pb-1 { + padding-bottom: 0.25rem !important; +} +.pb-2 { + padding-bottom: 0.5rem !important; +} +.pb-3 { + padding-bottom: 1rem !important; +} +.pb-4 { + padding-bottom: 1.5rem !important; +} +.pb-5 { + padding-bottom: 3rem !important; +} +.ps-0 { + padding-left: 0 !important; +} +.ps-1 { + padding-left: 0.25rem !important; +} +.ps-2 { + padding-left: 0.5rem !important; +} +.ps-3 { + padding-left: 1rem !important; +} +.ps-4 { + padding-left: 1.5rem !important; +} +.ps-5 { + padding-left: 3rem !important; +} +.gap-0 { + gap: 0 !important; +} +.gap-1 { + gap: 0.25rem !important; +} +.gap-2 { + gap: 0.5rem !important; +} +.gap-3 { + gap: 1rem !important; +} +.gap-4 { + gap: 1.5rem !important; +} +.gap-5 { + gap: 3rem !important; +} +.row-gap-0 { + row-gap: 0 !important; +} +.row-gap-1 { + row-gap: 0.25rem !important; +} +.row-gap-2 { + row-gap: 0.5rem !important; +} +.row-gap-3 { + row-gap: 1rem !important; +} +.row-gap-4 { + row-gap: 1.5rem !important; +} +.row-gap-5 { + row-gap: 3rem !important; +} +.column-gap-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; +} +.column-gap-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; +} +.column-gap-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; +} +.column-gap-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; +} +.column-gap-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; +} +.column-gap-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; +} +.font-monospace { + font-family: var(--bs-font-monospace) !important; +} +.fs-1 { + font-size: calc(1.375rem + 1.5vw) !important; +} +.fs-2 { + font-size: calc(1.325rem + 0.9vw) !important; +} +.fs-3 { + font-size: calc(1.3rem + 0.6vw) !important; +} +.fs-4 { + font-size: calc(1.275rem + 0.3vw) !important; +} +.fs-5 { + font-size: 1.25rem !important; +} +.fs-6 { + font-size: 1rem !important; +} +.fst-italic { + font-style: italic !important; +} +.fst-normal { + font-style: normal !important; +} +.fw-lighter { + font-weight: lighter !important; +} +.fw-light { + font-weight: 300 !important; +} +.fw-normal { + font-weight: 400 !important; +} +.fw-medium { + font-weight: 500 !important; +} +.fw-semibold { + font-weight: 600 !important; +} +.fw-bold { + font-weight: 700 !important; +} +.fw-bolder { + font-weight: bolder !important; +} +.lh-1 { + line-height: 1 !important; +} +.lh-sm { + line-height: 1.25 !important; +} +.lh-base { + line-height: 1.5 !important; +} +.lh-lg { + line-height: 2 !important; +} +.text-start { + text-align: left !important; +} +.text-end { + text-align: right !important; +} +.text-center { + text-align: center !important; +} +.text-decoration-none { + text-decoration: none !important; +} +.text-decoration-underline { + text-decoration: underline !important; +} +.text-decoration-line-through { + text-decoration: line-through !important; +} +.text-lowercase { + text-transform: lowercase !important; +} +.text-uppercase { + text-transform: uppercase !important; +} +.text-capitalize { + text-transform: capitalize !important; +} +.text-wrap { + white-space: normal !important; +} +.text-nowrap { + white-space: nowrap !important; +} +.text-break { + word-wrap: break-word !important; + word-break: break-word !important; +} +.text-primary { + --bs-text-opacity: 1; + color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important; +} +.text-secondary { + --bs-text-opacity: 1; + color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important; +} +.text-success { + --bs-text-opacity: 1; + color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important; +} +.text-info { + --bs-text-opacity: 1; + color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important; +} +.text-warning { + --bs-text-opacity: 1; + color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important; +} +.text-danger { + --bs-text-opacity: 1; + color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important; +} +.text-light { + --bs-text-opacity: 1; + color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important; +} +.text-dark { + --bs-text-opacity: 1; + color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important; +} +.text-black { + --bs-text-opacity: 1; + color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important; +} +.text-white { + --bs-text-opacity: 1; + color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important; +} +.text-body { + --bs-text-opacity: 1; + color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important; +} +.text-muted { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, 0.5) !important; +} +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, 0.5) !important; +} +.text-body-secondary { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} +.text-body-tertiary { + --bs-text-opacity: 1; + color: var(--bs-tertiary-color) !important; +} +.text-body-emphasis { + --bs-text-opacity: 1; + color: var(--bs-emphasis-color) !important; +} +.text-reset { + --bs-text-opacity: 1; + color: inherit !important; +} +.text-opacity-25 { + --bs-text-opacity: 0.25; +} +.text-opacity-50 { + --bs-text-opacity: 0.5; +} +.text-opacity-75 { + --bs-text-opacity: 0.75; +} +.text-opacity-100 { + --bs-text-opacity: 1; +} +.text-primary-emphasis { + color: var(--bs-primary-text-emphasis) !important; +} +.text-secondary-emphasis { + color: var(--bs-secondary-text-emphasis) !important; +} +.text-success-emphasis { + color: var(--bs-success-text-emphasis) !important; +} +.text-info-emphasis { + color: var(--bs-info-text-emphasis) !important; +} +.text-warning-emphasis { + color: var(--bs-warning-text-emphasis) !important; +} +.text-danger-emphasis { + color: var(--bs-danger-text-emphasis) !important; +} +.text-light-emphasis { + color: var(--bs-light-text-emphasis) !important; +} +.text-dark-emphasis { + color: var(--bs-dark-text-emphasis) !important; +} +.link-opacity-10 { + --bs-link-opacity: 0.1; +} +.link-opacity-10-hover:hover { + --bs-link-opacity: 0.1; +} +.link-opacity-25 { + --bs-link-opacity: 0.25; +} +.link-opacity-25-hover:hover { + --bs-link-opacity: 0.25; +} +.link-opacity-50 { + --bs-link-opacity: 0.5; +} +.link-opacity-50-hover:hover { + --bs-link-opacity: 0.5; +} +.link-opacity-75 { + --bs-link-opacity: 0.75; +} +.link-opacity-75-hover:hover { + --bs-link-opacity: 0.75; +} +.link-opacity-100 { + --bs-link-opacity: 1; +} +.link-opacity-100-hover:hover { + --bs-link-opacity: 1; +} +.link-offset-1 { + text-underline-offset: 0.125em !important; +} +.link-offset-1-hover:hover { + text-underline-offset: 0.125em !important; +} +.link-offset-2 { + text-underline-offset: 0.25em !important; +} +.link-offset-2-hover:hover { + text-underline-offset: 0.25em !important; +} +.link-offset-3 { + text-underline-offset: 0.375em !important; +} +.link-offset-3-hover:hover { + text-underline-offset: 0.375em !important; +} +.link-underline-primary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-secondary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-success { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-success-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-success-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-info { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-info-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-info-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-warning { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-danger { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-light { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-light-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-light-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-dark { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-underline-opacity-0 { + --bs-link-underline-opacity: 0; +} +.link-underline-opacity-0-hover:hover { + --bs-link-underline-opacity: 0; +} +.link-underline-opacity-10 { + --bs-link-underline-opacity: 0.1; +} +.link-underline-opacity-10-hover:hover { + --bs-link-underline-opacity: 0.1; +} +.link-underline-opacity-25 { + --bs-link-underline-opacity: 0.25; +} +.link-underline-opacity-25-hover:hover { + --bs-link-underline-opacity: 0.25; +} +.link-underline-opacity-50 { + --bs-link-underline-opacity: 0.5; +} +.link-underline-opacity-50-hover:hover { + --bs-link-underline-opacity: 0.5; +} +.link-underline-opacity-75 { + --bs-link-underline-opacity: 0.75; +} +.link-underline-opacity-75-hover:hover { + --bs-link-underline-opacity: 0.75; +} +.link-underline-opacity-100 { + --bs-link-underline-opacity: 1; +} +.link-underline-opacity-100-hover:hover { + --bs-link-underline-opacity: 1; +} +.bg-primary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-primary-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-secondary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-secondary-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-success-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-info { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important; +} +.bg-warning { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-warning-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-danger { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important; +} +.bg-light { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important; +} +.bg-dark { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important; +} +.bg-black { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important; +} +.bg-white { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important; +} +.bg-body { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-body-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-transparent { + --bs-bg-opacity: 1; + background-color: transparent !important; +} +.bg-body-secondary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-secondary-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-body-tertiary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-tertiary-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-opacity-10 { + --bs-bg-opacity: 0.1; +} +.bg-opacity-25 { + --bs-bg-opacity: 0.25; +} +.bg-opacity-50 { + --bs-bg-opacity: 0.5; +} +.bg-opacity-75 { + --bs-bg-opacity: 0.75; +} +.bg-opacity-100 { + --bs-bg-opacity: 1; +} +.bg-primary-subtle { + background-color: var(--bs-primary-bg-subtle) !important; +} +.bg-secondary-subtle { + background-color: var(--bs-secondary-bg-subtle) !important; +} +.bg-success-subtle { + background-color: var(--bs-success-bg-subtle) !important; +} +.bg-info-subtle { + background-color: var(--bs-info-bg-subtle) !important; +} +.bg-warning-subtle { + background-color: var(--bs-warning-bg-subtle) !important; +} +.bg-danger-subtle { + background-color: var(--bs-danger-bg-subtle) !important; +} +.bg-light-subtle { + background-color: var(--bs-light-bg-subtle) !important; +} +.bg-dark-subtle { + background-color: var(--bs-dark-bg-subtle) !important; +} +.bg-gradient { + background-image: var(--bs-gradient) !important; +} +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important; +} +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + user-select: auto !important; +} +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; +} +.pe-none { + pointer-events: none !important; +} +.pe-auto { + pointer-events: auto !important; +} +.rounded { + border-radius: var(--bs-border-radius) !important; +} +.rounded-0 { + border-radius: 0 !important; +} +.rounded-1 { + border-radius: var(--bs-border-radius-sm) !important; +} +.rounded-2 { + border-radius: var(--bs-border-radius) !important; +} +.rounded-3 { + border-radius: var(--bs-border-radius-lg) !important; +} +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important; +} +.rounded-5 { + border-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-circle { + border-radius: 50% !important; +} +.rounded-pill { + border-radius: var(--bs-border-radius-pill) !important; +} +.rounded-top { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} +.rounded-top-0 { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} +.rounded-top-1 { + border-top-left-radius: var(--bs-border-radius-sm) !important; + border-top-right-radius: var(--bs-border-radius-sm) !important; +} +.rounded-top-2 { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} +.rounded-top-3 { + border-top-left-radius: var(--bs-border-radius-lg) !important; + border-top-right-radius: var(--bs-border-radius-lg) !important; +} +.rounded-top-4 { + border-top-left-radius: var(--bs-border-radius-xl) !important; + border-top-right-radius: var(--bs-border-radius-xl) !important; +} +.rounded-top-5 { + border-top-left-radius: var(--bs-border-radius-xxl) !important; + border-top-right-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-top-circle { + border-top-left-radius: 50% !important; + border-top-right-radius: 50% !important; +} +.rounded-top-pill { + border-top-left-radius: var(--bs-border-radius-pill) !important; + border-top-right-radius: var(--bs-border-radius-pill) !important; +} +.rounded-end { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} +.rounded-end-0 { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} +.rounded-end-1 { + border-top-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-right-radius: var(--bs-border-radius-sm) !important; +} +.rounded-end-2 { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} +.rounded-end-3 { + border-top-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-right-radius: var(--bs-border-radius-lg) !important; +} +.rounded-end-4 { + border-top-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-right-radius: var(--bs-border-radius-xl) !important; +} +.rounded-end-5 { + border-top-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-end-circle { + border-top-right-radius: 50% !important; + border-bottom-right-radius: 50% !important; +} +.rounded-end-pill { + border-top-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-right-radius: var(--bs-border-radius-pill) !important; +} +.rounded-bottom { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} +.rounded-bottom-0 { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} +.rounded-bottom-1 { + border-bottom-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-left-radius: var(--bs-border-radius-sm) !important; +} +.rounded-bottom-2 { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} +.rounded-bottom-3 { + border-bottom-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-left-radius: var(--bs-border-radius-lg) !important; +} +.rounded-bottom-4 { + border-bottom-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-left-radius: var(--bs-border-radius-xl) !important; +} +.rounded-bottom-5 { + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-bottom-circle { + border-bottom-right-radius: 50% !important; + border-bottom-left-radius: 50% !important; +} +.rounded-bottom-pill { + border-bottom-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-left-radius: var(--bs-border-radius-pill) !important; +} +.rounded-start { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} +.rounded-start-0 { + border-bottom-left-radius: 0 !important; + border-top-left-radius: 0 !important; +} +.rounded-start-1 { + border-bottom-left-radius: var(--bs-border-radius-sm) !important; + border-top-left-radius: var(--bs-border-radius-sm) !important; +} +.rounded-start-2 { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} +.rounded-start-3 { + border-bottom-left-radius: var(--bs-border-radius-lg) !important; + border-top-left-radius: var(--bs-border-radius-lg) !important; +} +.rounded-start-4 { + border-bottom-left-radius: var(--bs-border-radius-xl) !important; + border-top-left-radius: var(--bs-border-radius-xl) !important; +} +.rounded-start-5 { + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; + border-top-left-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-start-circle { + border-bottom-left-radius: 50% !important; + border-top-left-radius: 50% !important; +} +.rounded-start-pill { + border-bottom-left-radius: var(--bs-border-radius-pill) !important; + border-top-left-radius: var(--bs-border-radius-pill) !important; +} +.visible { + visibility: visible !important; +} +.invisible { + visibility: hidden !important; +} +.z-n1 { + z-index: -1 !important; +} +.z-0 { + z-index: 0 !important; +} +.z-1 { + z-index: 1 !important; +} +.z-2 { + z-index: 2 !important; +} +.z-3 { + z-index: 3 !important; +} +@media (min-width: 576px) { + .float-sm-start { + float: left !important; + } + .float-sm-end { + float: right !important; + } + .float-sm-none { + float: none !important; + } + .object-fit-sm-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-sm-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-sm-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-sm-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-sm-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-grid { + display: grid !important; + } + .d-sm-inline-grid { + display: inline-grid !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: flex !important; + } + .d-sm-inline-flex { + display: inline-flex !important; + } + .d-sm-none { + display: none !important; + } + .flex-sm-fill { + flex: 1 1 auto !important; + } + .flex-sm-row { + flex-direction: row !important; + } + .flex-sm-column { + flex-direction: column !important; + } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; + } + .flex-sm-grow-0 { + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + flex-shrink: 1 !important; + } + .flex-sm-wrap { + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + justify-content: flex-start !important; + } + .justify-content-sm-end { + justify-content: flex-end !important; + } + .justify-content-sm-center { + justify-content: center !important; + } + .justify-content-sm-between { + justify-content: space-between !important; + } + .justify-content-sm-around { + justify-content: space-around !important; + } + .justify-content-sm-evenly { + justify-content: space-evenly !important; + } + .align-items-sm-start { + align-items: flex-start !important; + } + .align-items-sm-end { + align-items: flex-end !important; + } + .align-items-sm-center { + align-items: center !important; + } + .align-items-sm-baseline { + align-items: baseline !important; + } + .align-items-sm-stretch { + align-items: stretch !important; + } + .align-content-sm-start { + align-content: flex-start !important; + } + .align-content-sm-end { + align-content: flex-end !important; + } + .align-content-sm-center { + align-content: center !important; + } + .align-content-sm-between { + align-content: space-between !important; + } + .align-content-sm-around { + align-content: space-around !important; + } + .align-content-sm-stretch { + align-content: stretch !important; + } + .align-self-sm-auto { + align-self: auto !important; + } + .align-self-sm-start { + align-self: flex-start !important; + } + .align-self-sm-end { + align-self: flex-end !important; + } + .align-self-sm-center { + align-self: center !important; + } + .align-self-sm-baseline { + align-self: baseline !important; + } + .align-self-sm-stretch { + align-self: stretch !important; + } + .order-sm-first { + order: -1 !important; + } + .order-sm-0 { + order: 0 !important; + } + .order-sm-1 { + order: 1 !important; + } + .order-sm-2 { + order: 2 !important; + } + .order-sm-3 { + order: 3 !important; + } + .order-sm-4 { + order: 4 !important; + } + .order-sm-5 { + order: 5 !important; + } + .order-sm-last { + order: 6 !important; + } + .m-sm-0 { + margin: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-sm-0 { + margin-top: 0 !important; + } + .mt-sm-1 { + margin-top: 0.25rem !important; + } + .mt-sm-2 { + margin-top: 0.5rem !important; + } + .mt-sm-3 { + margin-top: 1rem !important; + } + .mt-sm-4 { + margin-top: 1.5rem !important; + } + .mt-sm-5 { + margin-top: 3rem !important; + } + .mt-sm-auto { + margin-top: auto !important; + } + .me-sm-0 { + margin-right: 0 !important; + } + .me-sm-1 { + margin-right: 0.25rem !important; + } + .me-sm-2 { + margin-right: 0.5rem !important; + } + .me-sm-3 { + margin-right: 1rem !important; + } + .me-sm-4 { + margin-right: 1.5rem !important; + } + .me-sm-5 { + margin-right: 3rem !important; + } + .me-sm-auto { + margin-right: auto !important; + } + .mb-sm-0 { + margin-bottom: 0 !important; + } + .mb-sm-1 { + margin-bottom: 0.25rem !important; + } + .mb-sm-2 { + margin-bottom: 0.5rem !important; + } + .mb-sm-3 { + margin-bottom: 1rem !important; + } + .mb-sm-4 { + margin-bottom: 1.5rem !important; + } + .mb-sm-5 { + margin-bottom: 3rem !important; + } + .mb-sm-auto { + margin-bottom: auto !important; + } + .ms-sm-0 { + margin-left: 0 !important; + } + .ms-sm-1 { + margin-left: 0.25rem !important; + } + .ms-sm-2 { + margin-left: 0.5rem !important; + } + .ms-sm-3 { + margin-left: 1rem !important; + } + .ms-sm-4 { + margin-left: 1.5rem !important; + } + .ms-sm-5 { + margin-left: 3rem !important; + } + .ms-sm-auto { + margin-left: auto !important; + } + .p-sm-0 { + padding: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-sm-0 { + padding-top: 0 !important; + } + .pt-sm-1 { + padding-top: 0.25rem !important; + } + .pt-sm-2 { + padding-top: 0.5rem !important; + } + .pt-sm-3 { + padding-top: 1rem !important; + } + .pt-sm-4 { + padding-top: 1.5rem !important; + } + .pt-sm-5 { + padding-top: 3rem !important; + } + .pe-sm-0 { + padding-right: 0 !important; + } + .pe-sm-1 { + padding-right: 0.25rem !important; + } + .pe-sm-2 { + padding-right: 0.5rem !important; + } + .pe-sm-3 { + padding-right: 1rem !important; + } + .pe-sm-4 { + padding-right: 1.5rem !important; + } + .pe-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-0 { + padding-bottom: 0 !important; + } + .pb-sm-1 { + padding-bottom: 0.25rem !important; + } + .pb-sm-2 { + padding-bottom: 0.5rem !important; + } + .pb-sm-3 { + padding-bottom: 1rem !important; + } + .pb-sm-4 { + padding-bottom: 1.5rem !important; + } + .pb-sm-5 { + padding-bottom: 3rem !important; + } + .ps-sm-0 { + padding-left: 0 !important; + } + .ps-sm-1 { + padding-left: 0.25rem !important; + } + .ps-sm-2 { + padding-left: 0.5rem !important; + } + .ps-sm-3 { + padding-left: 1rem !important; + } + .ps-sm-4 { + padding-left: 1.5rem !important; + } + .ps-sm-5 { + padding-left: 3rem !important; + } + .gap-sm-0 { + gap: 0 !important; + } + .gap-sm-1 { + gap: 0.25rem !important; + } + .gap-sm-2 { + gap: 0.5rem !important; + } + .gap-sm-3 { + gap: 1rem !important; + } + .gap-sm-4 { + gap: 1.5rem !important; + } + .gap-sm-5 { + gap: 3rem !important; + } + .row-gap-sm-0 { + row-gap: 0 !important; + } + .row-gap-sm-1 { + row-gap: 0.25rem !important; + } + .row-gap-sm-2 { + row-gap: 0.5rem !important; + } + .row-gap-sm-3 { + row-gap: 1rem !important; + } + .row-gap-sm-4 { + row-gap: 1.5rem !important; + } + .row-gap-sm-5 { + row-gap: 3rem !important; + } + .column-gap-sm-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-sm-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-sm-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-sm-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-sm-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-sm-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-sm-start { + text-align: left !important; + } + .text-sm-end { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .float-md-start { + float: left !important; + } + .float-md-end { + float: right !important; + } + .float-md-none { + float: none !important; + } + .object-fit-md-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-md-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-md-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-md-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-md-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-grid { + display: grid !important; + } + .d-md-inline-grid { + display: inline-grid !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: flex !important; + } + .d-md-inline-flex { + display: inline-flex !important; + } + .d-md-none { + display: none !important; + } + .flex-md-fill { + flex: 1 1 auto !important; + } + .flex-md-row { + flex-direction: row !important; + } + .flex-md-column { + flex-direction: column !important; + } + .flex-md-row-reverse { + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + flex-direction: column-reverse !important; + } + .flex-md-grow-0 { + flex-grow: 0 !important; + } + .flex-md-grow-1 { + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + flex-shrink: 1 !important; + } + .flex-md-wrap { + flex-wrap: wrap !important; + } + .flex-md-nowrap { + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + justify-content: flex-start !important; + } + .justify-content-md-end { + justify-content: flex-end !important; + } + .justify-content-md-center { + justify-content: center !important; + } + .justify-content-md-between { + justify-content: space-between !important; + } + .justify-content-md-around { + justify-content: space-around !important; + } + .justify-content-md-evenly { + justify-content: space-evenly !important; + } + .align-items-md-start { + align-items: flex-start !important; + } + .align-items-md-end { + align-items: flex-end !important; + } + .align-items-md-center { + align-items: center !important; + } + .align-items-md-baseline { + align-items: baseline !important; + } + .align-items-md-stretch { + align-items: stretch !important; + } + .align-content-md-start { + align-content: flex-start !important; + } + .align-content-md-end { + align-content: flex-end !important; + } + .align-content-md-center { + align-content: center !important; + } + .align-content-md-between { + align-content: space-between !important; + } + .align-content-md-around { + align-content: space-around !important; + } + .align-content-md-stretch { + align-content: stretch !important; + } + .align-self-md-auto { + align-self: auto !important; + } + .align-self-md-start { + align-self: flex-start !important; + } + .align-self-md-end { + align-self: flex-end !important; + } + .align-self-md-center { + align-self: center !important; + } + .align-self-md-baseline { + align-self: baseline !important; + } + .align-self-md-stretch { + align-self: stretch !important; + } + .order-md-first { + order: -1 !important; + } + .order-md-0 { + order: 0 !important; + } + .order-md-1 { + order: 1 !important; + } + .order-md-2 { + order: 2 !important; + } + .order-md-3 { + order: 3 !important; + } + .order-md-4 { + order: 4 !important; + } + .order-md-5 { + order: 5 !important; + } + .order-md-last { + order: 6 !important; + } + .m-md-0 { + margin: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-md-0 { + margin-top: 0 !important; + } + .mt-md-1 { + margin-top: 0.25rem !important; + } + .mt-md-2 { + margin-top: 0.5rem !important; + } + .mt-md-3 { + margin-top: 1rem !important; + } + .mt-md-4 { + margin-top: 1.5rem !important; + } + .mt-md-5 { + margin-top: 3rem !important; + } + .mt-md-auto { + margin-top: auto !important; + } + .me-md-0 { + margin-right: 0 !important; + } + .me-md-1 { + margin-right: 0.25rem !important; + } + .me-md-2 { + margin-right: 0.5rem !important; + } + .me-md-3 { + margin-right: 1rem !important; + } + .me-md-4 { + margin-right: 1.5rem !important; + } + .me-md-5 { + margin-right: 3rem !important; + } + .me-md-auto { + margin-right: auto !important; + } + .mb-md-0 { + margin-bottom: 0 !important; + } + .mb-md-1 { + margin-bottom: 0.25rem !important; + } + .mb-md-2 { + margin-bottom: 0.5rem !important; + } + .mb-md-3 { + margin-bottom: 1rem !important; + } + .mb-md-4 { + margin-bottom: 1.5rem !important; + } + .mb-md-5 { + margin-bottom: 3rem !important; + } + .mb-md-auto { + margin-bottom: auto !important; + } + .ms-md-0 { + margin-left: 0 !important; + } + .ms-md-1 { + margin-left: 0.25rem !important; + } + .ms-md-2 { + margin-left: 0.5rem !important; + } + .ms-md-3 { + margin-left: 1rem !important; + } + .ms-md-4 { + margin-left: 1.5rem !important; + } + .ms-md-5 { + margin-left: 3rem !important; + } + .ms-md-auto { + margin-left: auto !important; + } + .p-md-0 { + padding: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-md-0 { + padding-top: 0 !important; + } + .pt-md-1 { + padding-top: 0.25rem !important; + } + .pt-md-2 { + padding-top: 0.5rem !important; + } + .pt-md-3 { + padding-top: 1rem !important; + } + .pt-md-4 { + padding-top: 1.5rem !important; + } + .pt-md-5 { + padding-top: 3rem !important; + } + .pe-md-0 { + padding-right: 0 !important; + } + .pe-md-1 { + padding-right: 0.25rem !important; + } + .pe-md-2 { + padding-right: 0.5rem !important; + } + .pe-md-3 { + padding-right: 1rem !important; + } + .pe-md-4 { + padding-right: 1.5rem !important; + } + .pe-md-5 { + padding-right: 3rem !important; + } + .pb-md-0 { + padding-bottom: 0 !important; + } + .pb-md-1 { + padding-bottom: 0.25rem !important; + } + .pb-md-2 { + padding-bottom: 0.5rem !important; + } + .pb-md-3 { + padding-bottom: 1rem !important; + } + .pb-md-4 { + padding-bottom: 1.5rem !important; + } + .pb-md-5 { + padding-bottom: 3rem !important; + } + .ps-md-0 { + padding-left: 0 !important; + } + .ps-md-1 { + padding-left: 0.25rem !important; + } + .ps-md-2 { + padding-left: 0.5rem !important; + } + .ps-md-3 { + padding-left: 1rem !important; + } + .ps-md-4 { + padding-left: 1.5rem !important; + } + .ps-md-5 { + padding-left: 3rem !important; + } + .gap-md-0 { + gap: 0 !important; + } + .gap-md-1 { + gap: 0.25rem !important; + } + .gap-md-2 { + gap: 0.5rem !important; + } + .gap-md-3 { + gap: 1rem !important; + } + .gap-md-4 { + gap: 1.5rem !important; + } + .gap-md-5 { + gap: 3rem !important; + } + .row-gap-md-0 { + row-gap: 0 !important; + } + .row-gap-md-1 { + row-gap: 0.25rem !important; + } + .row-gap-md-2 { + row-gap: 0.5rem !important; + } + .row-gap-md-3 { + row-gap: 1rem !important; + } + .row-gap-md-4 { + row-gap: 1.5rem !important; + } + .row-gap-md-5 { + row-gap: 3rem !important; + } + .column-gap-md-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-md-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-md-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-md-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-md-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-md-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-md-start { + text-align: left !important; + } + .text-md-end { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .float-lg-start { + float: left !important; + } + .float-lg-end { + float: right !important; + } + .float-lg-none { + float: none !important; + } + .object-fit-lg-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-lg-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-lg-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-lg-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-lg-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-grid { + display: grid !important; + } + .d-lg-inline-grid { + display: inline-grid !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: flex !important; + } + .d-lg-inline-flex { + display: inline-flex !important; + } + .d-lg-none { + display: none !important; + } + .flex-lg-fill { + flex: 1 1 auto !important; + } + .flex-lg-row { + flex-direction: row !important; + } + .flex-lg-column { + flex-direction: column !important; + } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; + } + .flex-lg-grow-0 { + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + flex-shrink: 1 !important; + } + .flex-lg-wrap { + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + justify-content: flex-start !important; + } + .justify-content-lg-end { + justify-content: flex-end !important; + } + .justify-content-lg-center { + justify-content: center !important; + } + .justify-content-lg-between { + justify-content: space-between !important; + } + .justify-content-lg-around { + justify-content: space-around !important; + } + .justify-content-lg-evenly { + justify-content: space-evenly !important; + } + .align-items-lg-start { + align-items: flex-start !important; + } + .align-items-lg-end { + align-items: flex-end !important; + } + .align-items-lg-center { + align-items: center !important; + } + .align-items-lg-baseline { + align-items: baseline !important; + } + .align-items-lg-stretch { + align-items: stretch !important; + } + .align-content-lg-start { + align-content: flex-start !important; + } + .align-content-lg-end { + align-content: flex-end !important; + } + .align-content-lg-center { + align-content: center !important; + } + .align-content-lg-between { + align-content: space-between !important; + } + .align-content-lg-around { + align-content: space-around !important; + } + .align-content-lg-stretch { + align-content: stretch !important; + } + .align-self-lg-auto { + align-self: auto !important; + } + .align-self-lg-start { + align-self: flex-start !important; + } + .align-self-lg-end { + align-self: flex-end !important; + } + .align-self-lg-center { + align-self: center !important; + } + .align-self-lg-baseline { + align-self: baseline !important; + } + .align-self-lg-stretch { + align-self: stretch !important; + } + .order-lg-first { + order: -1 !important; + } + .order-lg-0 { + order: 0 !important; + } + .order-lg-1 { + order: 1 !important; + } + .order-lg-2 { + order: 2 !important; + } + .order-lg-3 { + order: 3 !important; + } + .order-lg-4 { + order: 4 !important; + } + .order-lg-5 { + order: 5 !important; + } + .order-lg-last { + order: 6 !important; + } + .m-lg-0 { + margin: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-lg-0 { + margin-top: 0 !important; + } + .mt-lg-1 { + margin-top: 0.25rem !important; + } + .mt-lg-2 { + margin-top: 0.5rem !important; + } + .mt-lg-3 { + margin-top: 1rem !important; + } + .mt-lg-4 { + margin-top: 1.5rem !important; + } + .mt-lg-5 { + margin-top: 3rem !important; + } + .mt-lg-auto { + margin-top: auto !important; + } + .me-lg-0 { + margin-right: 0 !important; + } + .me-lg-1 { + margin-right: 0.25rem !important; + } + .me-lg-2 { + margin-right: 0.5rem !important; + } + .me-lg-3 { + margin-right: 1rem !important; + } + .me-lg-4 { + margin-right: 1.5rem !important; + } + .me-lg-5 { + margin-right: 3rem !important; + } + .me-lg-auto { + margin-right: auto !important; + } + .mb-lg-0 { + margin-bottom: 0 !important; + } + .mb-lg-1 { + margin-bottom: 0.25rem !important; + } + .mb-lg-2 { + margin-bottom: 0.5rem !important; + } + .mb-lg-3 { + margin-bottom: 1rem !important; + } + .mb-lg-4 { + margin-bottom: 1.5rem !important; + } + .mb-lg-5 { + margin-bottom: 3rem !important; + } + .mb-lg-auto { + margin-bottom: auto !important; + } + .ms-lg-0 { + margin-left: 0 !important; + } + .ms-lg-1 { + margin-left: 0.25rem !important; + } + .ms-lg-2 { + margin-left: 0.5rem !important; + } + .ms-lg-3 { + margin-left: 1rem !important; + } + .ms-lg-4 { + margin-left: 1.5rem !important; + } + .ms-lg-5 { + margin-left: 3rem !important; + } + .ms-lg-auto { + margin-left: auto !important; + } + .p-lg-0 { + padding: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-lg-0 { + padding-top: 0 !important; + } + .pt-lg-1 { + padding-top: 0.25rem !important; + } + .pt-lg-2 { + padding-top: 0.5rem !important; + } + .pt-lg-3 { + padding-top: 1rem !important; + } + .pt-lg-4 { + padding-top: 1.5rem !important; + } + .pt-lg-5 { + padding-top: 3rem !important; + } + .pe-lg-0 { + padding-right: 0 !important; + } + .pe-lg-1 { + padding-right: 0.25rem !important; + } + .pe-lg-2 { + padding-right: 0.5rem !important; + } + .pe-lg-3 { + padding-right: 1rem !important; + } + .pe-lg-4 { + padding-right: 1.5rem !important; + } + .pe-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-0 { + padding-bottom: 0 !important; + } + .pb-lg-1 { + padding-bottom: 0.25rem !important; + } + .pb-lg-2 { + padding-bottom: 0.5rem !important; + } + .pb-lg-3 { + padding-bottom: 1rem !important; + } + .pb-lg-4 { + padding-bottom: 1.5rem !important; + } + .pb-lg-5 { + padding-bottom: 3rem !important; + } + .ps-lg-0 { + padding-left: 0 !important; + } + .ps-lg-1 { + padding-left: 0.25rem !important; + } + .ps-lg-2 { + padding-left: 0.5rem !important; + } + .ps-lg-3 { + padding-left: 1rem !important; + } + .ps-lg-4 { + padding-left: 1.5rem !important; + } + .ps-lg-5 { + padding-left: 3rem !important; + } + .gap-lg-0 { + gap: 0 !important; + } + .gap-lg-1 { + gap: 0.25rem !important; + } + .gap-lg-2 { + gap: 0.5rem !important; + } + .gap-lg-3 { + gap: 1rem !important; + } + .gap-lg-4 { + gap: 1.5rem !important; + } + .gap-lg-5 { + gap: 3rem !important; + } + .row-gap-lg-0 { + row-gap: 0 !important; + } + .row-gap-lg-1 { + row-gap: 0.25rem !important; + } + .row-gap-lg-2 { + row-gap: 0.5rem !important; + } + .row-gap-lg-3 { + row-gap: 1rem !important; + } + .row-gap-lg-4 { + row-gap: 1.5rem !important; + } + .row-gap-lg-5 { + row-gap: 3rem !important; + } + .column-gap-lg-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-lg-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-lg-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-lg-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-lg-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-lg-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-lg-start { + text-align: left !important; + } + .text-lg-end { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .float-xl-start { + float: left !important; + } + .float-xl-end { + float: right !important; + } + .float-xl-none { + float: none !important; + } + .object-fit-xl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-grid { + display: grid !important; + } + .d-xl-inline-grid { + display: inline-grid !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: flex !important; + } + .d-xl-inline-flex { + display: inline-flex !important; + } + .d-xl-none { + display: none !important; + } + .flex-xl-fill { + flex: 1 1 auto !important; + } + .flex-xl-row { + flex-direction: row !important; + } + .flex-xl-column { + flex-direction: column !important; + } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xl-grow-0 { + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xl-wrap { + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + justify-content: flex-start !important; + } + .justify-content-xl-end { + justify-content: flex-end !important; + } + .justify-content-xl-center { + justify-content: center !important; + } + .justify-content-xl-between { + justify-content: space-between !important; + } + .justify-content-xl-around { + justify-content: space-around !important; + } + .justify-content-xl-evenly { + justify-content: space-evenly !important; + } + .align-items-xl-start { + align-items: flex-start !important; + } + .align-items-xl-end { + align-items: flex-end !important; + } + .align-items-xl-center { + align-items: center !important; + } + .align-items-xl-baseline { + align-items: baseline !important; + } + .align-items-xl-stretch { + align-items: stretch !important; + } + .align-content-xl-start { + align-content: flex-start !important; + } + .align-content-xl-end { + align-content: flex-end !important; + } + .align-content-xl-center { + align-content: center !important; + } + .align-content-xl-between { + align-content: space-between !important; + } + .align-content-xl-around { + align-content: space-around !important; + } + .align-content-xl-stretch { + align-content: stretch !important; + } + .align-self-xl-auto { + align-self: auto !important; + } + .align-self-xl-start { + align-self: flex-start !important; + } + .align-self-xl-end { + align-self: flex-end !important; + } + .align-self-xl-center { + align-self: center !important; + } + .align-self-xl-baseline { + align-self: baseline !important; + } + .align-self-xl-stretch { + align-self: stretch !important; + } + .order-xl-first { + order: -1 !important; + } + .order-xl-0 { + order: 0 !important; + } + .order-xl-1 { + order: 1 !important; + } + .order-xl-2 { + order: 2 !important; + } + .order-xl-3 { + order: 3 !important; + } + .order-xl-4 { + order: 4 !important; + } + .order-xl-5 { + order: 5 !important; + } + .order-xl-last { + order: 6 !important; + } + .m-xl-0 { + margin: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xl-0 { + margin-top: 0 !important; + } + .mt-xl-1 { + margin-top: 0.25rem !important; + } + .mt-xl-2 { + margin-top: 0.5rem !important; + } + .mt-xl-3 { + margin-top: 1rem !important; + } + .mt-xl-4 { + margin-top: 1.5rem !important; + } + .mt-xl-5 { + margin-top: 3rem !important; + } + .mt-xl-auto { + margin-top: auto !important; + } + .me-xl-0 { + margin-right: 0 !important; + } + .me-xl-1 { + margin-right: 0.25rem !important; + } + .me-xl-2 { + margin-right: 0.5rem !important; + } + .me-xl-3 { + margin-right: 1rem !important; + } + .me-xl-4 { + margin-right: 1.5rem !important; + } + .me-xl-5 { + margin-right: 3rem !important; + } + .me-xl-auto { + margin-right: auto !important; + } + .mb-xl-0 { + margin-bottom: 0 !important; + } + .mb-xl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xl-3 { + margin-bottom: 1rem !important; + } + .mb-xl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xl-5 { + margin-bottom: 3rem !important; + } + .mb-xl-auto { + margin-bottom: auto !important; + } + .ms-xl-0 { + margin-left: 0 !important; + } + .ms-xl-1 { + margin-left: 0.25rem !important; + } + .ms-xl-2 { + margin-left: 0.5rem !important; + } + .ms-xl-3 { + margin-left: 1rem !important; + } + .ms-xl-4 { + margin-left: 1.5rem !important; + } + .ms-xl-5 { + margin-left: 3rem !important; + } + .ms-xl-auto { + margin-left: auto !important; + } + .p-xl-0 { + padding: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xl-0 { + padding-top: 0 !important; + } + .pt-xl-1 { + padding-top: 0.25rem !important; + } + .pt-xl-2 { + padding-top: 0.5rem !important; + } + .pt-xl-3 { + padding-top: 1rem !important; + } + .pt-xl-4 { + padding-top: 1.5rem !important; + } + .pt-xl-5 { + padding-top: 3rem !important; + } + .pe-xl-0 { + padding-right: 0 !important; + } + .pe-xl-1 { + padding-right: 0.25rem !important; + } + .pe-xl-2 { + padding-right: 0.5rem !important; + } + .pe-xl-3 { + padding-right: 1rem !important; + } + .pe-xl-4 { + padding-right: 1.5rem !important; + } + .pe-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-0 { + padding-bottom: 0 !important; + } + .pb-xl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xl-3 { + padding-bottom: 1rem !important; + } + .pb-xl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xl-5 { + padding-bottom: 3rem !important; + } + .ps-xl-0 { + padding-left: 0 !important; + } + .ps-xl-1 { + padding-left: 0.25rem !important; + } + .ps-xl-2 { + padding-left: 0.5rem !important; + } + .ps-xl-3 { + padding-left: 1rem !important; + } + .ps-xl-4 { + padding-left: 1.5rem !important; + } + .ps-xl-5 { + padding-left: 3rem !important; + } + .gap-xl-0 { + gap: 0 !important; + } + .gap-xl-1 { + gap: 0.25rem !important; + } + .gap-xl-2 { + gap: 0.5rem !important; + } + .gap-xl-3 { + gap: 1rem !important; + } + .gap-xl-4 { + gap: 1.5rem !important; + } + .gap-xl-5 { + gap: 3rem !important; + } + .row-gap-xl-0 { + row-gap: 0 !important; + } + .row-gap-xl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xl-3 { + row-gap: 1rem !important; + } + .row-gap-xl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xl-5 { + row-gap: 3rem !important; + } + .column-gap-xl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xl-start { + text-align: left !important; + } + .text-xl-end { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} +@media (min-width: 1400px) { + .float-xxl-start { + float: left !important; + } + .float-xxl-end { + float: right !important; + } + .float-xxl-none { + float: none !important; + } + .object-fit-xxl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xxl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xxl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xxl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xxl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xxl-inline { + display: inline !important; + } + .d-xxl-inline-block { + display: inline-block !important; + } + .d-xxl-block { + display: block !important; + } + .d-xxl-grid { + display: grid !important; + } + .d-xxl-inline-grid { + display: inline-grid !important; + } + .d-xxl-table { + display: table !important; + } + .d-xxl-table-row { + display: table-row !important; + } + .d-xxl-table-cell { + display: table-cell !important; + } + .d-xxl-flex { + display: flex !important; + } + .d-xxl-inline-flex { + display: inline-flex !important; + } + .d-xxl-none { + display: none !important; + } + .flex-xxl-fill { + flex: 1 1 auto !important; + } + .flex-xxl-row { + flex-direction: row !important; + } + .flex-xxl-column { + flex-direction: column !important; + } + .flex-xxl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xxl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xxl-grow-0 { + flex-grow: 0 !important; + } + .flex-xxl-grow-1 { + flex-grow: 1 !important; + } + .flex-xxl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xxl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xxl-wrap { + flex-wrap: wrap !important; + } + .flex-xxl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xxl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xxl-start { + justify-content: flex-start !important; + } + .justify-content-xxl-end { + justify-content: flex-end !important; + } + .justify-content-xxl-center { + justify-content: center !important; + } + .justify-content-xxl-between { + justify-content: space-between !important; + } + .justify-content-xxl-around { + justify-content: space-around !important; + } + .justify-content-xxl-evenly { + justify-content: space-evenly !important; + } + .align-items-xxl-start { + align-items: flex-start !important; + } + .align-items-xxl-end { + align-items: flex-end !important; + } + .align-items-xxl-center { + align-items: center !important; + } + .align-items-xxl-baseline { + align-items: baseline !important; + } + .align-items-xxl-stretch { + align-items: stretch !important; + } + .align-content-xxl-start { + align-content: flex-start !important; + } + .align-content-xxl-end { + align-content: flex-end !important; + } + .align-content-xxl-center { + align-content: center !important; + } + .align-content-xxl-between { + align-content: space-between !important; + } + .align-content-xxl-around { + align-content: space-around !important; + } + .align-content-xxl-stretch { + align-content: stretch !important; + } + .align-self-xxl-auto { + align-self: auto !important; + } + .align-self-xxl-start { + align-self: flex-start !important; + } + .align-self-xxl-end { + align-self: flex-end !important; + } + .align-self-xxl-center { + align-self: center !important; + } + .align-self-xxl-baseline { + align-self: baseline !important; + } + .align-self-xxl-stretch { + align-self: stretch !important; + } + .order-xxl-first { + order: -1 !important; + } + .order-xxl-0 { + order: 0 !important; + } + .order-xxl-1 { + order: 1 !important; + } + .order-xxl-2 { + order: 2 !important; + } + .order-xxl-3 { + order: 3 !important; + } + .order-xxl-4 { + order: 4 !important; + } + .order-xxl-5 { + order: 5 !important; + } + .order-xxl-last { + order: 6 !important; + } + .m-xxl-0 { + margin: 0 !important; + } + .m-xxl-1 { + margin: 0.25rem !important; + } + .m-xxl-2 { + margin: 0.5rem !important; + } + .m-xxl-3 { + margin: 1rem !important; + } + .m-xxl-4 { + margin: 1.5rem !important; + } + .m-xxl-5 { + margin: 3rem !important; + } + .m-xxl-auto { + margin: auto !important; + } + .mx-xxl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xxl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xxl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xxl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xxl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xxl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xxl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xxl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xxl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xxl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xxl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xxl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xxl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xxl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xxl-0 { + margin-top: 0 !important; + } + .mt-xxl-1 { + margin-top: 0.25rem !important; + } + .mt-xxl-2 { + margin-top: 0.5rem !important; + } + .mt-xxl-3 { + margin-top: 1rem !important; + } + .mt-xxl-4 { + margin-top: 1.5rem !important; + } + .mt-xxl-5 { + margin-top: 3rem !important; + } + .mt-xxl-auto { + margin-top: auto !important; + } + .me-xxl-0 { + margin-right: 0 !important; + } + .me-xxl-1 { + margin-right: 0.25rem !important; + } + .me-xxl-2 { + margin-right: 0.5rem !important; + } + .me-xxl-3 { + margin-right: 1rem !important; + } + .me-xxl-4 { + margin-right: 1.5rem !important; + } + .me-xxl-5 { + margin-right: 3rem !important; + } + .me-xxl-auto { + margin-right: auto !important; + } + .mb-xxl-0 { + margin-bottom: 0 !important; + } + .mb-xxl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xxl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xxl-3 { + margin-bottom: 1rem !important; + } + .mb-xxl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xxl-5 { + margin-bottom: 3rem !important; + } + .mb-xxl-auto { + margin-bottom: auto !important; + } + .ms-xxl-0 { + margin-left: 0 !important; + } + .ms-xxl-1 { + margin-left: 0.25rem !important; + } + .ms-xxl-2 { + margin-left: 0.5rem !important; + } + .ms-xxl-3 { + margin-left: 1rem !important; + } + .ms-xxl-4 { + margin-left: 1.5rem !important; + } + .ms-xxl-5 { + margin-left: 3rem !important; + } + .ms-xxl-auto { + margin-left: auto !important; + } + .p-xxl-0 { + padding: 0 !important; + } + .p-xxl-1 { + padding: 0.25rem !important; + } + .p-xxl-2 { + padding: 0.5rem !important; + } + .p-xxl-3 { + padding: 1rem !important; + } + .p-xxl-4 { + padding: 1.5rem !important; + } + .p-xxl-5 { + padding: 3rem !important; + } + .px-xxl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xxl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xxl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xxl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xxl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xxl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xxl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xxl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xxl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xxl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xxl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xxl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xxl-0 { + padding-top: 0 !important; + } + .pt-xxl-1 { + padding-top: 0.25rem !important; + } + .pt-xxl-2 { + padding-top: 0.5rem !important; + } + .pt-xxl-3 { + padding-top: 1rem !important; + } + .pt-xxl-4 { + padding-top: 1.5rem !important; + } + .pt-xxl-5 { + padding-top: 3rem !important; + } + .pe-xxl-0 { + padding-right: 0 !important; + } + .pe-xxl-1 { + padding-right: 0.25rem !important; + } + .pe-xxl-2 { + padding-right: 0.5rem !important; + } + .pe-xxl-3 { + padding-right: 1rem !important; + } + .pe-xxl-4 { + padding-right: 1.5rem !important; + } + .pe-xxl-5 { + padding-right: 3rem !important; + } + .pb-xxl-0 { + padding-bottom: 0 !important; + } + .pb-xxl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xxl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xxl-3 { + padding-bottom: 1rem !important; + } + .pb-xxl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xxl-5 { + padding-bottom: 3rem !important; + } + .ps-xxl-0 { + padding-left: 0 !important; + } + .ps-xxl-1 { + padding-left: 0.25rem !important; + } + .ps-xxl-2 { + padding-left: 0.5rem !important; + } + .ps-xxl-3 { + padding-left: 1rem !important; + } + .ps-xxl-4 { + padding-left: 1.5rem !important; + } + .ps-xxl-5 { + padding-left: 3rem !important; + } + .gap-xxl-0 { + gap: 0 !important; + } + .gap-xxl-1 { + gap: 0.25rem !important; + } + .gap-xxl-2 { + gap: 0.5rem !important; + } + .gap-xxl-3 { + gap: 1rem !important; + } + .gap-xxl-4 { + gap: 1.5rem !important; + } + .gap-xxl-5 { + gap: 3rem !important; + } + .row-gap-xxl-0 { + row-gap: 0 !important; + } + .row-gap-xxl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xxl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xxl-3 { + row-gap: 1rem !important; + } + .row-gap-xxl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xxl-5 { + row-gap: 3rem !important; + } + .column-gap-xxl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xxl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xxl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xxl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xxl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xxl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xxl-start { + text-align: left !important; + } + .text-xxl-end { + text-align: right !important; + } + .text-xxl-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .fs-1 { + font-size: 2.5rem !important; + } + .fs-2 { + font-size: 2rem !important; + } + .fs-3 { + font-size: 1.75rem !important; + } + .fs-4 { + font-size: 1.5rem !important; + } +} +@media print { + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-grid { + display: grid !important; + } + .d-print-inline-grid { + display: inline-grid !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: flex !important; + } + .d-print-inline-flex { + display: inline-flex !important; + } + .d-print-none { + display: none !important; + } +} diff --git a/src/Infrastructure/Services/Reports/carimbo.svg b/src/Infrastructure/Services/Reports/carimbo.svg new file mode 100644 index 00000000..ca6ae929 --- /dev/null +++ b/src/Infrastructure/Services/Reports/carimbo.svg @@ -0,0 +1,360 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Infrastructure/Services/Reports/certificate.html b/src/Infrastructure/Services/Reports/certificate.html new file mode 100644 index 00000000..3c623c35 --- /dev/null +++ b/src/Infrastructure/Services/Reports/certificate.html @@ -0,0 +1,165 @@ + + + + + + + + + + + DECLARAÇÃO DE ATIVIDADES DE ORIENTAÇÃO + + +

+
+ +
+
+ logo-cefet +
+

+ CENTRO FEDERAL DE EDUCAÇÃO TECNOLÓGICA CELSO SUCKOW DA FONSECA - + CEFET/RJ +

+

+ DIRETORIA DE PESQUISA E PÓS-GRADUAÇÃO +

+

+ DEPARTAMENTO DE PESQUISA +

+

+ COORDENADORIA DE PESQUISA E ESTUDOS TECNOLÓGICOS +

+
+
+

DECLARAÇÃO DE ATIVIDADES DE ORIENTAÇÃO

+

+ A Coordenadoria de Pesquisa e Estudos Tecnológicos declara que o + professor/pesquisador abaixo indicado participa do Programa + Institucional de Bolsas de Iniciação Científica orientando os alunos + indicados sob os respectivos Projetos de Pesquisa. +

+
+ + +
+
DADOS DO ORIENTADOR
+
+

Nome:

+

#NOME_ORIENTADOR#

+
+
+

Subárea do Projeto:

+

#SUBAREA_PROJETO#

+
+
+ + +
+
DADOS DOS ALUNOS
+
+
+
+ Nome do Orientado: +

#NOME_ORIENTADO#

+
+
+

Edital de Entrada: #DATA_EDITAL#

+

Bolsita do: #PIBIC_TIPO#

+
+
+

+ Início da Pesquisa neste edital: #INIP_EDITAL# +

+

Término: #FIMP_EDITAL#

+

Situação: #SITP_EDITAL#

+
+
+

+ Título do Projeto de Iniciação Cientifica: + #TITULO_PROJETO_ALUNO# +

+
+
+
+ + +
+
+ Rio de Janeiro, #DIA_SEMANA#, #DIA_DATA# de #MES_DATA# de #ANO_DATA# +
+
+ + +
+
+
+

#NOME_COORDENADOR#

+

Coordenador COPET

+
+
+
+ + +
+ carimbo +
+
+
+ + diff --git a/src/Infrastructure/Services/Reports/logo-cefet.svg b/src/Infrastructure/Services/Reports/logo-cefet.svg new file mode 100644 index 00000000..9618cd71 --- /dev/null +++ b/src/Infrastructure/Services/Reports/logo-cefet.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/src/Infrastructure/Services/Services.csproj b/src/Infrastructure/Services/Services.csproj index 50eee9ea..6e5407d2 100644 --- a/src/Infrastructure/Services/Services.csproj +++ b/src/Infrastructure/Services/Services.csproj @@ -3,15 +3,18 @@ net7.0 enable enable - 0.0.1 + 0.1.0 - + + + + @@ -19,10 +22,13 @@ - + PreserveNewest - + + PreserveNewest + + PreserveNewest diff --git a/src/Infrastructure/Services/StorageFileService.cs b/src/Infrastructure/Services/StorageFileService.cs index 8cb7afb9..a2c90930 100644 --- a/src/Infrastructure/Services/StorageFileService.cs +++ b/src/Infrastructure/Services/StorageFileService.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; -namespace Infrastructure.Services +namespace Services { public class StorageFileService : IStorageFileService { @@ -21,21 +21,26 @@ public StorageFileService(IConfiguration configuration) ?? throw new Exception("O diretório de armazenamento de arquivos não foi configurado."); // Verifica se as extensões de arquivos permitidas foram configuradas - var allowedExtensions = configuration.GetSection("StorageFile:AllowedExtensions") + IConfigurationSection allowedExtensions = configuration.GetSection("StorageFile:AllowedExtensions") ?? throw new Exception("As extensões de arquivos permitidas não foram configuradas."); _allowedExtensions = allowedExtensions.GetChildren().Select(x => x.Value).ToArray(); // Verifica se o tamanho máximo de arquivo foi configurado - if (long.TryParse(configuration["StorageFile:MaxFileSizeInBytes"], out long maxFileSizeInBytes)) - _maxFileSizeInBytes = maxFileSizeInBytes; - else - throw new Exception("O tamanho máximo de arquivo não foi configurado."); + _maxFileSizeInBytes = long.TryParse(configuration["StorageFile:MaxFileSizeInBytes"], out long maxFileSizeInBytes) + ? maxFileSizeInBytes + : throw new Exception("O tamanho máximo de arquivo não foi configurado."); // Verifica se o diretório de armazenamento de arquivos dos editais foi configurado _folder = configuration["StorageFile:Folder"] ?? throw new Exception("O diretório de armazenamento de arquivos não foi configurado."); + + // Cria diretório de arquivos caso não exista + if (!Directory.Exists(_directory)) + { + _ = Directory.CreateDirectory(_directory); + } } - #endregion + #endregion Global Scope #region Public Methods public async Task UploadFileAsync(IFormFile file, string? filePath = null) @@ -44,19 +49,24 @@ public async Task UploadFileAsync(IFormFile file, string? filePath = nul filePath = await GenerateFilePath(file, _folder, filePath, true); // Salva o arquivo - using (var stream = new FileStream(filePath, FileMode.Create)) + using (FileStream stream = new(filePath, FileMode.Create)) + { await file.CopyToAsync(stream); + } // Retorna o caminho do arquivo return filePath; } - public async Task DeleteFile(string filePath) + public async Task DeleteFileAsync(string filePath) { try { if (File.Exists(filePath)) + { File.Delete(filePath); + } + await Task.CompletedTask; } catch (Exception ex) @@ -64,19 +74,68 @@ public async Task DeleteFile(string filePath) throw new Exception($"O arquivo {filePath} não pode ser excluído.", ex); } } - #endregion + + public async Task UploadFileAsync(byte[] file, string? filePath) + { + // Verifica se caminho do arquivo é vazio + // FilePath representa aqui o caminho completo do arquivo ou o nome do arquivo + // Por exemplo: C:\Users\user\Documents\file.pdf ou file.pdf + if (string.IsNullOrEmpty(filePath)) + { + throw new Exception("O caminho do arquivo não pode ser vazio."); + } + + // Verifica se a extensão do arquivo é permitida + string extension = Path.GetExtension(filePath); + if (!_allowedExtensions.Contains(extension)) + { + throw new Exception($"A extensão {extension} do arquivo não é permitida."); + } + + // Verifica o tamanho do arquivo + if (file.Length > _maxFileSizeInBytes) + { + throw new Exception($"O tamanho do arquivo excede o máximo de {_maxFileSizeInBytes} bytes."); + } + + // Gera um nome único para o arquivo + string fileName = $"{Guid.NewGuid()}{Path.GetExtension(filePath)}"; + + // Cria o diretório caso não exista + string dirPath = Path.Combine(_directory, _folder); + if (!Directory.Exists(dirPath)) + { + _ = Directory.CreateDirectory(dirPath); + } + + // Gera o caminho do arquivo + filePath = Path.Combine(dirPath, fileName); + + // Salva o arquivo + using (FileStream stream = new(filePath, FileMode.Create)) + { + await stream.WriteAsync(file); + } + + return filePath; + } + #endregion Public Methods #region Private Methods - private async Task GenerateFilePath(IFormFile file, string custom_directory, string? filePath = null, bool onlyPdf = false) + private async Task GenerateFilePath(IFormFile file, string customDirectory, string? filePath = null, bool onlyPdf = false) { // Verifica se a extensão do arquivo é permitida - var extension = Path.GetExtension(file.FileName); - if ((onlyPdf && extension != ".pdf") || (!_allowedExtensions.Contains(extension))) + string extension = Path.GetExtension(file.FileName); + if ((onlyPdf && extension != ".pdf") || !_allowedExtensions.Contains(extension)) + { throw new Exception($"A extensão {extension} do arquivo não é permitida."); + } // Verifica o tamanho do arquivo if (file.Length > _maxFileSizeInBytes) + { throw new Exception($"O tamanho do arquivo excede o máximo de {_maxFileSizeInBytes} bytes."); + } // Gera um nome único para o arquivo string fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; @@ -85,9 +144,11 @@ private async Task GenerateFilePath(IFormFile file, string custom_direct if (string.IsNullOrEmpty(filePath)) { // Cria o diretório caso não exista - string dirPath = Path.Combine(_directory, custom_directory); + string dirPath = Path.Combine(_directory, customDirectory); if (!Directory.Exists(dirPath)) - Directory.CreateDirectory(dirPath); + { + _ = Directory.CreateDirectory(dirPath); + } // Gera o caminho do arquivo filePath = Path.Combine(dirPath, fileName); @@ -95,7 +156,7 @@ private async Task GenerateFilePath(IFormFile file, string custom_direct // Deleta o arquivo se o caminho do arquivo for informado else { - await DeleteFile(filePath); + await DeleteFileAsync(filePath); } return filePath; } @@ -103,14 +164,14 @@ private async Task GenerateFilePath(IFormFile file, string custom_direct private static IConfiguration InitializeConfigs() { // Adicione o caminho base para o arquivo appsettings.json - var basePath = Path.GetDirectoryName(typeof(StorageFileService).Assembly.Location); - var configurationBuilder = new ConfigurationBuilder() + string? basePath = Path.GetDirectoryName(typeof(StorageFileService).Assembly.Location); + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() .SetBasePath(basePath!) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); // Use a configuração criada acima para ler as configurações do appsettings.json return configurationBuilder.Build(); } - #endregion + #endregion Private Methods } } \ No newline at end of file diff --git a/src/Infrastructure/Services/TokenAuthenticationService.cs b/src/Infrastructure/Services/TokenAuthenticationService.cs index 36e92fa7..1db35af3 100644 --- a/src/Infrastructure/Services/TokenAuthenticationService.cs +++ b/src/Infrastructure/Services/TokenAuthenticationService.cs @@ -1,104 +1,132 @@ -using System.Security.Claims; -using System.IdentityModel.Tokens.Jwt; -using Domain.Contracts.Auth; -using Domain.Interfaces.Services; -using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using System.Text; +using Domain.Entities; +using Domain.Interfaces.Services; using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Tokens; -namespace Infrastructure.Services; -public class TokenAuthenticationService : ITokenAuthenticationService +namespace Services { - #region Global Scope - private readonly IDotEnvSecrets _dotEnvSecrets; - private readonly IHttpContextAccessor _httpContextAccessor; - - public TokenAuthenticationService(IHttpContextAccessor httpContextAccessor, IDotEnvSecrets dotEnvSecrets) + public class TokenAuthenticationService : ITokenAuthenticationService { - _httpContextAccessor = httpContextAccessor; - _dotEnvSecrets = dotEnvSecrets; - } - #endregion - - public UserLoginOutput GenerateToken(Guid? id, string? userName, string? role) - { - // Verifica se o id é nulo - if (id == null) - throw new Exception("Id do usuário não informado."); - - // Verifica se o nome do usuário é nulo - if (string.IsNullOrEmpty(userName)) - throw new Exception("Nome do usuário não informado."); + #region Global Scope + private readonly IDotEnvSecrets _dotEnvSecrets; + private readonly IHttpContextAccessor _httpContextAccessor; - // Verifica se o perfil do usuário é nulo - if (string.IsNullOrEmpty(role)) - throw new Exception("Perfil do usuário não informado."); - - // Declaração do usuário - var claims = new[] + public TokenAuthenticationService(IDotEnvSecrets dotEnvSecrets, IHttpContextAccessor httpContextAccessor) { - new Claim(ClaimTypes.Sid, id.Value.ToString()), - new Claim(ClaimTypes.Name, userName), - new Claim(ClaimTypes.Role, role), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) - }; - - // Gerar chave privada para assinar o token - var privateKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_dotEnvSecrets.GetJwtSecret() - ?? throw new Exception("Chave secreta não informada."))); - - // Gerar a assinatura digital - var credentials = new SigningCredentials(privateKey, SecurityAlgorithms.HmacSha256); - - // Tempo de expiração do token - var expireIn = int.Parse(_dotEnvSecrets.GetJwtExpirationTime() ?? "10"); - - // Definir o tempo de expiração - var expiration = DateTime.UtcNow.AddMinutes(expireIn); - - // Gerar o Token - var token = new JwtSecurityToken( - issuer: _dotEnvSecrets.GetJwtIssuer() ?? throw new Exception("Emissor do token não informado."), - audience: _dotEnvSecrets.GetJwtAudience() ?? throw new Exception("Público do token não informado."), - claims: claims, - expires: expiration, - signingCredentials: credentials); - - return new UserLoginOutput() + _dotEnvSecrets = dotEnvSecrets; + _httpContextAccessor = httpContextAccessor; + } + #endregion Global Scope + + /// + /// Gera o token de autenticação. + /// + /// Id do usuário. + /// Id do professor ou do aluno. + /// Nome do usuário. + /// Perfil do usuário. + /// Token de autenticação. + public string GenerateToken(Guid? id, Guid? actorId, string? userName, string? role) { - Token = new JwtSecurityTokenHandler().WriteToken(token), - Expiration = expiration - }; - } - - /// - /// Retorna as claims do usuário autenticado. - /// - /// Id, Name e Role. - public UserClaimsOutput GetUserAuthenticatedClaims() - { - // Get the current HttpContext to retrieve the claims principal - var httpContext = _httpContextAccessor.HttpContext; - - // Check if the user is authenticated - if (httpContext?.User.Identity == null || httpContext?.User.Identity.IsAuthenticated != true) - throw new Exception("User is not authenticated."); - - // Get the claims principal - var claimsIdentity = httpContext.User.Identity as ClaimsIdentity - ?? throw new Exception("User is not authenticated."); - - // Get the user's ID - var id = claimsIdentity.FindFirst(ClaimTypes.Sid)?.Value; - if (string.IsNullOrEmpty(id)) - throw new Exception("User ID not provided."); - - // Return the user's claims - return new UserClaimsOutput() + // Verifica se o id é nulo + if (id == null) + { + throw new Exception("Id do usuário não informado."); + } + + // Verifica se o nome do usuário é nulo + if (string.IsNullOrEmpty(userName)) + { + throw new Exception("Nome do usuário não informado."); + } + + // Verifica se o perfil do usuário é nulo + if (string.IsNullOrEmpty(role)) + { + throw new Exception("Perfil do usuário não informado."); + } + + // Verifica se o id do professor ou estudante é nulo + if (actorId == null) + { + throw new Exception($"Id do {role} inválido."); + } + + // Declaração do usuário + Claim[] claims = new[] + { + new Claim(ClaimTypes.Sid, id.Value.ToString()), + new Claim(ClaimTypes.Actor, actorId.Value.ToString()), + new Claim(ClaimTypes.Name, userName), + new Claim(ClaimTypes.Role, role), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + + // Gerar chave privada para assinar o token + SymmetricSecurityKey privateKey = new(Encoding.UTF8.GetBytes(_dotEnvSecrets.GetJwtSecret() + ?? throw new Exception("Chave secreta não informada."))); + + // Gerar a assinatura digital + SigningCredentials credentials = new(privateKey, SecurityAlgorithms.HmacSha256); + + // Tempo de expiração do token + int expireIn = int.Parse(_dotEnvSecrets.GetJwtExpirationTime() ?? "10"); + + // Definir o tempo de expiração + DateTime expiration = DateTime.UtcNow.AddMinutes(expireIn); + + // Gerar o Token + JwtSecurityToken token = new( + issuer: _dotEnvSecrets.GetJwtIssuer() ?? throw new Exception("Emissor do token não informado."), + audience: _dotEnvSecrets.GetJwtAudience() ?? throw new Exception("Público do token não informado."), + claims: claims, + expires: expiration, + signingCredentials: credentials); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + /// + /// Retorna as claims do usuário autenticado. + /// + /// Id, Name e Role. + public Dictionary GetUserAuthenticatedClaims() { - Id = Guid.Parse(id), - Name = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value, - Role = claimsIdentity.FindFirst(ClaimTypes.Role)?.Value - }; + // Get the current HttpContext to retrieve the claims principal + HttpContext? httpContext = _httpContextAccessor.HttpContext; + + // Check if the user is authenticated + if (httpContext?.User.Identity == null || httpContext?.User.Identity.IsAuthenticated != true) + { + throw new Exception("Usuário não autenticado."); + } + + // Get the claims principal + ClaimsIdentity claimsIdentity = httpContext.User.Identity as ClaimsIdentity + ?? throw new Exception("Usuário não autenticado."); + + // Get the user's ID + string? id = claimsIdentity.FindFirst(ClaimTypes.Sid)?.Value; + if (string.IsNullOrEmpty(id)) + { + throw new Exception("Id do usuário não informado."); + } + + string? actorId = claimsIdentity.FindFirst(ClaimTypes.Actor)?.Value; + if (string.IsNullOrEmpty(actorId)) + { + throw new Exception($"Id do {ClaimTypes.Role} não informado."); + } + + // Cria o output com o id do professor ou estudade e o usuário + var user = new User(Guid.Parse(id), claimsIdentity.FindFirst(ClaimTypes.Name)?.Value, claimsIdentity.FindFirst(ClaimTypes.Role)?.Value); + return new Dictionary + { + { Guid.Parse(actorId), user } + }; + } } } diff --git a/src/Infrastructure/WebAPI/Controllers/ActivityController.cs b/src/Infrastructure/WebAPI/Controllers/ActivityController.cs new file mode 100644 index 00000000..3efa7e8b --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/ActivityController.cs @@ -0,0 +1,92 @@ +using Application.Interfaces.UseCases.ActivityType; +using Application.Ports.Activity; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller responsável por gerenciar as atividades. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class ActivityController : ControllerBase + { + private readonly IGetLastNoticeActivities _getLastNoticeActivities; + private readonly IGetActivitiesByNoticeId _getActivitiesByNoticeId; + private readonly ILogger _logger; + /// + /// Construtor da classe. + /// + /// Serviço de obtenção das últimas atividades em uso pelo edital anterior. + /// Serviço de obtenção das atividades de um edital. + /// Serviço de log. + public ActivityController(IGetLastNoticeActivities getLastNoticeActivities, + IGetActivitiesByNoticeId getActivitiesByNoticeId, + ILogger logger) + { + _getLastNoticeActivities = getLastNoticeActivities; + _getActivitiesByNoticeId = getActivitiesByNoticeId; + _logger = logger; + } + + /// + /// Obtém as últimas atividades em uso pelo edital anterior. + /// + /// Lista de atividades mais recentes. + /// Retorna a lista de atividades mais recentes. + /// Requisição incorreta. + /// Usuário não autorizado. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task GetLastNoticeActivities() + { + try + { + var result = await _getLastNoticeActivities.ExecuteAsync(); + _logger.LogInformation("Atividades encontradas."); + return Ok(result); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Obtém as atividades de um edital. + /// + /// Id do edital. + /// Lista de atividades. + /// Retorna a lista de atividades. + /// Requisição incorreta. + /// Usuário não autorizado. + [HttpGet("notice/{noticeId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task GetActivitiesByNoticeId(Guid? noticeId) + { + if (noticeId == null) + { + return BadRequest("O ID do edital não pode ser nulo."); + } + + try + { + var result = await _getActivitiesByNoticeId.ExecuteAsync(noticeId); + _logger.LogInformation("Atividades encontradas para o edital {noticeId}.", noticeId); + return Ok(result); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/AreaController.cs b/src/Infrastructure/WebAPI/Controllers/AreaController.cs index d943a372..1c875391 100644 --- a/src/Infrastructure/WebAPI/Controllers/AreaController.cs +++ b/src/Infrastructure/WebAPI/Controllers/AreaController.cs @@ -1,32 +1,49 @@ -using Adapters.Gateways.Area; -using Adapters.Interfaces; +using Application.Interfaces.UseCases.Area; +using Application.Ports.Area; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Área. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class AreaController : ControllerBase { #region Global Scope - private readonly IAreaPresenterController _service; + private readonly IGetAreaById _getById; + private readonly IGetAreasByMainArea _getAreasByMainArea; + private readonly ICreateArea _create; + private readonly IUpdateArea _update; + private readonly IDeleteArea _delete; private readonly ILogger _logger; /// /// Construtor do Controller de Área. /// - /// - /// - public AreaController(IAreaPresenterController service, ILogger logger) + /// Serviço de obtenção de área pelo id. + /// Serviço de obtenção de todas as áreas ativas da área principal. + /// Serviço de criação de área. + /// Serviço de atualização de área. + /// Serviço de remoção de área. + /// Serviço de log. + public AreaController(IGetAreaById getById, + IGetAreasByMainArea getAreasByMainArea, + ICreateArea create, + IUpdateArea update, + IDeleteArea delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAreasByMainArea = getAreasByMainArea; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca área pelo id. @@ -34,23 +51,29 @@ public AreaController(IAreaPresenterController service, ILogger /// /// Área correspondente /// Retorna área correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma área encontrada. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task GetById(Guid? id) { - _logger.LogInformation("Executando ({MethodName}) com os parâmetros: Id = {id}", id); + _logger.LogInformation("Executando ({MethodName}) com os parâmetros: Id = {id}", nameof(GetById), id); if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O id informado não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Método ({MethodName}) executado. Retorno: Id = {id}", id); + var model = await _getById.ExecuteAsync(id); + _logger.LogInformation("Método ({MethodName}) executado. Retorno: Id = {id}", nameof(GetById), id); return Ok(model); } catch (Exception ex) @@ -66,30 +89,35 @@ public async Task> GetById(Guid? id) /// /// Todas as áreas ativas da área principal /// Retorna todas as áreas ativas da área principal + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma área encontrada. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAreasByMainArea(Guid? mainAreaId, int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task GetAreasByMainArea(Guid? mainAreaId, int skip = 0, int take = 50) { _logger.LogInformation("Executando método com os parâmetros: MainAreaId = {mainAreaId}, Skip = {skip}, Take = {take}", mainAreaId, skip, take); if (mainAreaId == null) { - const string msg = "O MainAreaId informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O MainAreaId informado não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var models = await _service.GetAreasByMainArea(mainAreaId, skip, take); - if (models == null) + var models = await _getAreasByMainArea.ExecuteAsync(mainAreaId, skip, take); + if (models == null || !models.Any()) { - const string msg = "Nenhuma Área encontrada."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhuma Área encontrada."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - int count = models.Count(); - _logger.LogInformation("Método finalizado, retorno: Número de entidades = {count}", count); + _logger.LogInformation("Método finalizado, retorno: Número de entidades = {count}", models.Count()); return Ok(models); } catch (Exception ex) @@ -104,17 +132,19 @@ public async Task>> GetAreasBy /// /// /// Área criada - /// Retorna área criada + /// Retorna área criada + /// Requisição incorreta. [HttpPost] [Authorize(Roles = "ADMIN")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Create([FromBody] CreateAreaRequest request) + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + public async Task Create([FromBody] CreateAreaInput request) { try { - var model = await _service.Create(request) as DetailedReadAreaResponse; - _logger.LogInformation("Método finalizado, retorno: Id = {id}", model?.Id); - return Ok(model); + var createdArea = await _create.ExecuteAsync(request); + _logger.LogInformation("Área criada {id}", createdArea?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdArea?.Id }, createdArea); } catch (Exception ex) { @@ -129,13 +159,23 @@ public async Task> Create([FromBody] Crea /// /// Área atualizada /// Retorna área atualizada + /// Requisição incorreta. + /// Usuário não autorizado. [HttpPut("{id}")] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateAreaRequest request) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task Update(Guid? id, [FromBody] UpdateAreaInput request) { + if (id == null) + { + return BadRequest("O id informado não pode ser nulo."); + } + try { - var model = await _service.Update(id, request) as DetailedReadAreaResponse; + var model = await _update.ExecuteAsync(id, request); _logger.LogInformation("Área atualizada: {id}", model?.Id); return Ok(model); } @@ -152,20 +192,23 @@ public async Task> Update(Guid? id, [From /// /// Área removida /// Retorna área removida + /// Requisição incorreta. + /// Usuário não autorizado. [HttpDelete("{id}")] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O id informado não pode ser nulo."); } try { - var model = await _service.Delete(id.Value) as DetailedReadAreaResponse; + var model = await _delete.ExecuteAsync(id.Value); _logger.LogInformation("Área removida: {id}", model?.Id); return Ok(model); } diff --git a/src/Infrastructure/WebAPI/Controllers/AssistanceTypeController.cs b/src/Infrastructure/WebAPI/Controllers/AssistanceTypeController.cs new file mode 100644 index 00000000..19851972 --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/AssistanceTypeController.cs @@ -0,0 +1,209 @@ +using Application.Ports.AssistanceType; +using Application.Interfaces.UseCases.AssistanceType; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller de Bolsa de Assistência. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class AssistanceTypeController : ControllerBase + { + #region Global Scope + private readonly IGetAssistanceTypeById _getById; + private readonly IGetAssistanceTypes _getAll; + private readonly ICreateAssistanceType _create; + private readonly IUpdateAssistanceType _update; + private readonly IDeleteAssistanceType _delete; + private readonly ILogger _logger; + /// + /// Construtor do Controller de Bolsa de Assistência. + /// + /// Serviço de obtenção de bolsa de assistência pelo id. + /// Serviço de obtenção de todas as bolsas de assistência ativas. + /// Serviço de criação de bolsa de assistência. + /// Serviço de atualização de bolsa de assistência. + /// Serviço de remoção de bolsa de assistência. + /// Serviço de log. + public AssistanceTypeController(IGetAssistanceTypeById getById, + IGetAssistanceTypes getAll, + ICreateAssistanceType create, + IUpdateAssistanceType update, + IDeleteAssistanceType delete, + ILogger logger) + { + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; + _logger = logger; + } + #endregion Global Scope + + /// + /// Busca bolsa de assistência pelo id. + /// + /// + /// Bolsa de Assistência correspondente + /// Retorna bolsa de assistência correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma bolsa de assistência encontrada. + [HttpGet("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAssistanceTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task GetById(Guid? id) + { + if (id == null) + { + const string errorMessage = "O id informado não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + + try + { + var model = await _getById.ExecuteAsync(id); + _logger.LogInformation("Bolsa de Assistência encontrada para o id {id}.", id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Busca todas as bolsas de assitência ativas. + /// + /// + /// Todas as bolsas de assitência ativas + /// Retorna todas as bolsas de assitência ativas + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma bolsa de assitência encontrada. + [HttpGet] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) + { + var models = await _getAll.ExecuteAsync(skip, take); + if (models == null) + { + const string errorMessage = "Nenhum Bolsa de Assistência encontrada."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); + } + _logger.LogInformation("Bolsas de Assistência encontradas: {quantidade}", models.Count()); + return Ok(models); + } + + /// + /// Cria bolsa de assistência. + /// + /// + /// Bolsa de Assistência criada + /// Retorna bolsa de assistência criada + /// Requisição incorreta. + /// Usuário não autorizado. + [HttpPost] + [Authorize(Roles = "ADMIN")] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadAssistanceTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task Create([FromBody] CreateAssistanceTypeInput request) + { + try + { + var createdAssistanceType = await _create.ExecuteAsync(request); + _logger.LogInformation("Bolsa de Assistência criada {id}", createdAssistanceType?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdAssistanceType?.Id }, createdAssistanceType); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Atualiza bolsa de assistência. + /// + /// + /// Bolsa de Assistência atualizada + /// Retorna bolsa de assistência atualizada + /// Requisição incorreta. + /// Usuário não autorizado. + [HttpPut("{id}")] + [Authorize(Roles = "ADMIN")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAssistanceTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task Update(Guid? id, [FromBody] UpdateAssistanceTypeInput request) + { + if (id == null) + { + return BadRequest("O id informado não pode ser nulo."); + } + + try + { + var updatedAssistanceType = await _update.ExecuteAsync(id, request); + _logger.LogInformation("Bolsa de Assistência atualizado: {id}", updatedAssistanceType?.Id); + return Ok(updatedAssistanceType); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Remove bolsa de assistência. + /// + /// + /// Bolsa de Assistência removido + /// Retorna bolsa de assistência removido + /// Requisição incorreta. + /// Usuário não autorizado. + [HttpDelete("{id}")] + [Authorize(Roles = "ADMIN")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadAssistanceTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task Delete(Guid? id) + { + if (id == null) + { + const string errorMessage = "O id informado não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + + try + { + var model = await _delete.ExecuteAsync(id.Value); + _logger.LogInformation("Bolsa de Assistência removido: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/AuthController.cs b/src/Infrastructure/WebAPI/Controllers/AuthController.cs index 26bcbbdc..7d8d6ce1 100644 --- a/src/Infrastructure/WebAPI/Controllers/AuthController.cs +++ b/src/Infrastructure/WebAPI/Controllers/AuthController.cs @@ -1,31 +1,44 @@ -using Adapters.Gateways.Auth; -using Adapters.Interfaces; +using Application.Ports.Auth; +using Application.Interfaces.UseCases.Auth; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Autenticação. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] public class AuthController : ControllerBase { #region Global Scope - private readonly IAuthPresenterController _authService; + private readonly IConfirmEmail _confirmEmail; + private readonly IForgotPassword _forgotPassword; + private readonly ILogin _login; + private readonly IResetPassword _resetPassword; private readonly ILogger _logger; /// /// Construtor do Controller de Autenticação. /// - /// - /// - public AuthController(IAuthPresenterController authService, ILogger logger) + /// Serviço de confirmação de e-mail. + /// Serviço de solicitação de reset de senha. + /// Serviço de login. + /// Serviço de reset de senha. + /// Serviço de log. + public AuthController(IConfirmEmail confirmEmail, + IForgotPassword forgotPassword, + ILogin login, + IResetPassword resetPassword, + ILogger logger) { - _authService = authService; + _confirmEmail = confirmEmail; + _forgotPassword = forgotPassword; + _login = login; + _resetPassword = resetPassword; _logger = logger; } - #endregion + #endregion Global Scope /// /// Realiza a confirmação do e-mail do usuário através do token de validação fornecido e do E-mail do usuário. @@ -34,13 +47,16 @@ public AuthController(IAuthPresenterController authService, ILoggerToken de validação /// Resultado da solicitação de validação /// E-mail confirmado com sucesso + /// Requisição incorreta. [AllowAnonymous] [HttpPost("ConfirmEmail", Name = "ConfirmEmail")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] public async Task> ConfirmEmail(string? email, string? token) { try { - var result = await _authService.ConfirmEmail(email, token); + string result = await _confirmEmail.ExecuteAsync(email, token); _logger.LogInformation("Resultado: {Result}", result); return Ok(result); } @@ -58,13 +74,16 @@ public async Task> ConfirmEmail(string? email, string? toke /// /// Resultado da requisição /// Solicitação realizada com sucesso + /// Requisição incorreta. [AllowAnonymous] [HttpPost("ForgotPassword", Name = "ForgotPassword")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] public async Task> ForgotPassword(string? email) { try { - var result = await _authService.ForgotPassword(email); + string result = await _forgotPassword.ExecuteAsync(email); _logger.LogInformation("Resultado: {Result}", result); return Ok(result); } @@ -81,13 +100,16 @@ public async Task> ForgotPassword(string? email) /// /// Retorna token de acesso /// Retorna token de acesso + /// Requisição incorreta. [AllowAnonymous] [HttpPost("Login", Name = "Login")] - public async Task> Login([FromBody] UserLoginRequest request) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserLoginOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + public async Task> Login([FromBody] UserLoginInput request) { try { - var model = await _authService.Login(request); + var model = await _login.ExecuteAsync(request); _logger.LogInformation("Login realizado pelo usuário: {email}.", request.Email); return Ok(model); } @@ -104,13 +126,16 @@ public async Task> Login([FromBody] UserLoginReq /// /// Retorna o status da alteração /// Retorna o status da alteração + /// Requisição incorreta. [AllowAnonymous] [HttpPost("ResetPassword", Name = "ResetPassword")] - public async Task> ResetPassword([FromBody] UserResetPasswordRequest request) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + public async Task> ResetPassword([FromBody] UserResetPasswordInput request) { try { - var result = await _authService.ResetPassword(request); + string result = await _resetPassword.ExecuteAsync(request); _logger.LogInformation("Resultado: {Result}", result); return Ok(result); } diff --git a/src/Infrastructure/WebAPI/Controllers/CampusController.cs b/src/Infrastructure/WebAPI/Controllers/CampusController.cs index ba68e92d..154ea298 100644 --- a/src/Infrastructure/WebAPI/Controllers/CampusController.cs +++ b/src/Infrastructure/WebAPI/Controllers/CampusController.cs @@ -1,32 +1,49 @@ -using Adapters.Gateways.Campus; -using Adapters.Interfaces; +using Application.Ports.Campus; +using Application.Interfaces.UseCases.Campus; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Campus. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class CampusController : ControllerBase { #region Global Scope - private readonly ICampusPresenterController _service; + private readonly IGetCampusById _getById; + private readonly IGetCampuses _getAll; + private readonly ICreateCampus _create; + private readonly IUpdateCampus _update; + private readonly IDeleteCampus _delete; private readonly ILogger _logger; /// /// Construtor do Controller de Campus. /// - /// - /// - public CampusController(ICampusPresenterController service, ILogger logger) + /// Serviço de obtenção de campus pelo id. + /// Serviço de obtenção de todos os campus ativos. + /// Serviço de criação de campus. + /// Serviço de atualização de campus. + /// Serviço de remoção de campus. + /// Serviço de log. + public CampusController(IGetCampusById getById, + IGetCampuses getAll, + ICreateCampus create, + IUpdateCampus update, + IDeleteCampus delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca campus pelo id. @@ -34,27 +51,31 @@ public CampusController(ICampusPresenterController service, ILogger /// Campus correspondente /// Retorna campus correspondente + /// Requisição incorreta. + /// Campus não encontrado. [HttpGet("{id}")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { - if (id == null) - { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); - } - try { - var model = await _service.GetById(id); + var model = await _getById.ExecuteAsync(id); + if (model == null) + { + const string errorMessage = "Campus não encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); + } _logger.LogInformation("Campus encontrado para o id {id}.", id); return Ok(model); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -64,37 +85,54 @@ public async Task> GetById(Guid? id) /// /// Todas os campus ativos /// Retorna todas os campus ativos + /// Requisição incorreta. + /// Nenhum Campus encontrado. [HttpGet] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + try { - const string msg = "Nenhum Campus encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + var models = await _getAll.ExecuteAsync(skip, take); + if (models == null) + { + const string errorMessage = "Nenhum Campus encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); + } + _logger.LogInformation("Campus encontrados: {quantidade}", models.Count()); + return Ok(models); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); } - _logger.LogInformation("Campus encontrados: {quantidade}", models.Count()); - return Ok(models); } /// - /// Cria campus. + /// Cria um novo campus. /// - /// - /// Campus criado - /// Retorna campus criado + /// Informações do campus + /// O campus criado + /// Retorna o campus recém-criado + /// Requisição inválida, se a entrada for inválida + /// Não autorizado, se o usuário não tiver a função necessária [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateCampusRequest request) + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadCampusOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> Create([FromBody] CreateCampusInput request) { try { - var model = await _service.Create(request) as DetailedReadCampusResponse; + var model = await _create.ExecuteAsync(request); _logger.LogInformation("Campus criado: {id}", model?.Id); - return Ok(model); + return CreatedAtAction(nameof(GetById), new { id = model?.Id }, model); } catch (Exception ex) { @@ -104,19 +142,38 @@ public async Task> Create([FromBody] Cr } /// - /// Atualiza campus. + /// Atualiza um campus. /// - /// - /// Campus atualizado - /// Retorna campus atualizado + /// O ID do campus a ser atualizado + /// Informações de atualização do campus + /// O campus atualizado + /// Retorna o campus atualizado + /// Requisição inválida, se a entrada for inválida + /// Não autorizado, se o usuário não tiver a função necessária + /// Não encontrado, se o campus não existir [HttpPut("{id}")] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateCampusRequest request) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadCampusOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Update(Guid? id, [FromBody] UpdateCampusInput request) { + if (id == null) + { + return BadRequest("O ID do campus não foi fornecido."); + } + try { - var model = await _service.Update(id, request) as DetailedReadCampusResponse; - _logger.LogInformation("Campus atualizado: {id}", model?.Id); + var model = await _update.ExecuteAsync(id.Value, request); + + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + + _logger.LogInformation("Campus atualizado: {id}", model.Id); return Ok(model); } catch (Exception ex) @@ -127,26 +184,38 @@ public async Task> Update(Guid? id, [Fr } /// - /// Remove campus. + /// Remove um campus. /// - /// - /// Campus removido - /// Retorna campus removido + /// O ID do campus a ser removido + /// O campus removido + /// Retorna o campus removido + /// Requisição inválida, se o ID for nulo + /// Não autorizado, se o usuário não tiver a função necessária + /// Não encontrado, se o campus não existir [HttpDelete("{id}")] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadCampusOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Remover(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do campus não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadCampusResponse; - _logger.LogInformation("Campus removido: {id}", model?.Id); + var model = await _delete.ExecuteAsync(id.Value); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + + _logger.LogInformation("Campus removido: {id}", model.Id); return Ok(model); } catch (Exception ex) diff --git a/src/Infrastructure/WebAPI/Controllers/CourseController.cs b/src/Infrastructure/WebAPI/Controllers/CourseController.cs index baba970d..bf721281 100644 --- a/src/Infrastructure/WebAPI/Controllers/CourseController.cs +++ b/src/Infrastructure/WebAPI/Controllers/CourseController.cs @@ -1,32 +1,49 @@ -using Adapters.Gateways.Course; -using Adapters.Interfaces; +using Application.Ports.Course; +using Application.Interfaces.UseCases.Course; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Curso. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class CourseController : ControllerBase { #region Global Scope - private readonly ICoursePresenterController _service; + private readonly IGetCourseById _getById; + private readonly IGetCourses _getAll; + private readonly ICreateCourse _create; + private readonly IUpdateCourse _update; + private readonly IDeleteCourse _delete; private readonly ILogger _logger; /// /// Construtor do Controller de Curso. /// - /// - /// - public CourseController(ICoursePresenterController service, ILogger logger) + /// Serviço de obtenção de curso pelo id. + /// Serviço de obtenção de todos os cursos ativos. + /// Serviço de criação de curso. + /// Serviço de atualização de curso. + /// Serviço de remoção de curso. + /// Serviço de log. + public CourseController(IGetCourseById getById, + IGetCourses getAll, + ICreateCourse create, + IUpdateCourse update, + IDeleteCourse delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca curso pelo id. @@ -34,27 +51,38 @@ public CourseController(ICoursePresenterController service, ILogger /// Curso correspondente /// Retorna curso correspondente + /// Requisição incorreta. + /// Curso não encontrado. + /// Usuário não autorizado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadCourseOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do curso não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Curso encontrado para o id {id}.", id); - return Ok(model); + var course = await _getById.ExecuteAsync(id); + if (course == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Curso encontrado para o ID {id}.", id); + return Ok(course); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -65,18 +93,29 @@ public async Task> GetById(Guid? id) /// Todas os cursos ativos /// Retorna todas os cursos ativos [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + try { - const string msg = "Nenhum Curso encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + var courses = await _getAll.ExecuteAsync(skip, take); + if (courses == null || !courses.Any()) + { + const string errorMessage = "Nenhum curso encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); + } + _logger.LogInformation("Cursos encontrados: {quantidade}", courses.Count()); + return Ok(courses); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); } - _logger.LogInformation("Cursos encontrados: {quantidade}", models.Count()); - return Ok(models); } /// @@ -84,17 +123,21 @@ public async Task>> GetAll(i /// /// /// Curso criado - /// Retorna curso criado + /// Retorna curso criado + /// Requisição incorreta. + /// Usuário não autorizado. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadCourseOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateCourseRequest request) + public async Task> Create([FromBody] CreateCourseInput request) { try { - var model = await _service.Create(request) as DetailedReadCourseResponse; - _logger.LogInformation("Curso criado: {id}", model?.Id); - return Ok(model); + var createdCourse = await _create.ExecuteAsync(request); + _logger.LogInformation("Curso criado: {id}", createdCourse?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdCourse?.Id }, createdCourse); } catch (Exception ex) { @@ -109,15 +152,31 @@ public async Task> Create([FromBody] Cr /// /// Curso atualizado /// Retorna curso atualizado + /// Requisição incorreta. + /// Usuário não autorizado. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadCourseOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateCourseRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateCourseInput request) { + if (id == null) + { + const string errorMessage = "O ID do curso não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.Update(id, request) as DetailedReadCourseResponse; - _logger.LogInformation("Curso atualizado: {id}", model?.Id); - return Ok(model); + var updatedCourse = await _update.ExecuteAsync(id.Value, request); + if (updatedCourse == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Curso atualizado: {id}", updatedCourse?.Id); + return Ok(updatedCourse); } catch (Exception ex) { @@ -132,22 +191,31 @@ public async Task> Update(Guid? id, [Fr /// /// Curso removido /// Retorna curso removido + /// Requisição incorreta. + /// Usuário não autorizado. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadCourseOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do curso não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadCourseResponse; - _logger.LogInformation("Curso removido: {id}", model?.Id); - return Ok(model); + var deletedCourse = await _delete.ExecuteAsync(id.Value); + if (deletedCourse == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Curso removido: {id}", deletedCourse?.Id); + return Ok(deletedCourse); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/MainAreaController.cs b/src/Infrastructure/WebAPI/Controllers/MainAreaController.cs index b41f1b6c..64b63eac 100644 --- a/src/Infrastructure/WebAPI/Controllers/MainAreaController.cs +++ b/src/Infrastructure/WebAPI/Controllers/MainAreaController.cs @@ -1,32 +1,49 @@ -using Adapters.Gateways.MainArea; -using Adapters.Interfaces; +using Application.Ports.MainArea; +using Application.Interfaces.UseCases.MainArea; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Área Principal. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class MainAreaController : ControllerBase { #region Global Scope - private readonly IMainAreaPresenterController _service; + private readonly IGetMainAreaById _getById; + private readonly IGetMainAreas _getAll; + private readonly ICreateMainArea _create; + private readonly IUpdateMainArea _update; + private readonly IDeleteMainArea _delete; private readonly ILogger _logger; /// /// Construtor do Controller de Área Principal. /// - /// - /// - public MainAreaController(IMainAreaPresenterController service, ILogger logger) + /// Serviço de obtenção de área principal pelo id. + /// Serviço de obtenção de todas as áreas principais ativas. + /// Serviço de criação de área principal. + /// Serviço de atualização de área principal. + /// Serviço de remoção de área principal. + /// Serviço de log. + public MainAreaController(IGetMainAreaById getById, + IGetMainAreas getAll, + ICreateMainArea create, + IUpdateMainArea update, + IDeleteMainArea delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca área principal pelo id. @@ -34,27 +51,37 @@ public MainAreaController(IMainAreaPresenterController service, ILogger /// Área principal correspondente /// Retorna área principal correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Área principal não encontrada. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedMainAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID da área principal não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Área Principal encontrada para o id {id}.", id); - return Ok(model); + var mainArea = await _getById.ExecuteAsync(id.Value); + if (mainArea == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Área Principal encontrada para o ID {id}.", id); + return Ok(mainArea); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -64,19 +91,25 @@ public async Task> GetById(Guid? id) /// /// Todas as áreas principais ativas /// Retorna todas as áreas principais ativas + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma área principal encontrada. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + var mainAreas = await _getAll.ExecuteAsync(skip, take); + if (mainAreas == null || !mainAreas.Any()) { - const string msg = "Nenhuma Área Principal encontrada."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhuma Área Principal encontrada."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Áreas principais encontradas: {quantidade}", models.Count()); - return Ok(models); + _logger.LogInformation("Áreas principais encontradas: {quantidade}", mainAreas.Count()); + return Ok(mainAreas); } /// @@ -84,17 +117,21 @@ public async Task>> GetAll /// /// /// Área principal criada - /// Retorna área principal criada + /// Retorna área principal criada + /// Requisição incorreta. + /// Usuário não autorizado. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedMainAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateMainAreaRequest request) + public async Task> Create([FromBody] CreateMainAreaInput request) { try { - var model = await _service.Create(request) as DetailedReadMainAreaResponse; - _logger.LogInformation("Área principal criada: {id}", model?.Id); - return Ok(model); + var createdMainArea = await _create.ExecuteAsync(request); + _logger.LogInformation("Área principal criada: {id}", createdMainArea?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdMainArea?.Id }, createdMainArea); } catch (Exception ex) { @@ -109,15 +146,33 @@ public async Task> Create([FromBody] /// /// Área principal atualizada /// Retorna área principal atualizada + /// Requisição incorreta. + /// Usuário não autorizado. + /// Área principal não encontrada. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedMainAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateMainAreaRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateMainAreaInput request) { + if (id == null) + { + const string errorMessage = "O ID da área principal não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.Update(id, request) as DetailedReadMainAreaResponse; - _logger.LogInformation("Área principal atualizada: {id}", model?.Id); - return Ok(model); + var updatedMainArea = await _update.ExecuteAsync(id.Value, request); + if (updatedMainArea == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Área principal atualizada: {id}", updatedMainArea?.Id); + return Ok(updatedMainArea); } catch (Exception ex) { @@ -132,22 +187,33 @@ public async Task> Update(Guid? id, [ /// /// Área principal removida /// Retorna área principal removida + /// Requisição incorreta. + /// Usuário não autorizado. + /// Área principal não encontrada. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedMainAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID da área principal não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadMainAreaResponse; - _logger.LogInformation("Área principal removida: {id}", model?.Id); - return Ok(model); + var deletedMainArea = await _delete.ExecuteAsync(id.Value); + if (deletedMainArea == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Área principal removida: {id}", deletedMainArea?.Id); + return Ok(deletedMainArea); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/NoticeController.cs b/src/Infrastructure/WebAPI/Controllers/NoticeController.cs index 4d4c26c6..dced1552 100644 --- a/src/Infrastructure/WebAPI/Controllers/NoticeController.cs +++ b/src/Infrastructure/WebAPI/Controllers/NoticeController.cs @@ -1,32 +1,49 @@ -using Adapters.Gateways.Notice; -using Adapters.Interfaces; +using Application.Ports.Notice; +using Application.Interfaces.UseCases.Notice; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Edital. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class NoticeController : ControllerBase { #region Global Scope - private readonly INoticePresenterController _service; + private readonly IGetNoticeById _getById; + private readonly IGetNotices _getAll; + private readonly ICreateNotice _create; + private readonly IUpdateNotice _update; + private readonly IDeleteNotice _delete; private readonly ILogger _logger; /// /// Construtor do Controller de Edital. /// - /// - /// - public NoticeController(INoticePresenterController service, ILogger logger) + /// Serviço de obtenção de edital pelo id. + /// Serviço de obtenção de todos os editais ativos. + /// Serviço de criação de edital. + /// Serviço de atualização de edital. + /// Serviço de remoção de edital. + /// Serviço de log. + public NoticeController(IGetNoticeById getById, + IGetNotices getAll, + ICreateNotice create, + IUpdateNotice update, + IDeleteNotice delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca edital pelo id. @@ -34,27 +51,37 @@ public NoticeController(INoticePresenterController service, ILogger /// Edital correspondente /// Retorna edital correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Edital não encontrado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadNoticeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do edital não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Edital encontrado para o id {id}.", id); - return Ok(model); + var notice = await _getById.ExecuteAsync(id.Value); + if (notice == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Edital encontrado para o ID {id}.", id); + return Ok(notice); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -64,19 +91,25 @@ public async Task> GetById(Guid? id) /// /// Todas os editais ativos /// Retorna todas os editais ativos + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhum edital encontrado. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + var notices = await _getAll.ExecuteAsync(skip, take); + if (notices == null || !notices.Any()) { - const string msg = "Nenhum Edital encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhum edital encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Editais encontrados: {quantidade}", models.Count()); - return Ok(models); + _logger.LogInformation("Editais encontrados: {quantidade}", notices.Count()); + return Ok(notices); } /// @@ -84,17 +117,21 @@ public async Task>> GetAll(i /// /// /// Edital criado - /// Retorna edital criado + /// Retorna edital criado + /// Requisição incorreta. + /// Usuário não autorizado. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadNoticeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromForm] CreateNoticeRequest request) + public async Task> Create([FromForm] CreateNoticeInput request) { try { - var model = await _service.Create(request) as DetailedReadNoticeResponse; - _logger.LogInformation("Edital criado: {id}", model?.Id); - return Ok(model); + var createdNotice = await _create.ExecuteAsync(request); + _logger.LogInformation("Edital criado: {id}", createdNotice?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdNotice?.Id }, createdNotice); } catch (Exception ex) { @@ -109,15 +146,33 @@ public async Task> Create([FromForm] Cr /// /// Edital atualizado /// Retorna edital atualizado + /// Requisição incorreta. + /// Usuário não autorizado. + /// Edital não encontrado. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadNoticeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromForm] UpdateNoticeRequest request) + public async Task> Update(Guid? id, [FromForm] UpdateNoticeInput request) { + if (id == null) + { + const string errorMessage = "O ID do edital não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.Update(id, request) as DetailedReadNoticeResponse; - _logger.LogInformation("Edital atualizado: {id}", model?.Id); - return Ok(model); + var updatedNotice = await _update.ExecuteAsync(id.Value, request); + if (updatedNotice == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Edital atualizado: {id}", updatedNotice?.Id); + return Ok(updatedNotice); } catch (Exception ex) { @@ -132,22 +187,33 @@ public async Task> Update(Guid? id, [Fr /// /// Edital removido /// Retorna edital removido + /// Requisição incorreta. + /// Usuário não autorizado. + /// Edital não encontrado. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadNoticeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do edital não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadNoticeResponse; - _logger.LogInformation("Edital removido: {id}", model?.Id); - return Ok(model); + var deletedNotice = await _delete.ExecuteAsync(id.Value); + if (deletedNotice == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Edital removido: {id}", deletedNotice?.Id); + return Ok(deletedNotice); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/ProfessorController.cs b/src/Infrastructure/WebAPI/Controllers/ProfessorController.cs index 4cd26eca..fbac9beb 100644 --- a/src/Infrastructure/WebAPI/Controllers/ProfessorController.cs +++ b/src/Infrastructure/WebAPI/Controllers/ProfessorController.cs @@ -1,32 +1,50 @@ -using Adapters.Gateways.Professor; -using Adapters.Interfaces; +using Application.Ports.Professor; +using Application.Interfaces.UseCases.Professor; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Professor. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class ProfessorController : ControllerBase { #region Global Scope - private readonly IProfessorPresenterController _service; + private readonly IGetProfessorById _getById; + private readonly IGetProfessors _getAll; + private readonly ICreateProfessor _create; + private readonly IUpdateProfessor _update; + private readonly IDeleteProfessor _delete; private readonly ILogger _logger; + /// /// Construtor do Controller de Professor. /// - /// - /// - public ProfessorController(IProfessorPresenterController service, ILogger logger) + /// Serviço de obtenção de professor pelo id. + /// Serviço de obtenção de todos os professores ativos. + /// Serviço de criação de professor. + /// Serviço de atualização de professor. + /// Serviço de remoção de professor. + /// Serviço de log. + public ProfessorController(IGetProfessorById getById, + IGetProfessors getAll, + ICreateProfessor create, + IUpdateProfessor update, + IDeleteProfessor delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca Professor pelo id. @@ -34,27 +52,37 @@ public ProfessorController(IProfessorPresenterController service, ILogger /// Professor correspondente /// Retorna Professor correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Professor não encontrado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProfessorOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do professor não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Professor encontrado para o id {id}.", id); - return Ok(model); + var professor = await _getById.ExecuteAsync(id.Value); + if (professor == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Professor encontrado para o ID {id}.", id); + return Ok(professor); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -64,19 +92,25 @@ public async Task> GetById(Guid? id) /// /// Todos os Professor ativos /// Retorna todos os Professor ativos + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhum Professor encontrado. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + var professors = await _getAll.ExecuteAsync(skip, take); + if (professors == null || !professors.Any()) { - const string msg = "Nenhum Professor encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhum professor encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Professor encontrados: {quantidade}", models.Count()); - return Ok(models); + _logger.LogInformation("Professores encontrados: {quantidade}", professors.Count()); + return Ok(professors); } /// @@ -84,17 +118,19 @@ public async Task>> GetAl /// /// /// Professor criado - /// Retorna Professor criado + /// Retorna Professor criado + /// Requisição incorreta. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadProfessorOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] [AllowAnonymous] - public async Task> Create([FromBody] CreateProfessorRequest request) + public async Task> Create([FromBody] CreateProfessorInput request) { try { - var model = await _service.Create(request) as DetailedReadProfessorResponse; - _logger.LogInformation("Professor criado: {id}", model?.Id); - return Ok(model); + var createdProfessor = await _create.ExecuteAsync(request); + _logger.LogInformation("Professor criado: {id}", createdProfessor?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdProfessor?.Id }, createdProfessor); } catch (Exception ex) { @@ -109,15 +145,33 @@ public async Task> Create([FromBody] /// /// Professor atualizado /// Retorna Professor atualizado + /// Requisição incorreta. + /// Usuário não autorizado. + /// Professor não encontrado. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProfessorOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN, PROFESSOR")] - public async Task> Update(Guid? id, [FromBody] UpdateProfessorRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateProfessorInput request) { + if (id == null) + { + const string errorMessage = "O ID do professor não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.Update(id, request) as DetailedReadProfessorResponse; - _logger.LogInformation("Professor atualizado: {id}", model?.Id); - return Ok(model); + var updatedProfessor = await _update.ExecuteAsync(id.Value, request); + if (updatedProfessor == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Professor atualizado: {id}", updatedProfessor?.Id); + return Ok(updatedProfessor); } catch (Exception ex) { @@ -132,22 +186,33 @@ public async Task> Update(Guid? id, /// /// Professor removido /// Retorna Professor removido + /// Requisição incorreta. + /// Usuário não autorizado. + /// Professor não encontrado. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProfessorOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN, PROFESSOR")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do professor não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadProfessorResponse; - _logger.LogInformation("Professor removido: {id}", model?.Id); - return Ok(model); + var deletedProfessor = await _delete.ExecuteAsync(id.Value); + if (deletedProfessor == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Professor removido: {id}", deletedProfessor?.Id); + return Ok(deletedProfessor); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/ProgramTypeController.cs b/src/Infrastructure/WebAPI/Controllers/ProgramTypeController.cs index 1ea5efe3..1815d3c1 100644 --- a/src/Infrastructure/WebAPI/Controllers/ProgramTypeController.cs +++ b/src/Infrastructure/WebAPI/Controllers/ProgramTypeController.cs @@ -1,60 +1,88 @@ -using Adapters.Gateways.ProgramType; -using Adapters.Interfaces; +using Application.Ports.ProgramType; +using Application.Interfaces.UseCases.ProgramType; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Tipo de Programa. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class ProgramTypeController : ControllerBase { #region Global Scope - private readonly IProgramTypePresenterController _service; + private readonly IGetProgramTypeById _getById; + private readonly IGetProgramTypes _getAll; + private readonly ICreateProgramType _create; + private readonly IUpdateProgramType _update; + private readonly IDeleteProgramType _delete; private readonly ILogger _logger; + /// /// Construtor do Controller de Tipo de Programa. /// - /// - /// - public ProgramTypeController(IProgramTypePresenterController service, ILogger logger) + /// Serviço de obtenção de tipo de programa pelo id. + /// Serviço de obtenção de todos os tipos de programas ativos. + /// Serviço de criação de tipo de programa. + /// Serviço de atualização de tipo de programa. + /// Serviço de remoção de tipo de programa. + /// Serviço de log. + public ProgramTypeController(IGetProgramTypeById getById, + IGetProgramTypes getAll, + ICreateProgramType create, + IUpdateProgramType update, + IDeleteProgramType delete, + ILogger logger) { - _service = service; + _getById = getById; + _getAll = getAll; + _create = create; + _update = update; + _delete = delete; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca tipo de programa pelo id. /// /// /// Tipo de Programa correspondente - /// Retorna tipo de programa correspondente + /// Retorna Tipo de Programa correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Tipo de Programa não encontrado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProgramTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do tipo de programa não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Tipo de Programa encontrado para o id {id}.", id); - return Ok(model); + var programType = await _getById.ExecuteAsync(id.Value); + if (programType == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Tipo de programa encontrado para o ID {id}.", id); + return Ok(programType); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); } } @@ -64,19 +92,25 @@ public async Task> GetById(Guid? i /// /// Todas os tipos de programas ativos /// Retorna todas os tipos de programas ativos + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhum tipo de programa encontrado. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + var programTypes = await _getAll.ExecuteAsync(skip, take); + if (programTypes == null || !programTypes.Any()) { - const string msg = "Nenhum Tipo de Programa encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhum tipo de programa encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Tipos de Programas encontrados: {quantidade}", models.Count()); - return Ok(models); + _logger.LogInformation("Tipos de programa encontrados: {quantidade}", programTypes.Count()); + return Ok(programTypes); } /// @@ -84,17 +118,19 @@ public async Task>> Get /// /// /// Tipo de Programa criado - /// Retorna tipo de programa criado + /// Retorna tipo de programa criado + /// Requisição incorreta. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadProgramTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateProgramTypeRequest request) + public async Task> Create([FromBody] CreateProgramTypeInput request) { try { - var model = await _service.Create(request) as DetailedReadProgramTypeResponse; - _logger.LogInformation("Tipo de Programa criado: {id}", model?.Id); - return Ok(model); + var createdProgramType = await _create.ExecuteAsync(request); + _logger.LogInformation("Tipo de programa criado: {id}", createdProgramType?.Id); + return CreatedAtAction(nameof(GetById), new { id = createdProgramType?.Id }, createdProgramType); } catch (Exception ex) { @@ -109,15 +145,33 @@ public async Task> Create([FromBod /// /// Tipo de Programa atualizado /// Retorna tipo de programa atualizado + /// Requisição incorreta. + /// Usuário não autorizado. + /// Tipo de Programa não encontrado. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProgramTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateProgramTypeRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateProgramTypeInput request) { + if (id == null) + { + const string errorMessage = "O ID do tipo de programa não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.Update(id, request) as DetailedReadProgramTypeResponse; - _logger.LogInformation("Tipo de Programa atualizado: {id}", model?.Id); - return Ok(model); + var updatedProgramType = await _update.ExecuteAsync(id.Value, request); + if (updatedProgramType == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Tipo de programa atualizado: {id}", updatedProgramType?.Id); + return Ok(updatedProgramType); } catch (Exception ex) { @@ -132,22 +186,33 @@ public async Task> Update(Guid? id /// /// Tipo de Programa removido /// Retorna tipo de programa removido + /// Requisição incorreta. + /// Usuário não autorizado. + /// Tipo de Programa não encontrado. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProgramTypeOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do tipo de programa não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.Delete(id.Value) as DetailedReadProgramTypeResponse; - _logger.LogInformation("Tipo de Programa removido: {id}", model?.Id); - return Ok(model); + var deletedProgramType = await _delete.ExecuteAsync(id.Value); + if (deletedProgramType == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Tipo de programa removido: {id}", deletedProgramType?.Id); + return Ok(deletedProgramType); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/ProjectController.cs b/src/Infrastructure/WebAPI/Controllers/ProjectController.cs index f222e3d9..d9f10198 100644 --- a/src/Infrastructure/WebAPI/Controllers/ProjectController.cs +++ b/src/Infrastructure/WebAPI/Controllers/ProjectController.cs @@ -1,35 +1,67 @@ -using Adapters.Gateways.Project; -using Adapters.Interfaces; +using Application.Ports.Project; +using Application.Interfaces.UseCases.Project; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Application.Ports.ProjectActivity; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de projetos. /// [ApiController] - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class ProjectController : ControllerBase { #region Global Scope - private readonly IProjectPresenterController _service; + private readonly IAppealProject _appealProject; + private readonly ICancelProject _cancelProject; + private readonly IGetClosedProjects _getClosedProjects; + private readonly IGetOpenProjects _getOpenProjects; + private readonly IGetProjectById _getProjectById; + private readonly IOpenProject _openProject; + private readonly ISubmitProject _submitProject; + private readonly IUpdateProject _updateProject; + private readonly IGetActivitiesByProjectId _getActivitiesByProjectId; private readonly ILogger _logger; /// /// Construtor do Controller de projetos. /// - /// - /// - public ProjectController( - IProjectPresenterController service, + /// Serviço de obtenção de projeto pelo id. + /// Serviço de obtenção de projetos abertos. + /// Serviço de obtenção de projetos fechados. + /// Serviço de abertura de projeto. + /// Serviço de atualização de projeto. + /// Serviço de cancelamento de projeto. + /// Serviço de recurso de projeto. + /// Serviço de submissão de projeto. + /// Serviço de obtenção de atividades de projeto. + /// Serviço de log. + public ProjectController(IGetProjectById getProjectById, + IGetOpenProjects getOpenProjects, + IGetClosedProjects getClosedProjects, + IOpenProject openProject, + IUpdateProject updateProject, + ICancelProject cancelProject, + IAppealProject appealProject, + ISubmitProject submitProject, + IGetActivitiesByProjectId getActivitiesByProjectId, ILogger logger) { - _service = service; + _getProjectById = getProjectById; + _getOpenProjects = getOpenProjects; + _getClosedProjects = getClosedProjects; + _openProject = openProject; + _updateProject = updateProject; + _cancelProject = cancelProject; + _appealProject = appealProject; + _submitProject = submitProject; + _getActivitiesByProjectId = getActivitiesByProjectId; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca projeto pelo id. @@ -37,77 +69,124 @@ public ProjectController( /// /// Projeto correspondente /// Retorna projeto correspondente + /// Requisição incorreta. + /// Usuário não autorizado. /// Nenhum projeto encontrado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetProjectById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetProjectById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.GetProjectById(id); - _logger.LogInformation("Projeto encontrado para o id {id}.", id); - return Ok(model); + var project = await _getProjectById.ExecuteAsync(id.Value); + if (project == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Projeto encontrado para o ID {id}.", id); + return Ok(project); } catch (Exception ex) { _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Busca atividades de projeto pelo id do projeto. + /// + /// Id do projeto + /// Atividades de projeto correspondentes + /// Retorna atividades de projeto correspondentes + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhuma atividade encontrada. + [HttpGet("activity/{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetActivitiesByProjectId(Guid? projectId) + { + var activities = await _getActivitiesByProjectId.ExecuteAsync(projectId); + if (activities == null || !activities.Any()) + { + const string errorMessage = "Nenhuma atividade encontrada."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } + _logger.LogInformation("Atividades encontradas: {quantidade}", activities.Count()); + return Ok(activities); } + /// /// Busca projetos abertos. /// - /// - /// - /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Indica que apenas os projetos relacionados ao usuário serão retornados. /// Projetos abertos do usuário logado. /// Retorna projetos abertos do usuário logado. + /// Ocorreu um erro ao buscar projetos abertos. + /// Usuário não autorizado. /// Nenhum projeto encontrado. [HttpGet("opened")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetOpenProjects(int skip = 0, int take = 50, bool onlyMyProjects = true) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetOpenProjects(int skip = 0, int take = 50, bool onlyMyProjects = true) { - var models = await _service.GetOpenProjects(skip, take, onlyMyProjects); - if (models == null) + var projects = await _getOpenProjects.ExecuteAsync(skip, take, onlyMyProjects); + if (projects == null || !projects.Any()) { - const string msg = "Nenhum Projeto encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhum projeto aberto encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Projetos encontrados: {quantidade}", models.Count); - return Ok(models); + _logger.LogInformation("Projetos abertos encontrados: {quantidade}", projects.Count); + return Ok(projects); } /// /// Busca projetos fechados. /// - /// - /// - /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Indica que apenas os projetos relacionados ao usuário serão retornados. /// Projetos fechados do usuário logado. /// Retorna projetos fechados do usuário logado. + /// Ocorreu um erro ao buscar projetos fechados. + /// Usuário não autorizado. /// Nenhum projeto encontrado. [HttpGet("closed")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetClosedProjects(int skip = 0, int take = 50, bool onlyMyProjects = true) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetClosedProjects(int skip = 0, int take = 50, bool onlyMyProjects = true) { - var models = await _service.GetClosedProjects(skip, take, onlyMyProjects); - if (models == null) + var projects = await _getClosedProjects.ExecuteAsync(skip, take, onlyMyProjects); + if (projects == null || !projects.Any()) { - const string msg = "Nenhum Projeto encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + const string errorMessage = "Nenhum projeto fechado encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); } - _logger.LogInformation("Projetos encontrados: {quantidade}", models.Count); - return Ok(models); + _logger.LogInformation("Projetos fechados encontrados: {quantidade}", projects.Count); + return Ok(projects); } /// @@ -117,16 +196,18 @@ public async Task>> GetClos /// Projeto criado /// Retorna projeto criado /// Ocorreu um erro ao criar projeto. + /// Usuário não autorizado. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(ResumedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] [Authorize(Roles = "ADMIN, PROFESSOR")] - public async Task> OpenProject([FromBody] OpenProjectRequest request) + public async Task> OpenProject([FromBody] OpenProjectInput request) { try { - var model = await _service.OpenProject(request); - _logger.LogInformation("Projeto aberto: {id}", model?.Id); - return Ok(model); + var project = await _openProject.ExecuteAsync(request); + _logger.LogInformation("Projeto aberto: {id}", project?.Id); + return CreatedAtAction(nameof(GetProjectById), new { id = project?.Id }, project); } catch (Exception ex) { @@ -138,20 +219,37 @@ public async Task> OpenProject([FromBod /// /// Atualiza projeto. /// - /// - /// + /// Id do projeto + /// Informações de atualização do projeto /// Projeto atualizado /// Retorna projeto atualizado /// Ocorreu um erro ao atualizar projeto. - [HttpPut("{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> UpdateProject(Guid? id, [FromBody] UpdateProjectRequest request) + /// Usuário não autorizado. + /// Nenhum projeto encontrado. + [HttpPut("{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ResumedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> UpdateProject(Guid? projectId, [FromBody] UpdateProjectInput request) { + if (projectId == null) + { + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + try { - var model = await _service.UpdateProject(id, request); - _logger.LogInformation("Projeto atualizado: {id}", model?.Id); - return Ok(model); + var project = await _updateProject.ExecuteAsync(projectId.Value, request); + if (project == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Projeto atualizado: {id}", project?.Id); + return Ok(project); } catch (Exception ex) { @@ -163,27 +261,37 @@ public async Task> UpdateProject(Guid? /// /// Cancela projeto. /// - /// - /// + /// Id do projeto + /// Observação do cancelamento /// Projeto cancelado /// Retorna projeto cancelado /// Ocorreu um erro ao cancelar projeto. - [HttpDelete("{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> CancelProject(Guid? id, string? observation) + /// Usuário não autorizado. + /// Projeto não encontrado. + [HttpDelete("{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ResumedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> CancelProject(Guid? projectId, string? observation) { - if (id == null) + if (projectId == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.CancelProject(id.Value, observation); - _logger.LogInformation("Projeto removido: {id}", model?.Id); - return Ok(model); + var project = await _cancelProject.ExecuteAsync(projectId.Value, observation); + if (project == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Projeto cancelado: {id}", project?.Id); + return Ok(project); } catch (Exception ex) { @@ -195,27 +303,31 @@ public async Task> CancelProject(Guid? /// /// Solicita recurso para o projeto. /// - /// - /// + /// Id do projeto + /// Descrição do recurso /// Projeto com recurso solicitado /// Retorna projeto com recurso solicitado /// Ocorreu um erro ao solicitar recurso para o projeto. - [HttpPut("appeal/{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> AppealProject(Guid? id, string? appealDescription) + /// Usuário não autorizado. + [HttpPut("appeal/{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ResumedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> AppealProject(Guid? projectId, string? appealDescription) { - if (id == null) + if (projectId == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.AppealProject(id.Value, appealDescription); - _logger.LogInformation("Recurso do Projeto: {id}", model?.Id); - return Ok(model); + var project = await _appealProject.ExecuteAsync(projectId.Value, appealDescription); + _logger.LogInformation("Recurso do projeto solicitado: {id}", project?.Id); + return Ok(project); } catch (Exception ex) { @@ -227,26 +339,30 @@ public async Task> AppealProject(Guid? /// /// Submete projeto. /// - /// + /// Id do projeto /// Projeto submetido /// Retorna projeto submetido /// Ocorreu um erro ao submeter projeto. - [HttpPut("submit/{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> SubmitProject(Guid? id) + /// Usuário não autorizado. + [HttpPut("submit/{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ResumedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> SubmitProject(Guid? projectId) { - if (id == null) + if (projectId == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); } try { - var model = await _service.SubmitProject(id.Value); - _logger.LogInformation("Submissão do Projeto: {id}", model?.Id); - return Ok(model); + var project = await _submitProject.ExecuteAsync(projectId.Value); + _logger.LogInformation("Projeto submetido: {id}", project?.Id); + return Ok(project); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/ProjectEvaluationController.cs b/src/Infrastructure/WebAPI/Controllers/ProjectEvaluationController.cs new file mode 100644 index 00000000..110a8e3e --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/ProjectEvaluationController.cs @@ -0,0 +1,210 @@ +using Application.Ports.ProjectEvaluation; +using Application.Interfaces.UseCases.ProjectEvaluation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Application.Ports.Project; +using Application.Interfaces.UseCases.Project; + +namespace WebAPI.Controllers +{ + /// + /// Controller de projetos. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class ProjectEvaluationController : ControllerBase + { + #region Global Scope + private readonly IEvaluateAppealProject _evaluateAppealProject; + private readonly IEvaluateSubmissionProject _evaluateSubmissionProject; + private readonly IEvaluateStudentDocuments _evaluateStudentDocuments; + private readonly IGetEvaluationByProjectId _getEvaluationByProjectId; + private readonly IGetProjectsToEvaluate _getProjectsToEvaluate; + private readonly ILogger _logger; + + /// + /// Construtor do Controller de projetos. + /// + /// Serviço de avaliação de recurso de projeto. + /// Serviço de avaliação de submissão de projeto. + /// Serviço de avaliação de documentos do estudante. + /// Serviço de obtenção de avaliação de projeto pelo id do projeto. + /// Serviço de obtenção de projetos para avaliação. + /// Serviço de log. + public ProjectEvaluationController(IEvaluateAppealProject evaluateAppealProject, + IEvaluateSubmissionProject evaluateSubmissionProject, + IEvaluateStudentDocuments evaluateStudentDocuments, + IGetEvaluationByProjectId getEvaluationByProjectId, + IGetProjectsToEvaluate getProjectsToEvaluate, + ILogger logger) + { + _evaluateAppealProject = evaluateAppealProject; + _evaluateSubmissionProject = evaluateSubmissionProject; + _evaluateStudentDocuments = evaluateStudentDocuments; + _getEvaluationByProjectId = getEvaluationByProjectId; + _getProjectsToEvaluate = getProjectsToEvaluate; + _logger = logger; + } + #endregion Global Scope + + /// + /// Busca projetos em aberto e que se encontram no estágio de avaliação (Submetido, Recurso, Análise de Documentos). + /// + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Projetos para avaliação. + /// Retorna projetos para avaliação. + /// Ocorreu um erro ao buscar projetos para avaliação. + /// Usuário não autorizado. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task>> GetProjectsToEvaluate(int skip = 0, int take = 50) + { + try + { + var projects = await _getProjectsToEvaluate.ExecuteAsync(skip, take); + if (projects == null || !projects.Any()) + { + const string errorMessage = "Nenhum projeto para avaliação encontrado."; + _logger.LogWarning(errorMessage); + return NotFound(errorMessage); + } + _logger.LogInformation("Projetos para avaliação encontrados: {quantidade}", projects.Count); + return Ok(projects); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro ao buscar projetos para avaliação: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Busca avaliação do projeto pelo id do projeto. + /// + /// Id do projeto. + /// Avaliação do projeto correspondente + /// Retorna avaliação do projeto correspondente + /// Retorna mensagem de erro + /// Retorna mensagem de erro + /// Nenhum avaliação do projeto encontrado. + [HttpGet("{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectEvaluationOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetEvaluationByProjectId(Guid? projectId) + { + if (projectId == null) + { + const string errorMessage = "O ID do projeto não pode ser nulo."; + _logger.LogWarning(errorMessage); + return BadRequest(errorMessage); + } + + try + { + var evaluation = await _getEvaluationByProjectId.ExecuteAsync(projectId.Value); + if (evaluation == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Avaliação do projeto encontrada para o ID {id}.", projectId); + return Ok(evaluation); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Realiza a avaliação da submissão de um projeto. + /// + /// Dados da avaliação. + /// Projeto correspondente + /// Retorna avaliação do projeto correspondente + /// Retorna mensagem de erro + /// Retorna mensagem de erro + [HttpPost("submission")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> EvaluateSubmissionProject([FromBody] EvaluateSubmissionProjectInput request) + { + try + { + var evaluatedProject = await _evaluateSubmissionProject.ExecuteAsync(request); + _logger.LogInformation("Avaliação da submissão do projeto {id} realizada.", evaluatedProject?.Id); + return Ok(evaluatedProject); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Realiza a avaliação do recurso de um projeto. + /// + /// Dados da avaliação. + /// Projeto correspondente + /// Retorna avaliação do projeto correspondente + /// Retorna mensagem de erro + /// Retorna mensagem de erro + [HttpPut("appeal")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> EvaluateAppealProjectRequest([FromBody] EvaluateAppealProjectInput request) + { + try + { + var evaluatedProject = await _evaluateAppealProject.ExecuteAsync(request); + _logger.LogInformation("Avaliação do recurso do projeto {id} realizada.", evaluatedProject?.Id); + return Ok(evaluatedProject); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Realiza a avaliação dos documentos de um estudante. + /// + /// Dados da avaliação. + /// Projeto correspondente + /// Retorna avaliação do projeto correspondente + /// Retorna mensagem de erro + /// Retorna mensagem de erro + [HttpPut("documents")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadProjectOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> EvaluateStudentDocuments([FromBody] EvaluateStudentDocumentsInput request) + { + try + { + var evaluatedProject = await _evaluateStudentDocuments.ExecuteAsync(request); + _logger.LogInformation("Avaliação dos documentos do estudante do projeto {id} realizada.", evaluatedProject?.Id); + return Ok(evaluatedProject); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController copy.cs b/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController copy.cs new file mode 100644 index 00000000..712ac567 --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController copy.cs @@ -0,0 +1,229 @@ +using Application.Ports.ProjectPartialReport; +using Application.Interfaces.UseCases.ProjectPartialReport; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller de Relatório de Projeto. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class ProjectPartialReportController : ControllerBase + { + #region Global Scope + private readonly IGetProjectPartialReportById _getProjectPartialReportById; + private readonly IGetProjectPartialReportByProjectId _getProjectPartialReportByProjectId; + private readonly ICreateProjectPartialReport _createProjectPartialReport; + private readonly IUpdateProjectPartialReport _updateProjectPartialReport; + private readonly IDeleteProjectPartialReport _deleteProjectPartialReport; + private readonly ILogger _logger; + + /// + /// Construtor do Controller de Relatório de Projeto. + /// + /// Use Case de busca de relatório parcial de projeto por Id. + /// Use Case de busca de relatório parcial de projeto por Id do projeto. + /// Use Case de criação de relatório parcial de projeto. + /// Use Case de atualização de relatório parcial de projeto. + /// Use Case de remoção de relatório parcial de projeto. + /// Logger. + public ProjectPartialReportController(IGetProjectPartialReportById getProjectPartialReportById, + IGetProjectPartialReportByProjectId getProjectPartialReportByProjectId, + ICreateProjectPartialReport createProjectPartialReport, + IUpdateProjectPartialReport updateProjectPartialReport, + IDeleteProjectPartialReport deleteProjectPartialReport, + ILogger logger) + { + _getProjectPartialReportById = getProjectPartialReportById; + _getProjectPartialReportByProjectId = getProjectPartialReportByProjectId; + _createProjectPartialReport = createProjectPartialReport; + _updateProjectPartialReport = updateProjectPartialReport; + _deleteProjectPartialReport = deleteProjectPartialReport; + _logger = logger; + } + #endregion Global Scope + + /// + /// Busca relatório parcial de projeto pelo Id. + /// + /// Id do relatório parcial de projeto. + /// Relatório de projeto. + /// Relatório de projeto encontrado. + /// Id não informado. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpGet("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectPartialReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _getProjectPartialReportById.ExecuteAsync(id); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto encontrado para o ID {id}.", id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Busca relatório parcial de projeto pelo Id do projeto. + /// + /// Id do projeto. + /// Relatório parcial do projeto. + /// Relatórios de projeto encontrados. + /// Id não informado. + /// Usuário não autorizado. + /// Relatórios de projeto não encontrados. + [HttpGet("project/{projectId}")] + [ProducesResponseType(typeof(DetailedReadProjectPartialReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetByProjectId(Guid? projectId) + { + if (projectId == null) + { + return BadRequest("O ID do projeto informado não pode ser nulo."); + } + + try + { + var model = await _getProjectPartialReportByProjectId.ExecuteAsync(projectId); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto encontrado para o ID do projeto {projectId}.", projectId); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Cria relatório parcial de projeto. + /// + /// Dados para criação de relatório parcial de projeto. + /// Relatório de projeto criado. + /// Relatório de projeto criado. + /// Dados inválidos. + /// Usuário não autorizado. + [HttpPost] + [ProducesResponseType(typeof(DetailedReadProjectPartialReportOutput), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> Create([FromBody] CreateProjectPartialReportInput request) + { + try + { + var model = await _createProjectPartialReport.ExecuteAsync(request); + _logger.LogInformation("Relatório de Projeto criado: {id}", model?.Id); + return CreatedAtAction(nameof(GetById), new { id = model?.Id }, model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Atualiza relatório parcial de projeto. + /// + /// Id do relatório parcial de projeto. + /// Dados para atualização de relatório parcial de projeto. + /// Relatório de projeto atualizado. + /// Relatório de projeto atualizado. + /// Dados inválidos. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpPut("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectPartialReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Update(Guid? id, [FromBody] UpdateProjectPartialReportInput request) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _updateProjectPartialReport.ExecuteAsync(id.Value, request); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto atualizado: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Remove relatório parcial de projeto. + /// + /// Id do relatório parcial de projeto. + /// Relatório de projeto removido. + /// Relatório de projeto removido. + /// Id não informado. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpDelete("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectPartialReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Delete(Guid? id) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _deleteProjectPartialReport.ExecuteAsync(id.Value); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto removido: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController.cs b/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController.cs new file mode 100644 index 00000000..8313a448 --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/ProjectFinalReportController.cs @@ -0,0 +1,229 @@ +using Application.Ports.ProjectFinalReport; +using Application.Interfaces.UseCases.ProjectFinalReport; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller de Relatório de Projeto. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class ProjectFinalReportController : ControllerBase + { + #region Global Scope + private readonly IGetProjectFinalReportById _getProjectFinalReportById; + private readonly IGetProjectFinalReportByProjectId _getProjectFinalReportByProjectId; + private readonly ICreateProjectFinalReport _createProjectFinalReport; + private readonly IUpdateProjectFinalReport _updateProjectFinalReport; + private readonly IDeleteProjectFinalReport _deleteProjectFinalReport; + private readonly ILogger _logger; + + /// + /// Construtor do Controller de Relatório de Projeto. + /// + /// Use Case de busca de relatório final de projeto por Id. + /// Use Case de busca de relatório final de projeto por Id do projeto. + /// Use Case de criação de relatório final de projeto. + /// Use Case de atualização de relatório final de projeto. + /// Use Case de remoção de relatório final de projeto. + /// Logger. + public ProjectFinalReportController(IGetProjectFinalReportById getProjectFinalReportById, + IGetProjectFinalReportByProjectId getProjectFinalReportByProjectId, + ICreateProjectFinalReport createProjectFinalReport, + IUpdateProjectFinalReport updateProjectFinalReport, + IDeleteProjectFinalReport deleteProjectFinalReport, + ILogger logger) + { + _getProjectFinalReportById = getProjectFinalReportById; + _getProjectFinalReportByProjectId = getProjectFinalReportByProjectId; + _createProjectFinalReport = createProjectFinalReport; + _updateProjectFinalReport = updateProjectFinalReport; + _deleteProjectFinalReport = deleteProjectFinalReport; + _logger = logger; + } + #endregion Global Scope + + /// + /// Busca relatório final de projeto pelo Id. + /// + /// Id do relatório final de projeto. + /// Relatório de projeto. + /// Relatório de projeto encontrado. + /// Id não informado. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpGet("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectFinalReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _getProjectFinalReportById.ExecuteAsync(id); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto encontrado para o ID {id}.", id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Busca relatório final de projeto pelo Id do projeto. + /// + /// Id do projeto. + /// Relatório final do projeto. + /// Relatórios de projeto encontrados. + /// Id não informado. + /// Usuário não autorizado. + /// Relatórios de projeto não encontrados. + [HttpGet("project/{projectId}")] + [ProducesResponseType(typeof(DetailedReadProjectFinalReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetByProjectId(Guid? projectId) + { + if (projectId == null) + { + return BadRequest("O ID do projeto informado não pode ser nulo."); + } + + try + { + var model = await _getProjectFinalReportByProjectId.ExecuteAsync(projectId); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto encontrado para o ID do projeto {projectId}.", projectId); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Cria relatório final de projeto. + /// + /// Dados para criação de relatório final de projeto. + /// Relatório de projeto criado. + /// Relatório de projeto criado. + /// Dados inválidos. + /// Usuário não autorizado. + [HttpPost] + [ProducesResponseType(typeof(DetailedReadProjectFinalReportOutput), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> Create([FromBody] CreateProjectFinalReportInput request) + { + try + { + var model = await _createProjectFinalReport.ExecuteAsync(request); + _logger.LogInformation("Relatório de Projeto criado: {id}", model?.Id); + return CreatedAtAction(nameof(GetById), new { id = model?.Id }, model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Atualiza relatório final de projeto. + /// + /// Id do relatório final de projeto. + /// Dados para atualização de relatório final de projeto. + /// Relatório de projeto atualizado. + /// Relatório de projeto atualizado. + /// Dados inválidos. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpPut("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectFinalReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Update(Guid? id, [FromBody] UpdateProjectFinalReportInput request) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _updateProjectFinalReport.ExecuteAsync(id.Value, request); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto atualizado: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Remove relatório final de projeto. + /// + /// Id do relatório final de projeto. + /// Relatório de projeto removido. + /// Relatório de projeto removido. + /// Id não informado. + /// Usuário não autorizado. + /// Relatório de projeto não encontrado. + [HttpDelete("{id}")] + [ProducesResponseType(typeof(DetailedReadProjectFinalReportOutput), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Delete(Guid? id) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _deleteProjectFinalReport.ExecuteAsync(id.Value); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Relatório de Projeto removido: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/StudentController.cs b/src/Infrastructure/WebAPI/Controllers/StudentController.cs index ff916d32..2edac6cd 100644 --- a/src/Infrastructure/WebAPI/Controllers/StudentController.cs +++ b/src/Infrastructure/WebAPI/Controllers/StudentController.cs @@ -1,32 +1,58 @@ -using Adapters.Gateways.Student; -using Adapters.Interfaces; +using Application.Ports.Student; +using Application.Interfaces.UseCases.Student; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Estudante. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class StudentController : ControllerBase { #region Global Scope - private readonly IStudentPresenterController _service; + private readonly IGetStudentById _getStudentById; + private readonly IGetStudentByRegistrationCode _getStudentByRegistrationCode; + private readonly IGetStudents _getAllStudents; + private readonly ICreateStudent _createStudent; + private readonly IUpdateStudent _updateStudent; + private readonly IDeleteStudent _deleteStudent; + private readonly IRequestStudentRegister _requestStudentRegister; private readonly ILogger _logger; + /// /// Construtor do Controller de Estudante. /// - /// - /// - public StudentController(IStudentPresenterController service, ILogger logger) + /// Obtém Estudante pelo id + /// Obtém Estudante pela matrícula + /// Obtém todos os Estudantes + /// Cria Estudante + /// Atualiza Estudante + /// Remove Estudante + /// Solicita registro de Estudante + /// Logger + public StudentController(IGetStudentById getStudentById, + IGetStudentByRegistrationCode getStudentByRegistrationCode, + IGetStudents getAllStudents, + ICreateStudent createStudent, + IUpdateStudent updateStudent, + IDeleteStudent deleteStudent, + IRequestStudentRegister requestStudentRegister, + ILogger logger) { - _service = service; + _getStudentById = getStudentById; + _getStudentByRegistrationCode = getStudentByRegistrationCode; + _getAllStudents = getAllStudents; + _createStudent = createStudent; + _updateStudent = updateStudent; + _deleteStudent = deleteStudent; + _requestStudentRegister = requestStudentRegister; _logger = logger; } - #endregion + #endregion Global Scope /// /// Busca Estudante pelo id. @@ -34,22 +60,68 @@ public StudentController(IStudentPresenterController service, ILogger /// Estudante correspondente /// Retorna Estudante correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Estudante não encontrado. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var student = await _getStudentById.ExecuteAsync(id.Value); + if (student == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Estudante encontrado para o ID {id}.", id); + return Ok(student); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Busca Estudante pelo id. + /// + /// + /// Estudante correspondente + /// Retorna Estudante correspondente + /// Requisição incorreta. + /// Usuário não autorizado. + /// Estudante não encontrado. + [HttpGet("RegistrationCode/{registrationCode}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetByRegistrationCode(string? registrationCode) + { + if (string.IsNullOrWhiteSpace(registrationCode)) + { + return BadRequest("A matrícula informada não pode ser nula ou vazia."); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Estudante encontrado para o id {id}.", id); - return Ok(model); + var student = await _getStudentByRegistrationCode.ExecuteAsync(registrationCode); + if (student == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Estudante encontrado para a matrícula {registrationCode}.", registrationCode); + return Ok(student); } catch (Exception ex) { @@ -58,25 +130,68 @@ public async Task> GetById(Guid? id) } } + /// + /// Solicita registro de Estudante através do e-mail. + /// + /// E-mail do estudante + /// Informa se o envio do e-mail foi bem sucedido + /// E-mail enviado com sucesso + /// Requisição incorreta. + /// Usuário não autorizado. + /// Ocorreu um erro ao solicitar o registro do estudante. + [HttpPost("RequestRegister/{email}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [Authorize(Roles = "ADMIN, PROFESSOR")] + public async Task> RequestStudentRegister(string? email) + { + if (string.IsNullOrWhiteSpace(email)) + { + return BadRequest("O e-mail informado não pode ser nulo ou vazio."); + } + + try + { + string? message = await _requestStudentRegister.ExecuteAsync(email); + if (string.IsNullOrWhiteSpace(message)) + { + return StatusCode(StatusCodes.Status500InternalServerError, "Ocorreu um erro ao solicitar o registro do estudante."); + } + _logger.LogInformation("{Message}.", message); + return Ok(message); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + /// /// Busca todas os Estudante ativos. /// /// /// Todas os Estudante ativos /// Retorna todas os Estudante ativos + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhum Estudante encontrado. [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAll(int skip = 0, int take = 50) { - var models = await _service.GetAll(skip, take); - if (models == null) + var students = await _getAllStudents.ExecuteAsync(skip, take); + if (students == null || !students.Any()) { - const string msg = "Nenhum Estudante encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + return NotFound("Nenhum estudante encontrado."); } - _logger.LogInformation("Estudante encontrados: {quantidade}", models.Count()); - return Ok(models); + _logger.LogInformation("Estudantes encontrados: {quantidade}", students.Count()); + return Ok(students); } /// @@ -84,17 +199,20 @@ public async Task>> GetAll( /// /// /// Estudante criado - /// Retorna Estudante criado + /// Retorna Estudante criado + /// Requisição incorreta. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadStudentOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [AllowAnonymous] - public async Task> Create([FromBody] CreateStudentRequest request) + public async Task> Create([FromBody] CreateStudentInput request) { try { - var model = await _service.Create(request) as DetailedReadStudentResponse; - _logger.LogInformation("Estudante criado: {id}", model?.Id); - return Ok(model); + var student = await _createStudent.ExecuteAsync(request); + _logger.LogInformation("Estudante criado: {id}", student?.Id); + return CreatedAtAction(nameof(GetById), new { id = student?.Id }, student); } catch (Exception ex) { @@ -109,15 +227,31 @@ public async Task> Create([FromBody] C /// /// Estudante atualizado /// Retorna Estudante atualizado + /// Requisição incorreta. + /// Usuário não autorizado. + /// Nenhum Estudante encontrado. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN, STUDENT")] - public async Task> Update(Guid? id, [FromBody] UpdateStudentRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateStudentInput request) { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + try { - var model = await _service.Update(id, request) as DetailedReadStudentResponse; - _logger.LogInformation("Estudante atualizado: {id}", model?.Id); - return Ok(model); + var updatedStudent = await _updateStudent.ExecuteAsync(id.Value, request); + if (updatedStudent == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Estudante atualizado: {id}", updatedStudent?.Id); + return Ok(updatedStudent); } catch (Exception ex) { @@ -132,22 +266,31 @@ public async Task> Update(Guid? id, [F /// /// Estudante removido /// Retorna Estudante removido + /// Retorna mensagem de erro + /// Retorna mensagem de erro + /// Retorna mensagem de erro [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN, STUDENT")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.Delete(id.Value) as DetailedReadStudentResponse; - _logger.LogInformation("Estudante removido: {id}", model?.Id); - return Ok(model); + var deletedStudent = await _deleteStudent.ExecuteAsync(id.Value); + if (deletedStudent == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Estudante removido: {id}", deletedStudent?.Id); + return Ok(deletedStudent); } catch (Exception ex) { diff --git a/src/Infrastructure/WebAPI/Controllers/StudentDocumentsController.cs b/src/Infrastructure/WebAPI/Controllers/StudentDocumentsController.cs new file mode 100644 index 00000000..82f6ce45 --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/StudentDocumentsController.cs @@ -0,0 +1,227 @@ +using Application.Ports.StudentDocuments; +using Application.Interfaces.UseCases.StudentDocuments; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller de Documentos de Estudante. + /// + [ApiController] + [Route("api/v1/[controller]")] + [Authorize] + public class StudentDocumentsController : ControllerBase + { + #region Global Scope + private readonly IGetStudentDocumentsByProjectId _getStudentDocumentsByProjectId; + private readonly IGetStudentDocumentsByStudentId _getStudentDocumentsByStudentId; + private readonly ICreateStudentDocuments _createStudentDocuments; + private readonly IUpdateStudentDocuments _updateStudentDocuments; + private readonly IDeleteStudentDocuments _deleteStudentDocuments; + private readonly ILogger _logger; + + /// + /// Construtor do Controller de Documentos de Estudante. + /// + /// Use Case de busca de documentos de estudante pelo id do projeto. + /// Use Case de busca de documentos de estudante pelo id do estudante. + /// Use Case de adição de documentos de estudante. + /// Use Case de atualização de documentos de estudante. + /// Use Case de remoção de documentos de estudante. + /// Logger + public StudentDocumentsController(IGetStudentDocumentsByProjectId getStudentDocumentsByProjectId, + IGetStudentDocumentsByStudentId getStudentDocumentsByStudentId, + ICreateStudentDocuments createStudentDocuments, + IUpdateStudentDocuments updateStudentDocuments, + IDeleteStudentDocuments deleteStudentDocuments, + ILogger logger) + { + _getStudentDocumentsByProjectId = getStudentDocumentsByProjectId; + _getStudentDocumentsByStudentId = getStudentDocumentsByStudentId; + _createStudentDocuments = createStudentDocuments; + _updateStudentDocuments = updateStudentDocuments; + _deleteStudentDocuments = deleteStudentDocuments; + _logger = logger; + } + #endregion Global Scope + + /// + /// Busca documentos de estudante pelo id do projeto. + /// + /// Id do projeto + /// Documentos de estudante encontrados. + /// O id informado não pode ser nulo. + /// Usuário não autenticado. + /// Documentos de estudante não encontrados. + [HttpGet("project/{projectId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentDocumentsOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetByProjectId(Guid? projectId) + { + if (projectId == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _getStudentDocumentsByProjectId.ExecuteAsync(projectId); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Documentos encontrados para o projeto com ID {projectId}.", projectId); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Busca documentos de estudante pelo id do estudante. + /// + /// Id do estudante + /// Documentos de estudante encontrados. + /// O id informado não pode ser nulo. + /// Usuário não autenticado. + /// Documentos de estudante não encontrados. + [HttpGet("student/{studentId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentDocumentsOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetByStudentId(Guid? studentId) + { + if (studentId == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _getStudentDocumentsByStudentId.ExecuteAsync(studentId); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Documentos encontrados para o estudante com ID {studentId}.", studentId); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return NotFound(ex.Message); + } + } + + /// + /// Adiciona documentos de estudante ao projeto. + /// + /// Dados para adição dos documentos. + /// Documentos de estudante adicionados. + /// O id informado não pode ser nulo. + /// Usuário não autenticado. + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadStudentDocumentsOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [Authorize(Roles = "ADMIN, STUDENT")] + public async Task> Create([FromBody] CreateStudentDocumentsInput request) + { + try + { + var model = await _createStudentDocuments.ExecuteAsync(request); + _logger.LogInformation("Documentos do estudante adicionados: {id}", model?.Id); + return CreatedAtAction(nameof(GetByProjectId), new { projectId = model?.ProjectId }, model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Atualiza documentos de estudante. + /// + /// Id dos documentos do estudante + /// Dados para atualização dos documentos. + /// Documentos de estudante atualizados. + /// O id informado não pode ser nulo. + /// Usuário não autenticado. + /// Documentos de estudante não encontrados. + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentDocumentsOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + [Authorize(Roles = "ADMIN, STUDENT")] + public async Task> Update(Guid? id, [FromBody] UpdateStudentDocumentsInput request) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _updateStudentDocuments.ExecuteAsync(id.Value, request); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Documentos do estudante atualizados: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Remove documentos de estudante. + /// + /// Id dos documentos do estudante + /// Documentos de estudante removidos. + /// O id informado não pode ser nulo. + /// Usuário não autenticado. + /// Documentos de estudante não encontrados. + [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadStudentDocumentsOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + [Authorize(Roles = "ADMIN, STUDENT")] + public async Task> Delete(Guid? id) + { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var model = await _deleteStudentDocuments.ExecuteAsync(id.Value); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Documentos do estudante removidos: {id}", model?.Id); + return Ok(model); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/SubAreaController.cs b/src/Infrastructure/WebAPI/Controllers/SubAreaController.cs index b10d343a..ab4fa31d 100644 --- a/src/Infrastructure/WebAPI/Controllers/SubAreaController.cs +++ b/src/Infrastructure/WebAPI/Controllers/SubAreaController.cs @@ -1,54 +1,80 @@ -using Adapters.Gateways.SubArea; -using Adapters.Interfaces; +using Application.Ports.SubArea; +using Application.Interfaces.UseCases.SubArea; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Sub Área. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class SubAreaController : ControllerBase { #region Global Scope - private readonly ISubAreaPresenterController _service; + private readonly IGetSubAreaById _getSubAreaById; + private readonly IGetSubAreasByArea _getSubAreasByArea; + private readonly ICreateSubArea _createSubArea; + private readonly IUpdateSubArea _updateSubArea; + private readonly IDeleteSubArea _deleteSubArea; private readonly ILogger _logger; + /// /// Construtor do Controller de Sub Área. /// - /// - /// - public SubAreaController(ISubAreaPresenterController service, ILogger logger) + /// Obtém sub área pelo id. + /// Obtém todas as sub áreas ativas da área. + /// Cria sub área. + /// Atualiza sub área. + /// Remove sub área. + /// Logger. + public SubAreaController(IGetSubAreaById getSubAreaById, + IGetSubAreasByArea getSubAreasByArea, + ICreateSubArea createSubArea, + IUpdateSubArea updateSubArea, + IDeleteSubArea deleteSubArea, + ILogger logger) { - _service = service; + _getSubAreaById = getSubAreaById; + _getSubAreasByArea = getSubAreasByArea; + _createSubArea = createSubArea; + _updateSubArea = updateSubArea; + _deleteSubArea = deleteSubArea; _logger = logger; } - #endregion + #endregion Global Scope /// - /// Busca sub área pelo id. + /// Obtém sub área pelo id. /// - /// - /// Sub Área correspondente - /// Retorna sub área correspondente + /// Id da sub área. + /// Sub área. + /// Sub área encontrada. + /// Id não informado. + /// Usuário não autorizado. + /// Sub área não encontrada. [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadSubAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.GetById(id); - _logger.LogInformation("Sub Área encontrada para o id {id}.", id); + var model = await _getSubAreaById.ExecuteAsync(id); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Sub Área encontrada para o ID {id}.", id); return Ok(model); } catch (Exception ex) @@ -59,49 +85,65 @@ public async Task> GetById(Guid? id) } /// - /// Busca todas as sub áreas ativas pela área. + /// Obtém todas as sub áreas ativas da área. /// - /// - /// Todas as sub áreas ativas da área - /// Retorna todas as sub áreas ativas da área - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetSubAreasByArea(Guid? areaId, int skip = 0, int take = 50) + /// Id da área. + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Sub áreas. + /// Sub áreas encontradas. + /// Id não informado. + /// Usuário não autorizado. + /// Sub áreas não encontradas. + [HttpGet("area/{areaId}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetSubAreasByArea(Guid? areaId, int skip = 0, int take = 50) { if (areaId == null) { - const string msg = "O AreadId informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID da área informado não pode ser nulo."); } - var models = await _service.GetSubAreasByArea(areaId, skip, take); - if (models == null) + try + { + var models = await _getSubAreasByArea.ExecuteAsync(areaId, skip, take); + if (!models.Any()) + { + return NotFound("Nenhuma Sub Área encontrada."); + } + _logger.LogInformation("Sub Áreas encontradas: {quantidade}", models.Count()); + return Ok(models); + } + catch (Exception ex) { - const string msg = "Nenhuma Sub Área encontrada."; - _logger.LogWarning(msg); - return NotFound(msg); + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); } - _logger.LogInformation("Sub Áreas encontradas: {quantidade}", models.Count()); - return Ok(models); } /// /// Cria sub área. /// - /// - /// Sub Área criada - /// Retorna sub área criada + /// Dados da sub área. + /// Sub área criada. + /// Sub área criada. + /// Dados da sub área inválidos. + /// Usuário não autorizado. [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(DetailedReadSubAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateSubAreaRequest request) + public async Task> Create([FromBody] CreateSubAreaInput request) { try { - var model = await _service.Create(request) as DetailedReadSubAreaResponse; + var model = await _createSubArea.ExecuteAsync(request); _logger.LogInformation("Sub Área criada: {id}", model?.Id); - return Ok(model); + return CreatedAtAction(nameof(GetById), new { id = model?.Id }, model); } catch (Exception ex) { @@ -113,16 +155,33 @@ public async Task> Create([FromBody] C /// /// Atualiza sub área. /// - /// - /// Sub Área atualizada - /// Retorna sub área atualizada + /// Id da sub área. + /// Dados da sub área. + /// Sub área atualizada. + /// Sub área atualizada. + /// Dados da sub área inválidos. + /// Usuário não autorizado. + /// Sub área não encontrada. [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadSubAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateSubAreaRequest request) + public async Task> Update(Guid? id, [FromBody] UpdateSubAreaInput request) { + if (id == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + try { - var model = await _service.Update(id, request) as DetailedReadSubAreaResponse; + var model = await _updateSubArea.ExecuteAsync(id.Value, request); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } _logger.LogInformation("Sub Área atualizada: {id}", model?.Id); return Ok(model); } @@ -136,23 +195,32 @@ public async Task> Update(Guid? id, [F /// /// Remove sub área. /// - /// - /// Sub Área removida - /// Retorna sub área removida + /// Id da sub área. + /// Sub área removida. + /// Sub área removida. + /// Id não informado. + /// Usuário não autorizado. + /// Sub área não encontrada. [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DetailedReadSubAreaOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) + public async Task> Delete(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.Delete(id.Value) as DetailedReadSubAreaResponse; + var model = await _deleteSubArea.ExecuteAsync(id.Value); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } _logger.LogInformation("Sub Área removida: {id}", model?.Id); return Ok(model); } @@ -163,4 +231,4 @@ public async Task> Delete(Guid? id) } } } -} \ No newline at end of file +} diff --git a/src/Infrastructure/WebAPI/Controllers/TypeAssistanceController.cs b/src/Infrastructure/WebAPI/Controllers/TypeAssistanceController.cs deleted file mode 100644 index ae870ca4..00000000 --- a/src/Infrastructure/WebAPI/Controllers/TypeAssistanceController.cs +++ /dev/null @@ -1,159 +0,0 @@ -using Adapters.Gateways.TypeAssistance; -using Adapters.Interfaces; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Infrastructure.WebAPI.Controllers -{ - /// - /// Controller de Bolsa de Assistência. - /// - [ApiController] - [Route("Api/[controller]")] - [Authorize] - public class TypeAssistanceController : ControllerBase - { - #region Global Scope - private readonly ITypeAssistancePresenterController _service; - private readonly ILogger _logger; - /// - /// Construtor do Controller de Bolsa de Assistência. - /// - /// - /// - public TypeAssistanceController(ITypeAssistancePresenterController service, ILogger logger) - { - _service = service; - _logger = logger; - } - #endregion - - /// - /// Busca bolsa de assistência pelo id. - /// - /// - /// Bolsa de Assistência correspondente - /// Retorna bolsa de assistência correspondente - [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetById(Guid? id) - { - if (id == null) - { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); - } - - try - { - var model = await _service.GetById(id); - _logger.LogInformation("Bolsa de Assistência encontrado para o id {id}.", id); - return Ok(model); - } - catch (Exception ex) - { - _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return NotFound(ex.Message); - } - } - - /// - /// Busca todas as bolsas de assitência ativas. - /// - /// - /// Todas as bolsas de assitência ativas - /// Retorna todas as bolsas de assitência ativas - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAll(int skip = 0, int take = 50) - { - var models = await _service.GetAll(skip, take); - if (models == null) - { - const string msg = "Nenhum Bolsa de Assistência encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); - } - _logger.LogInformation("Tipos de Programas encontrados: {quantidade}", models.Count()); - return Ok(models); - } - - /// - /// Cria bolsa de assistência. - /// - /// - /// Bolsa de Assistência criado - /// Retorna bolsa de assistência criado - [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize(Roles = "ADMIN")] - public async Task> Create([FromBody] CreateTypeAssistanceRequest request) - { - try - { - var model = await _service.Create(request) as DetailedReadTypeAssistanceResponse; - _logger.LogInformation("Bolsa de Assistência criado: {id}", model?.Id); - return Ok(model); - } - catch (Exception ex) - { - _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return BadRequest(ex.Message); - } - } - - /// - /// Atualiza bolsa de assistência. - /// - /// - /// Bolsa de Assistência atualizado - /// Retorna bolsa de assistência atualizado - [HttpPut("{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> Update(Guid? id, [FromBody] UpdateTypeAssistanceRequest request) - { - try - { - var model = await _service.Update(id, request) as DetailedReadTypeAssistanceResponse; - _logger.LogInformation("Bolsa de Assistência atualizado: {id}", model?.Id); - return Ok(model); - } - catch (Exception ex) - { - _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return BadRequest(ex.Message); - } - } - - /// - /// Remove bolsa de assistência. - /// - /// - /// Bolsa de Assistência removido - /// Retorna bolsa de assistência removido - [HttpDelete("{id}")] - [Authorize(Roles = "ADMIN")] - public async Task> Delete(Guid? id) - { - if (id == null) - { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); - } - - try - { - var model = await _service.Delete(id.Value) as DetailedReadTypeAssistanceResponse; - _logger.LogInformation("Bolsa de Assistência removido: {id}", model?.Id); - return Ok(model); - } - catch (Exception ex) - { - _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); - return BadRequest(ex.Message); - } - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Controllers/UserController.cs b/src/Infrastructure/WebAPI/Controllers/UserController.cs index 5bc9dc5b..02400542 100644 --- a/src/Infrastructure/WebAPI/Controllers/UserController.cs +++ b/src/Infrastructure/WebAPI/Controllers/UserController.cs @@ -1,54 +1,92 @@ -using Adapters.Gateways.User; -using Adapters.Interfaces; +using Application.Ports.User; +using Application.Interfaces.UseCases.User; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Infrastructure.WebAPI.Controllers +namespace WebAPI.Controllers { /// /// Controller de Usuário. /// [ApiController] - [Route("Api/[controller]")] + [Route("api/v1/[controller]")] [Authorize] public class UserController : ControllerBase { #region Global Scope - private readonly IUserPresenterController _service; + private readonly IActivateUser _activateUser; + private readonly IDeactivateUser _deactivateUser; + private readonly IGetActiveUsers _getActiveUsers; + private readonly IGetInactiveUsers _getInactiveUsers; + private readonly IGetUserById _getUserById; + private readonly IUpdateUser _updateUser; + private readonly IMakeAdmin _makeAdmin; + private readonly IMakeCoordinator _makeCoordinator; private readonly ILogger _logger; + /// /// Construtor do Controller de Usuário. /// - /// - /// - public UserController(IUserPresenterController service, ILogger logger) + /// Ativa usuário. + /// Desativa usuário. + /// Obtém todos os usuários ativos. + /// Obtém todos os usuários inativos. + /// Obtém usuário pelo id. + /// Atualiza usuário. + /// Torna usuário administrador. + /// Torna usuário coordenador. + /// Logger. + public UserController(IActivateUser activateUser, + IDeactivateUser deactivateUser, + IGetActiveUsers getActiveUsers, + IGetInactiveUsers getInactiveUsers, + IGetUserById getUserById, + IUpdateUser updateUser, + IMakeAdmin makeAdmin, + IMakeCoordinator makeCoordinator, + ILogger logger) { - _service = service; + _activateUser = activateUser; + _deactivateUser = deactivateUser; + _getActiveUsers = getActiveUsers; + _getInactiveUsers = getInactiveUsers; + _getUserById = getUserById; + _updateUser = updateUser; + _makeAdmin = makeAdmin; + _makeCoordinator = makeCoordinator; _logger = logger; } - #endregion + #endregion Global Scope /// - /// Busca usuário pelo id. + /// Obtém usuário pelo id. /// - /// - /// Todos os usuários ativos - /// Retorna todos os usuários ativos + /// Id do usuário. + /// Usuário encontrado. + /// Usuário encontrado. + /// Id não informado. + /// Usuário não autorizado. + /// Usuário não encontrado. [HttpGet("{id}", Name = "GetUserById")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetById(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> GetById(Guid? id) { if (id == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.GetUserById(id); - _logger.LogInformation("Usuário encontrado para o id {id}.", id); + var model = await _getUserById.ExecuteAsync(id); + if (model == null) + { + return NotFound("Nenhum registro encontrado."); + } + _logger.LogInformation("Usuário encontrado para o ID {id}.", id); return Ok(model); } catch (Exception ex) @@ -59,62 +97,70 @@ public async Task>> GetById(Guid? id) } /// - /// Busca todos os usuários ativos. + /// Obtém todos os usuários ativos. /// - /// - /// Todos os usuários ativos - /// Retorna todos os usuários ativos + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Usuários ativos. + /// Usuários encontrados. + /// Usuário não autorizado. + /// Usuários não encontrados. [HttpGet("Active/", Name = "GetAllActiveUsers")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAllActive(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAllActive(int skip = 0, int take = 50) { - var models = await _service.GetActiveUsers(skip, take); - if (models == null) + var models = await _getActiveUsers.ExecuteAsync(skip, take); + if (!models.Any()) { - const string msg = "Nenhum usuário encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + return NotFound("Nenhum usuário encontrado."); } _logger.LogInformation("Usuários encontrados: {quantidade}", models.Count()); return Ok(models); } /// - /// Busca todos os usuários inativos. + /// Obtém todos os usuários inativos. /// - /// - /// Todos os usuários inativos - /// Retorna todos os usuários ativos + /// Quantidade de registros a serem ignorados. + /// Quantidade de registros a serem retornados. + /// Usuários inativos. + /// Usuários encontrados. + /// Usuário não autorizado. + /// Usuários não encontrados. [HttpGet("Inactive/", Name = "GetAllInactiveUsers")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAllInactive(int skip = 0, int take = 50) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task>> GetAllInactive(int skip = 0, int take = 50) { - var models = await _service.GetInactiveUsers(skip, take); - if (models == null) + var models = await _getInactiveUsers.ExecuteAsync(skip, take); + if (!models.Any()) { - const string msg = "Nenhum usuário encontrado."; - _logger.LogWarning(msg); - return NotFound(msg); + return NotFound("Nenhum usuário encontrado."); } _logger.LogInformation("Usuários encontrados: {quantidade}", models.Count()); return Ok(models); } /// - /// Realiza atualização do usuário logado. + /// Atualiza usuário autenticado. /// - /// - /// Retorna usuário atualizado - /// Retorna usuário atualizado - [HttpPut(Name = "UpdateUser")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Update([FromBody] UserUpdateRequest request) + /// Dados do usuário. + /// Usuário atualizado. + /// Usuário atualizado. + /// Dados inválidos. + /// Usuário não autorizado. + [HttpPut("{userId}", Name = "UpdateUser")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> Update([FromBody] UserUpdateInput request) { try { - // Atualiza o usuário e retorna o usuário atualizado - var model = await _service.UpdateUser(request); - + var model = await _updateUser.ExecuteAsync(request); _logger.LogInformation("Usuário atualizado: {id}", model?.Id); return Ok(model); } @@ -126,26 +172,30 @@ public async Task> Update([FromBody] UserUpdateRe } /// - /// Realiza reativação de usário. + /// Ativa usuário pelo Id. /// - /// - /// Retorna usuário reativado - /// Retorna usuário reativado - [HttpPut("Active/{id}", Name = "ActivateUser")] + /// Id do usuário. + /// Usuário ativado. + /// Usuário ativado. + /// Id não informado. + /// Usuário não autorizado. + /// Usuário não encontrado. + [HttpPut("Active/{userId}", Name = "ActivateUser")] [Authorize(Roles = "ADMIN")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Activate(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Activate(Guid? userId) { - if (id == null) + if (userId == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.ActivateUser(id.Value); + var model = await _activateUser.ExecuteAsync(userId.Value); _logger.LogInformation("Usuário ativado: {id}", model?.Id); return Ok(model); } @@ -157,26 +207,30 @@ public async Task> Activate(Guid? id) } /// - /// Realiza desativação de usário. + /// Desativa usuário pelo Id. /// - /// - /// Retorna usuário desativado - /// Retorna usuário desativado - [HttpPut("Inactive/{id}", Name = "DeactivateUser")] + /// Id do usuário. + /// Usuário desativado. + /// Usuário desativado. + /// Id não informado. + /// Usuário não autorizado. + /// Usuário não encontrado. + [HttpPut("Inactive/{userId}", Name = "DeactivateUser")] [Authorize(Roles = "ADMIN")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Deactivate(Guid? id) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))] + public async Task> Deactivate(Guid? userId) { - if (id == null) + if (userId == null) { - const string msg = "O id informado não pode ser nulo."; - _logger.LogWarning(msg); - return BadRequest(msg); + return BadRequest("O ID informado não pode ser nulo."); } try { - var model = await _service.DeactivateUser(id.Value); + var model = await _deactivateUser.ExecuteAsync(userId.Value); _logger.LogInformation("Usuário desativado: {id}", model?.Id); return Ok(model); } @@ -186,5 +240,71 @@ public async Task> Deactivate(Guid? id) return BadRequest(ex.Message); } } + + /// + /// Torna usuário administrador pelo Id. + /// + /// Id do usuário. + /// Resultado da operação. + /// Usuário administrador. + /// Id não informado. + /// Usuário não autorizado. + [HttpPut("Admin/{userId}", Name = "MakeAdmin")] + [Authorize(Roles = "ADMIN")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> MakeAdmin(Guid? userId) + { + if (userId == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var result = await _makeAdmin.ExecuteAsync(userId.Value); + _logger.LogInformation("Operação realizada: {Resultado}", result); + return Ok(result); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } + + /// + /// Torna usuário coordenador pelo Id. + /// + /// Id do usuário. + /// Resultado da operação. + /// Usuário coordenador. + /// Id não informado. + /// Usuário não autorizado. + [HttpPut("Coordinator/{userId}", Name = "MakeCoordinator")] + [Authorize(Roles = "ADMIN")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserReadOutput))] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] + public async Task> MakeCoordinator(Guid? userId) + { + if (userId == null) + { + return BadRequest("O ID informado não pode ser nulo."); + } + + try + { + var result = await _makeCoordinator.ExecuteAsync(userId.Value); + _logger.LogInformation("Operação realizada: {Resultado}", result); + return Ok(result); + } + catch (Exception ex) + { + _logger.LogError("Ocorreu um erro: {ErrorMessage}", ex.Message); + return BadRequest(ex.Message); + } + } } -} \ No newline at end of file +} diff --git a/src/Infrastructure/WebAPI/Controllers/VersionController.cs b/src/Infrastructure/WebAPI/Controllers/VersionController.cs new file mode 100644 index 00000000..952d7e88 --- /dev/null +++ b/src/Infrastructure/WebAPI/Controllers/VersionController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebAPI.Controllers +{ + /// + /// Controller de validação. + /// + [ApiController] + [Route("api/v1/[controller]")] + public class Version : ControllerBase + { + /// + /// Retorna a versão da API. + /// + [HttpGet] + public IActionResult Get() + { + return Ok(GetType()?.Assembly?.GetName()?.Version?.ToString() ?? "Versão não identificada."); + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Dockerfile b/src/Infrastructure/WebAPI/Dockerfile index 30d51704..e023e320 100644 --- a/src/Infrastructure/WebAPI/Dockerfile +++ b/src/Infrastructure/WebAPI/Dockerfile @@ -7,11 +7,12 @@ EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src -COPY ["Infrastructure/WebAPI/WebAPI.csproj", "Infrastructure/WebAPI/"] -COPY ["Infrastructure/IoC/IoC.csproj", "Infrastructure/IoC/"] -COPY ["Adapters/Adapters.csproj", "Adapters/"] COPY ["Domain/Domain.csproj", "Domain/"] +COPY ["Application/Application.csproj", "Application/"] +COPY ["Infrastructure/Services/Services.csproj", "Infrastructure/Services/"] COPY ["Infrastructure/Persistence/Persistence.csproj", "Infrastructure/Persistence/"] +COPY ["Infrastructure/IoC/IoC.csproj", "Infrastructure/IoC/"] +COPY ["Infrastructure/WebAPI/WebAPI.csproj", "Infrastructure/WebAPI/"] RUN dotnet restore "Infrastructure/WebAPI/WebAPI.csproj" COPY . . WORKDIR "/src/Infrastructure/WebAPI" diff --git a/src/Infrastructure/WebAPI/Middlewares/ExceptionHandlingMiddleware.cs b/src/Infrastructure/WebAPI/Middlewares/ExceptionHandlingMiddleware.cs new file mode 100644 index 00000000..8f2c697f --- /dev/null +++ b/src/Infrastructure/WebAPI/Middlewares/ExceptionHandlingMiddleware.cs @@ -0,0 +1,61 @@ +using System.Net; +using System.Text.Json; + +namespace WebAPI.Middlewares +{ + /// + /// Middleware para tratamento de exceções. + /// + public class ExceptionHandlingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Construtor do middleware. + /// + /// + /// + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Método de invocação do middleware. + /// + /// + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception occurred."); + + // Handle the exception and generate a response + await HandleExceptionAsync(context, ex); + } + } + + private static Task HandleExceptionAsync(HttpContext context, Exception exception) + { + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + + var response = new + { + error = new + { + message = "An error occurred.", + details = exception.Message, + } + }; + + return context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } + } +} diff --git a/src/Infrastructure/WebAPI/Program.cs b/src/Infrastructure/WebAPI/Program.cs index 0b7531cb..7639d45b 100644 --- a/src/Infrastructure/WebAPI/Program.cs +++ b/src/Infrastructure/WebAPI/Program.cs @@ -1,15 +1,20 @@ -namespace Infrastructure.WebAPI; -/// -/// Classe de iniciação da WebAPI. -/// -public static class Program +namespace WebAPI { /// - /// Método principal da WebAPI. + /// Classe de iniciação da WebAPI. /// - public static void Main(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) - .Build() - .Run(); + public static class Program + { + /// + /// Método principal da WebAPI. + /// + /// + public static void Main(string[] args) + { + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) + .Build() + .Run(); + } + } } \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/Startup.cs b/src/Infrastructure/WebAPI/Startup.cs index 5c7a1f78..4036ebbb 100644 --- a/src/Infrastructure/WebAPI/Startup.cs +++ b/src/Infrastructure/WebAPI/Startup.cs @@ -1,71 +1,134 @@ using AspNetCoreRateLimit; using Infrastructure.IoC; +using WebAPI.Middlewares; -namespace Infrastructure.WebAPI; - -/// -/// Classe de iniciação da WebAPI. -/// -public class Startup +namespace WebAPI { /// - /// Realiza a configuração dos serviços de injeção de dependência. + /// Classe de iniciação da WebAPI. /// - public void ConfigureServices(IServiceCollection services) + public class Startup { - // Adição dos Controllers - services.AddControllers(); + private const string CORS_POLICY_NAME = "_allowSpecificOrigins"; + private IConfiguration? _configuration; - // Realiza comunicação com os demais Projetos. - services.AddInfrastructure(); - services.AddAdapters(); - services.AddDomain(); + /// + /// Realiza a configuração dos serviços de injeção de dependência. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + // Adição dos Controllers + services.AddControllers(); - // Configuração do Swagger - services.AddInfrastructureSwagger(); + // Realiza comunicação com os demais Projetos. + services.AddInfrastructure(ref _configuration); + services.AddPersistence(); + services.AddExternalServices(); + services.AddApplication(); - // Configuração do JWT - services.AddInfrastructureJWT(); + // Configuração do Swagger + services.AddInfrastructureSwagger(); - // Permite que rotas sejam acessíveis em lowercase - services.AddRouting(options => options.LowercaseUrls = true); - } + // Configuração do JWT + services.AddInfrastructureJWT(); - /// - /// Adiciona as configurações de segurança, documentação e roteamento. - /// - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - // Show detailed error page in development mode - app.UseDeveloperExceptionPage(); + // Permite que rotas sejam acessíveis em lowercase + services.AddRouting(options => options.LowercaseUrls = true); - // Enable Swagger middleware for API documentation in development mode - app.UseSwagger(); - app.UseSwaggerUI(); + #region CORS + // Definição de política de CORS + services.AddCors(options => + { + // Permite qualquer origem, cabeçalho e método + options.AddDefaultPolicy( + builder => + { + builder.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + + // // Busca os valores de ALLOW_ORIGINS do arquivo .env + // var allowedOrigins = Environment.GetEnvironmentVariable("ALLOW_ORIGINS") + // ?? throw new Exception("ALLOW_ORIGINS não definido nas variáveis de ambiente."); + + // // Permite apenas as origens definidas no arquivo .env + // options.AddPolicy(name: CORS_POLICY_NAME, + // policy => + // { + // // Busca os valores de ALLOW_ORIGINS do arquivo .env + // policy.WithOrigins( + // origins: allowedOrigins.Split(',')!) + // .AllowAnyHeader() + // .AllowAnyMethod(); + // }); + }); + #endregion CORS + + #region Rate Limit + services.AddMemoryCache(); + services.AddInMemoryRateLimiting(); + var ipRateLimitingConfig = _configuration?.GetSection("IpRateLimiting"); + if (ipRateLimitingConfig is not null) + services.Configure(ipRateLimitingConfig); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + #endregion Rate Limit } - // Enable HTTP Strict Transport Security (HSTS) headers for secure communication - app.UseHsts(); + /// + /// Adiciona as configurações de segurança, documentação e roteamento. + /// + /// + /// + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + // Enable Swagger middleware for API documentation + app.UseSwagger(); + + if (env.IsDevelopment()) + { + // Show detailed error page in development mode + app.UseDeveloperExceptionPage(); + + // Enable Swagger UI in development mode + app.UseSwaggerUI(); - // Redirect HTTP requests to HTTPS for secure communication - app.UseHttpsRedirection(); + // Show development mode message + Console.WriteLine("Swagger is up."); + } - // Enable routing for incoming requests - app.UseRouting(); + // UseExceptionHandler for non-development environments + app.UseMiddleware(); - // Enable authentication for the API - app.UseAuthentication(); + // Enable HTTP Strict Transport Security (HSTS) headers for secure communication + app.UseHsts(); - // Enable authorization for the API - app.UseAuthorization(); + // Redirect HTTP requests to HTTPS for secure communication + app.UseHttpsRedirection(); - // Apply rate limiting middleware to control the number of requests allowed - app.UseClientRateLimiting(); - app.UseIpRateLimiting(); + // Enable CORS + app.UseCors(); + // app.UseCors(CORS_POLICY_NAME); - // Configure API endpoints - app.UseEndpoints(endpoints => endpoints.MapControllers()); + // Enable routing for incoming requests + app.UseRouting(); + + // Enable authentication for the API + app.UseAuthentication(); + + // Enable authorization for the API + app.UseAuthorization(); + + // Apply rate limiting middleware to control the number of requests allowed + app.UseClientRateLimiting(); + app.UseIpRateLimiting(); + + // Configure API endpoints + app.UseEndpoints(endpoints => endpoints.MapControllers()); + } } -} +} \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/WebAPI.csproj b/src/Infrastructure/WebAPI/WebAPI.csproj index ab416046..7a4111cf 100644 --- a/src/Infrastructure/WebAPI/WebAPI.csproj +++ b/src/Infrastructure/WebAPI/WebAPI.csproj @@ -1,11 +1,12 @@ + net7.0 enable enable - ../../docker-compose.dcproj ef326666-1cfa-4625-aae3-41ea85c01828 - 0.0.1 + false + 0.1.0 @@ -13,17 +14,31 @@ 4 bin\Debug\net7.0\Infrastructure.WebAPI.xml + true + + + 4 + bin\Release\net7.0\Infrastructure.WebAPI.xml + true + + + + + + + + - + @@ -31,4 +46,5 @@ PreserveNewest + \ No newline at end of file diff --git a/src/Infrastructure/WebAPI/appsettings.development.json b/src/Infrastructure/WebAPI/appsettings.development.json deleted file mode 100644 index beabe56f..00000000 --- a/src/Infrastructure/WebAPI/appsettings.development.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=COPET_DB;Port=15432;User ID=copet-admin;password=Copet@123;Include Error Detail=true;" - }, - "Serilog": { - "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"], - "MinimumLevel": { - "Default": "Information", - "Override": { - "Microsoft": "Warning", - "System": "Warning" - } - }, - "Filter": [ - { - "Name": "ByIncludingOnly", - "Args": { - "expression": "StartsWith(SourceContext, 'Infrastructure.WebAPI.')" - } - } - ], - "WriteTo": [ - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss}] {SourceContext} [{Level}] {Message}{NewLine}{Exception}" - } - }, - { - "Name": "File", - "Args": { - "path": "logs/copetsystem_logs_.log", - "rollingInterval": "Day", - "rollonFileSizeLimit": true, - "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog" - } - }, - { - "Name": "Seq", - "Args": { - "serverUrl": "http://localhost:5341", - "apiKey": "5nwFLI503IATAxkmzU4O" - } - } - ], - "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"] - }, - "IpRateLimiting": { - "EnableEndpointRateLimiting": true, - "StackBlockedRequests": true, - "RealIpHeader": "X-Real-IP", - "ClientIdHeader": "X-ClientId" - }, - "IpRateLimitPolicies": { - "Default": { - "IpRules": [ - { - "IpAddress": "127.0.0.1", - "Rule": "1r/5s" - }, - { - "IpAddress": "::1", - "Rule": "1r/5s" - } - ], - "EndpointRules": [ - { - "Endpoint": "*", - "Rule": "10r/10s" - } - ] - } - }, - "StorageFile": { - "Directory": "./local-storage", - "AllowedExtensions": [".txt", ".pdf", ".doc", ".docx", ".png", ".jpg"], - "MaxFileSizeInBytes": 10485760, - "Folder": "Notices", - "StudentDocDirectory": "StudentDocs", - "ReportDirectory": "Reports" - }, - "Jwt": { - "SecretKey": "jwt_secret_key_gpic_webapi", - "ExpireIn": 60, - "Issuer": "gpic-webapi", - "Audience": "webapi.gpic.server" - }, - "SmtpConfiguration": { - "Server": "smtp.office365.com", - "Port": 587 - }, - "SmtpUsername": "${SMTP_EMAIL_USERNAME}", - "SmtpPassword": "${SMTP_EMAIL_PASSWORD}" -} diff --git a/src/Infrastructure/WebAPI/appsettings.json b/src/Infrastructure/WebAPI/appsettings.json index 3d405741..9f3ebc08 100644 --- a/src/Infrastructure/WebAPI/appsettings.json +++ b/src/Infrastructure/WebAPI/appsettings.json @@ -1,7 +1,4 @@ { - "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=COPET_DB;Port=15432;User ID=copet-admin;password=Copet@123;Include Error Detail=true;" - }, "Serilog": { "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"], "MinimumLevel": { @@ -15,14 +12,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss}] {SourceContext} [{Level}] {Message}{NewLine}{Exception}" - } - }, - { - "Name": "Seq", - "Args": { - "serverUrl": "http://localhost:5341", - "apiKey": "5nwFLI503IATAxkmzU4O" + "outputTemplate": "[{Timestamp:dd/MM/yyyy HH:mm:ss}] {SourceContext} [{Level}] {Message}{NewLine}{Exception}" } } ], @@ -58,14 +48,14 @@ ] }, "StorageFile": { + "Directory": "/Users/eduardo/Projects/CEFET/CopetSystem/GPIC.WebAPI/files", + "Folder": "blobs", "AllowedExtensions": [".txt", ".pdf", ".doc", ".docx", ".png", ".jpg"], "MaxFileSizeInBytes": 10485760 }, - "Jwt": { - "ExpireIn": 60 - }, "SmtpConfiguration": { "Server": "smtp.office365.com", "Port": 587 - } + }, + "TempPath": "/Users/eduardo/Projects/CEFET/CopetSystem/GPIC.WebAPI/temp" } diff --git a/src/Infrastructure/WebFunctions/Dockerfile b/src/Infrastructure/WebFunctions/Dockerfile new file mode 100644 index 00000000..b82b47ca --- /dev/null +++ b/src/Infrastructure/WebFunctions/Dockerfile @@ -0,0 +1,34 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated7.0 AS base +FROM mohsinonxrm/azure-functions-dotnet:4-isolated7.0-arm64v8 AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/runtime:7.0 as runtime7.0 +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build + +COPY --from=runtime7.0 /usr/share/dotnet/host /usr/share/dotnet/host +COPY --from=runtime7.0 /usr/share/dotnet/shared /usr/share/dotnet/shared + +ENV AzureWebJobsStorage="UseDevelopmentStorage=true" +ENV AzureFunctionsJobHost__Logging__Console__IsEnabled=true + +WORKDIR /src +COPY ["Domain/Domain.csproj", "Domain/"] +COPY ["Application/Application.csproj", "Application/"] +COPY ["Infrastructure/Services/Services.csproj", "Infrastructure/Services/"] +COPY ["Infrastructure/Persistence/Persistence.csproj", "Infrastructure/Persistence/"] +COPY ["Infrastructure/IoC/IoC.csproj", "Infrastructure/IoC/"] +COPY ["Infrastructure/WebFunctions/WebFunctions.csproj", "Infrastructure/WebFunctions/"] +RUN dotnet restore "Infrastructure/WebFunctions/WebFunctions.csproj" +COPY . . +WORKDIR "/src/Infrastructure/WebFunctions" +RUN dotnet build "WebFunctions.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "WebFunctions.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/Functions/ClosePendingProjectsFunction.cs b/src/Infrastructure/WebFunctions/Functions/ClosePendingProjectsFunction.cs new file mode 100644 index 00000000..3ce6483d --- /dev/null +++ b/src/Infrastructure/WebFunctions/Functions/ClosePendingProjectsFunction.cs @@ -0,0 +1,49 @@ +using Application.Interfaces.UseCases.Project; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using WebFunctions.Models; + +namespace WebFunctions.Functions +{ + public class ClosePendingProjectsFunction + { + private readonly ILogger _logger; + private readonly IClosePendingProjects _closePendingProjects; + public ClosePendingProjectsFunction(ILogger logger, IClosePendingProjects closePendingProjects) + { + _logger = logger; + _closePendingProjects = closePendingProjects; + } + + /// + /// Encerra todos os projetos que estão com alguma pendência e que a data de expiração já passou. + /// Execução diária às 06:00 UTC, equivalente à 03:00 BRT. + /// + /// Informações do timer. + [Function("ClosePendingProjects")] + public async Task Run([TimerTrigger("0 0 6 * * *")] CustomTimerInfo timer) + { + // Informa início da execução + _logger.LogInformation("Encerramento de projetos com pendência e fora do prazo iniciada."); + + try + { + // Realiza o encerramento dos projetos + string result = await _closePendingProjects.ExecuteAsync(); + + // Informa fim da execução + _logger.LogInformation("Encerramento de projetos com pendência e fora do prazo finalizada. Resultado: {Result}", result); + } + catch (Exception ex) + { + _logger.LogError("Erro ao executar encerramento de projetos com pendência e fora do prazo.", ex); + } + + // Informa próxima execução + if (timer is not null) + { + _logger.LogInformation("Próxima encerramento de projetos com pendência e fora do prazo: {NextExecutionTime}", timer.ScheduleStatus?.Next.ToString("dd/MM/yyyy HH:mm:ss")); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/Functions/GenerateCertificateFunction.cs b/src/Infrastructure/WebFunctions/Functions/GenerateCertificateFunction.cs new file mode 100644 index 00000000..038df523 --- /dev/null +++ b/src/Infrastructure/WebFunctions/Functions/GenerateCertificateFunction.cs @@ -0,0 +1,51 @@ +using Application.Interfaces.UseCases.Project; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using WebFunctions.Models; + +namespace Infrastructure.Functions.WebFunctions +{ + public class GenerateCertificateFunction + { + private readonly ILogger _logger; + private readonly IGenerateCertificate _generateCertificate; + public GenerateCertificateFunction(ILogger logger, IGenerateCertificate generateCertificate) + { + _logger = logger; + _generateCertificate = generateCertificate; + } + + /// + /// Gera os certificados de todos os projetos que estão com a data de expiração vencida. + /// Encerra os projetos que estão com a data de expiração vencida. + /// Suspende professores que não entregaram o relatório final. + /// Execução diária às 04:00 UTC, equivalente à 01:00 BRT. + /// + /// Informações do timer. + [Function("GenerateCertificate")] + public async Task Run([TimerTrigger("0 0 4 * * *")] CustomTimerInfo timer) + { + // Informa início da execução + _logger.LogInformation("Geração de certificados iniciada."); + + try + { + // Realiza a geração dos certificados + string result = await _generateCertificate.ExecuteAsync(); + + // Informa fim da execução + _logger.LogInformation("Geração de certificados finalizada. Resultado: {Result}", result); + } + catch (Exception ex) + { + _logger.LogError("Erro ao executar geração de certificados.", ex); + } + + // Informa próxima execução + if (timer is not null) + { + _logger.LogInformation("Próxima geração de certificados: {NextExecutionTime}", timer.ScheduleStatus?.Next.ToString("dd/MM/yyyy HH:mm:ss")); + } + } + } +} diff --git a/src/Infrastructure/WebFunctions/Functions/ReportDeadlineNotificationFunction.cs b/src/Infrastructure/WebFunctions/Functions/ReportDeadlineNotificationFunction.cs new file mode 100644 index 00000000..99c0417f --- /dev/null +++ b/src/Infrastructure/WebFunctions/Functions/ReportDeadlineNotificationFunction.cs @@ -0,0 +1,51 @@ +using Application.Interfaces.UseCases.Notice; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using WebFunctions.Models; + +namespace Infrastructure.Functions.WebFunctions +{ + public class ReportDeadlineNotificationFunction + { + private readonly ILogger _logger; + private readonly IReportDeadlineNotification _reportDeadlineNotification; + public ReportDeadlineNotificationFunction( + ILogger logger, + IReportDeadlineNotification reportDeadlineNotification) + { + _logger = logger; + _reportDeadlineNotification = reportDeadlineNotification; + } + + /// + /// Envia notificação para os professores sobre o prazo para entrega dos relatórios. + /// Execução diária às 05:00 UTC, equivalente à 02:00 BRT. + /// + /// Informações do timer. + [Function("ReportDeadlineNotification")] + public async Task Run([TimerTrigger("0 0 5 * * *")] CustomTimerInfo timer) + { + // Informa início da execução + _logger.LogInformation("Notificação de prazo para entrega de relatório iniciada."); + + try + { + // Realiza a notificação dos professores e prazo para entrega dos relatórios + string result = await _reportDeadlineNotification.ExecuteAsync(); + + // Informa fim da execução + _logger.LogInformation("Notificação de prazo para entrega de relatório finalizada. Resultado: {Result}", result); + } + catch (Exception ex) + { + _logger.LogError("Erro ao executar notificação de prazo para entrega de relatório.", ex); + } + + // Informa próxima execução + if (timer is not null) + { + _logger.LogInformation("Próxima notificação de prazo para entrega de relatório: {NextExecutionTime}", timer.ScheduleStatus?.Next.ToString("dd/MM/yyyy HH:mm:ss")); + } + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/Models/CustomScheduleStatus.cs b/src/Infrastructure/WebFunctions/Models/CustomScheduleStatus.cs new file mode 100644 index 00000000..b1e3ddef --- /dev/null +++ b/src/Infrastructure/WebFunctions/Models/CustomScheduleStatus.cs @@ -0,0 +1,9 @@ +namespace WebFunctions.Models +{ + public class CustomScheduleStatus + { + public DateTime Last { get; set; } + public DateTime Next { get; set; } + public DateTime LastUpdated { get; set; } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/Models/CustomTimerInfo.cs b/src/Infrastructure/WebFunctions/Models/CustomTimerInfo.cs new file mode 100644 index 00000000..7359b6f6 --- /dev/null +++ b/src/Infrastructure/WebFunctions/Models/CustomTimerInfo.cs @@ -0,0 +1,8 @@ +namespace WebFunctions.Models +{ + public class CustomTimerInfo + { + public CustomScheduleStatus? ScheduleStatus { get; set; } + public bool IsPastDue { get; set; } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/Program.cs b/src/Infrastructure/WebFunctions/Program.cs new file mode 100644 index 00000000..cd8517ec --- /dev/null +++ b/src/Infrastructure/WebFunctions/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Hosting; +using Infrastructure.IoC; +using Microsoft.Extensions.Configuration; + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices((hostContext, services) => + { + IConfiguration? configuration = null; + services.AddInfrastructure(ref configuration, hostContext); + services.AddPersistence(); + services.AddExternalServices(); + services.AddApplication(); + }) + .Build(); + +host.Run(); diff --git a/src/Infrastructure/WebFunctions/Properties/launchSettings.json b/src/Infrastructure/WebFunctions/Properties/launchSettings.json new file mode 100644 index 00000000..723a72d4 --- /dev/null +++ b/src/Infrastructure/WebFunctions/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "WebFunctions": { + "commandName": "Project", + "commandLineArgs": "--port 7055", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/WebFunctions.csproj b/src/Infrastructure/WebFunctions/WebFunctions.csproj new file mode 100644 index 00000000..19299062 --- /dev/null +++ b/src/Infrastructure/WebFunctions/WebFunctions.csproj @@ -0,0 +1,45 @@ + + + Exe + enable + enable + net7.0 + v4 + 0.1.0 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/appsettings.json b/src/Infrastructure/WebFunctions/appsettings.json new file mode 100644 index 00000000..556a4742 --- /dev/null +++ b/src/Infrastructure/WebFunctions/appsettings.json @@ -0,0 +1,65 @@ +{ + "SiteUrl": "https://localhost:5001", + "Serilog": { + "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss}] {SourceContext} [{Level}] {Message}{NewLine}{Exception}" + } + } + ], + "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"] + }, + "IpRateLimiting": { + "EnableEndpointRateLimiting": true, + "StackBlockedRequests": false, + "RealIPHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "HttpStatusCode": 429, + "GeneralRules": [ + { + "Endpoint": "*", + "Period": "10s", + "Limit": 10 + }, + { + "Endpoint": "*:/api/auth/*", + "Period": "1s", + "Limit": 1 + }, + { + "Endpoint": "(post):/api/professor", + "Period": "1s", + "Limit": 1 + }, + { + "Endpoint": "(post):/api/student", + "Period": "1s", + "Limit": 1 + } + ] + }, + "StorageFile": { + "Directory": "./files", + "Folder": "blobs", + "AllowedExtensions": [".txt", ".pdf", ".doc", ".docx", ".png", ".jpg"], + "MaxFileSizeInBytes": 10485760 + }, + "Jwt": { + "ExpireIn": 60 + }, + "SmtpConfiguration": { + "Server": "smtp.office365.com", + "Port": 587 + }, + "TempPath": "./temp" +} diff --git a/src/Infrastructure/WebFunctions/function.json b/src/Infrastructure/WebFunctions/function.json new file mode 100644 index 00000000..e69de29b diff --git a/src/Infrastructure/WebFunctions/host.json b/src/Infrastructure/WebFunctions/host.json new file mode 100644 index 00000000..ee5cf5f8 --- /dev/null +++ b/src/Infrastructure/WebFunctions/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file diff --git a/src/Infrastructure/WebFunctions/local.settings.json b/src/Infrastructure/WebFunctions/local.settings.json new file mode 100644 index 00000000..8eea88f4 --- /dev/null +++ b/src/Infrastructure/WebFunctions/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + } +}