From bcd80068c565a6f9d554ee980167ca242b7e501c Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Tue, 12 Nov 2024 16:14:48 +0200 Subject: [PATCH 01/22] Add coalitions --- api/Vote.Monitor.sln | 7 + .../PoliciesInstaller.cs | 1 + .../CoalitionLeaderAuthorizationHandler.cs | 49 + .../CoalitionLeaderRequirement.cs | 7 + .../Specifications/CoalitionLeaderView.cs | 13 + .../GetCoalitionLeaderDetailsSpecification.cs | 26 + .../GetMonitoringNgoSpecification.cs | 2 +- .../GetMonitoringObserverSpecification.cs | 7 +- .../Feature.Attachments/Create/Endpoint.cs | 2 +- api/src/Feature.Attachments/Get/Endpoint.cs | 2 +- api/src/Feature.Attachments/List/Endpoint.cs | 2 +- .../GetAttachmentByIdSpecification.cs | 4 +- .../GetObserverAttachmentsSpecification.cs | 2 + .../GetById/Endpoint.cs | 2 +- .../Get/Endpoint.cs | 6 +- .../ListReceived/Endpoint.cs | 2 +- .../ListSent/Endpoint.cs | 6 +- .../Get/Endpoint.cs | 2 +- .../List/Endpoint.cs | 2 +- ...stCitizenReportAttachmentsSpecification.cs | 3 +- .../Specifications/ListNotesSpecification.cs | 9 +- .../GetById/Endpoint.cs | 7 +- .../GetFilters/Endpoint.cs | 4 +- .../GetSubmissionsAggregated/Endpoint.cs | 7 +- .../ListEntries/Endpoint.cs | 2 +- .../Models/CitizenReportModel.cs | 2 +- .../UpdateStatus/Endpoint.cs | 3 +- .../CoalitionsFeatureInstaller.cs | 19 + api/src/Feature.Coalitions/Create/Endpoint.cs | 64 + api/src/Feature.Coalitions/Create/Request.cs | 9 + .../Feature.Coalitions/Create/Validator.cs | 12 + api/src/Feature.Coalitions/Delete/Endpoint.cs | 47 + api/src/Feature.Coalitions/Delete/Request.cs | 7 + .../Feature.Coalitions/Delete/Validator.cs | 10 + api/src/Feature.Coalitions/EnableTesting.cs | 5 + .../Feature.Coalitions.csproj | 22 + .../Feature.Coalitions/FormAccess/Endpoint.cs | 98 + .../Feature.Coalitions/FormAccess/Request.cs | 13 + .../FormAccess/Validator.cs | 12 + api/src/Feature.Coalitions/Get/Endpoint.cs | 34 + api/src/Feature.Coalitions/Get/Request.cs | 7 + api/src/Feature.Coalitions/Get/Validator.cs | 10 + api/src/Feature.Coalitions/GetMy/Endpoint.cs | 45 + api/src/Feature.Coalitions/GetMy/Request.cs | 11 + api/src/Feature.Coalitions/GetMy/Validator.cs | 9 + api/src/Feature.Coalitions/GlobalUsings.cs | 11 + api/src/Feature.Coalitions/List/Endpoint.cs | 141 + api/src/Feature.Coalitions/List/Request.cs | 9 + api/src/Feature.Coalitions/List/Validator.cs | 14 + .../Models/CoalitionMember.cs | 14 + .../Models/CoalitionModel.cs | 28 + .../Services/FormSubmissionsCleanupService.cs | 99 + .../IFormSubmissionsCleanupService.cs | 23 + .../Specifications/GetMonitoringNgos.cs | 8 + api/src/Feature.Coalitions/Update/Endpoint.cs | 75 + api/src/Feature.Coalitions/Update/Request.cs | 9 + .../Feature.Coalitions/Update/Validator.cs | 19 + .../Feature.Form.Submissions/Any/Endpoint.cs | 2 +- .../Delete/Endpoint.cs | 8 +- .../GetAggregated/Endpoint.cs | 12 +- .../GetById/Endpoint.cs | 6 +- .../GetFilters/Endpoint.cs | 4 +- .../ListByObserver/Endpoint.cs | 10 +- .../ListEntries/Endpoint.cs | 15 +- .../SetCompletion/Endpoint.cs | 4 +- .../GetCoalitionFormSpecification.cs | 20 + .../Specifications/GetFormSpecification.cs | 12 - ...tFormSubmissionForObserverSpecification.cs | 12 +- .../GetFormSubmissionSpecification.cs | 3 + .../GetMonitoringNgoFormSpecification.cs | 16 + .../GetMonitoringObserverSpecification.cs | 2 +- .../UpdateStatus/Endpoint.cs | 4 +- .../Upsert/Endpoint.cs | 13 +- api/src/Feature.Forms/FetchAll/Endpoint.cs | 57 +- api/src/Feature.Forms/FromForm/Endpoint.cs | 1 - api/src/Feature.Forms/FromForm/Validator.cs | 1 - .../Feature.Forms/GetFormsVersion/Endpoint.cs | 11 +- api/src/Feature.Forms/List/Endpoint.cs | 6 +- .../GetFormByIdSpecification.cs | 5 +- .../GetIncidentReportsForFormSpecification.cs | 4 +- .../GetSubmissionsForFormSpecification.cs | 4 +- .../Get/Endpoint.cs | 2 +- .../List/Endpoint.cs | 2 +- ...tIncidentReportAttachmentsSpecification.cs | 3 +- .../Specifications/ListNotesSpecification.cs | 4 +- .../GetById/Endpoint.cs | 7 +- .../GetFilters/Endpoint.cs | 4 +- .../GetSubmissionsAggregated/Endpoint.cs | 5 +- .../ListByObserver/Endpoint.cs | 9 +- .../ListEntries/Endpoint.cs | 14 +- .../ListMy/Endpoint.cs | 7 +- .../SetCompletion/Endpoint.cs | 3 +- .../GetIncidentReportSpecification.cs | 4 +- .../GetMonitoringObserverSpecification.cs | 6 +- .../UpdateStatus/Endpoint.cs | 4 +- .../GetMonitoringNgoSpecification.cs | 2 +- .../Assign/Request.cs | 2 +- .../ClearTags/Endpoint.cs | 1 + .../Export/Endpoint.cs | 2 +- .../List/Endpoint.cs | 10 +- .../ListTags/Endpoint.cs | 1 + .../Parser/MonitoringObserverImportModel.cs | 4 +- .../MonitoringObserverImportModelValidator.cs | 2 - .../ResendInvites/Endpoint.cs | 35 +- .../GetMonitoringObserverForElectionRound.cs | 5 +- .../GetMonitoringObserverSpecification.cs | 5 +- .../GetNoteByIdSpecification.cs | 5 +- .../Specifications/GetNotesSpecification.cs | 2 + api/src/Feature.Notifications/Get/Endpoint.cs | 8 +- .../ListReceived/Endpoint.cs | 4 +- .../ListRecipients/Endpoint.cs | 10 +- .../ListSent/Endpoint.cs | 6 +- .../Feature.Notifications/Send/Endpoint.cs | 8 +- .../GetMonitoringObserverSpecification.cs | 7 +- .../Feature.ObserverGuide/GetById/Endpoint.cs | 2 +- .../Feature.ObserverGuide/List/Endpoint.cs | 6 +- .../SetCompletion/Endpoint.cs | 4 +- .../GetMonitoringObserverSpecification.cs | 6 +- ...tionInformationForObserverSpecification.cs | 16 +- ...tPollingStationInformationSpecification.cs | 6 +- .../Delete/Endpoint.cs | 2 +- .../AddAttachment/Endpoint.cs | 2 +- .../Feature.QuickReports/Delete/Endpoint.cs | 2 + .../DeleteAttachment/Endpoint.cs | 2 + .../GetFilters/Endpoint.cs | 4 +- api/src/Feature.QuickReports/List/Endpoint.cs | 2 +- .../GetMonitoringObserverSpecification.cs | 2 +- ...tQuickReportAttachmentByIdSpecification.cs | 3 +- .../ListObserverQuickReportsSpecification.cs | 2 +- .../UpdateStatus/Endpoint.cs | 1 + .../Feature.QuickReports/Upsert/Endpoint.cs | 2 +- .../GetNgoAdminStatistics/Endpoint.cs | 2 +- .../Create/Endpoint.cs | 2 +- .../Endpoint.cs | 2 +- .../Monitoring/Endpoint.cs | 36 +- .../Monitoring/NgoElectionRoundView.cs | 4 + .../GetNgoElectionSpecification.cs | 28 - .../GetObserverElectionSpecification.cs | 2 +- .../ListElectionRoundsSpecification.cs | 2 +- .../Create/Request.cs | 2 +- .../Create/Validator.cs | 4 +- .../Create/Endpoint.cs | 9 +- .../FetchLevels/Endpoint.cs | 2 +- .../GetPollingStationSpecification.cs | 19 +- .../Update/Endpoint.cs | 9 +- .../Extensions/CustomSentryUserFactory.cs | 2 +- api/src/Vote.Monitor.Api/Program.cs | 2 + .../Vote.Monitor.Api/Vote.Monitor.Api.csproj | 4 +- .../Vote.Monitor.Api/appsettings.Testing.json | 35 - .../Extensions/ConfigurationExtensions.cs | 2 +- .../Helpers/DeterministicGuid.cs | 14 + .../Services/Csv/CsvReader.cs | 4 +- .../Helpers/EmailTemplateLoader.cs | 2 +- .../FileStorage/S3/S3FileStorageService.cs | 6 +- .../Serialization/SerializerService.cs | 2 +- .../Vote.Monitor.Core.csproj | 2 - .../ApplicationUser.cs | 29 +- .../Entities/CoalitionAggregate/Coalition.cs | 51 + .../CoalitionAggregate/CoalitionFormAccess.cs | 39 + .../CoalitionAggregate/CoalitionMembership.cs | 38 + .../Entities/FormBase/BaseForm.cs | 1 - .../Entities/FormTemplateAggregate/Form.cs | 12 +- .../MonitoringNgoAggregate/MonitoringNgo.cs | 4 +- .../MonitoringObserverNotification.cs | 4 +- .../ApplicationUserConfiguration.cs | 5 + .../CoalitionConfiguration.cs | 18 + .../CoalitionFormAccessConfiguration.cs | 28 + .../CoalitionMembershipConfiguration.cs | 28 + .../20241111172627_AddCoalitions.Designer.cs | 6967 ++++++++++++++++ .../20241111172627_AddCoalitions.cs | 162 + ...34_AddDisplayNameInAspNetUsers.Designer.cs | 6973 +++++++++++++++++ ...41112130734_AddDisplayNameInAspNetUsers.cs | 30 + .../VoteMonitorContextModelSnapshot.cs | 172 + .../Seeders/PlatformAdminSeeder.cs | 3 +- .../Seeders/PlatformAdminSeederSettings.cs | 2 - .../Vote.Monitor.Domain/VoteMonitorContext.cs | 12 +- .../Vote.Monitor.Module.Notifications.csproj | 2 + .../CreateMonitoringNgoView.cs | 4 +- .../NgoAdminAuthorizationHandlerTests.cs | 6 +- .../Endpoints/CreateEndpointTests.cs | 4 +- .../Endpoints/DeleteEndpointTests.cs | 4 +- .../Endpoints/GetEndpointTests.cs | 4 +- .../Endpoints/ListEndpointTests.cs | 6 +- .../Validators/DeleteValidatorTests.cs | 2 +- .../Validators/GetByIdValidatorTests.cs | 2 +- .../Endpoints/GetEndpointTests.cs | 2 +- .../Endpoints/ListEndpointTests.cs | 4 +- .../Endpoints/ListEndpointTests.cs | 2 +- .../ListRequestValidatorTests.cs | 2 +- .../Endpoints/UpsertEndpointTests.cs | 4 +- .../ValidatorTests/UpsertValidatorTests.cs | 2 +- .../Endpoints/ListMyEndpointTests.cs | 2 +- .../Endpoints/UpsertEndpointTests.cs | 18 +- .../DeleteRequestValidatorTests.cs | 2 +- ...ubmissionsAggregateFilterValidatorTests.cs | 2 +- .../ValidatorTests/GetByIdValidatorTests.cs | 2 +- .../ListByObserverValidatorTests.cs | 4 +- .../ValidatorTests/UpsertValidatorTests.cs | 2 +- .../ValidatorTests/CreateValidatorTests.cs | 2 +- .../ValidatorTests/UpdateValidatorTests.cs | 2 +- .../FromFormRequestValidatorTests.cs | 2 +- .../FromTemplateRequestValidatorTests.cs | 2 +- .../Endpoints/GetEndpointTests.cs | 2 +- .../Endpoints/ListEndpointTests.cs | 4 +- .../Endpoints/ListEndpointTests.cs | 2 +- .../ListRequestValidatorTests.cs | 2 +- .../ValidatorTests/GetByIdValidatorTests.cs | 2 +- ...entReportsAggregateFilterValidatorTests.cs | 2 +- .../ListByObserverValidatorTests.cs | 4 +- .../ListEntriesValidatorTests.cs | 4 +- .../ListFormsOverviewValidatorTests.cs | 4 +- .../UpdateStatusValidatorTests.cs | 4 +- .../ValidatorTests/UpsertValidatorTests.cs | 2 +- .../GetRequestValidatorTests.cs | 2 +- .../ListRequestValidatorTests.cs | 8 +- .../ValidatorTests/ActivateValidatorTests.cs | 2 +- .../AddObserverRequestValidatorTests.cs | 6 +- .../AssignObserverRequestValidatorTests.cs | 2 +- .../ValidatorTests/ClearTagsValidatorTags.cs | 4 +- .../ValidatorTests/GetValidatorTests.cs | 2 +- .../ValidatorTests/ListValidatorTests.cs | 2 +- .../RemoveObserverRequestValidatorTests.cs | 2 +- .../RemoveObserverValidatorTests.cs | 2 +- .../ValidatorTests/SuspendValidatorTests.cs | 2 +- .../ValidatorTests/TagValidatorTests.cs | 6 +- .../ValidatorTests/UntagValidatorTests.cs | 4 +- .../ValidatorTests/UpdateValidatorTests.cs | 2 +- .../Endpoints/DeleteEndpointTests.cs | 4 +- .../Endpoints/ListEndpointTests.cs | 4 +- .../Endpoints/UpsertEndpointTests.cs | 2 +- .../ListRequestValidatorTests.cs | 2 +- .../Validators/DeleteValidatorTests.cs | 2 +- .../Validators/GetByIdValidatorTests.cs | 2 +- .../Endpoints/DeleteEndpointTests.cs | 4 +- .../Endpoints/GetEndpointTests.cs | 4 +- .../Endpoints/UpsertEndpointTests.cs | 2 +- .../DeleteRequestValidatorTests.cs | 2 +- .../GetRequestValidatorTests.cs | 2 +- .../ValidatorTests/UpsertValidatorTests.cs | 2 +- .../DeleteRequestValidatorTests.cs | 2 +- .../ValidatorTests/UpsertValidatorTests.cs | 6 +- .../Endpoints/AddAttachmentEndpointTests.cs | 4 +- .../DeleteAttachmentEndpointTests.cs | 2 +- .../Endpoints/DeleteEndpointTests.cs | 2 +- .../Endpoints/ListMyEndpointTests.cs | 10 +- .../ListMyRequestValidatorTests.cs | 2 +- .../ListRequestValidatorTests.cs | 2 +- .../GetNgoAdminStatisticsValidatorTests.cs | 2 +- .../AnswerAggregateFactoryTests.cs | 2 +- .../FormSubmissionsAggregateTests.cs | 6 +- .../PSIFormSubmissionsAggregateTests.cs | 2 +- .../MultiSelectAnswerRequestValidatorTests.cs | 2 +- .../Services/ObserverCsvParserTests.cs | 2 +- .../GetPollingStationSpecificationTests.cs | 19 +- .../ListRequestValidatorTests.cs | 8 +- .../ApiTesting.cs | 41 + .../BaseApiTestFixture.cs | 13 + .../BaseDbTestFixture.cs | 11 + .../Consts/Coalitions.cs | 7 + .../Consts/ElectionRounds.cs | 9 + .../Consts/Ngos.cs | 33 + .../Consts/Observers.cs | 13 + .../Consts/PollingStations.cs | 9 + .../CustomWebApplicationFactory.cs | 81 + .../Db/ITestDatabase.cs | 14 + .../Db/PostgresTestDatabase.cs | 74 + .../Db/TestDatabaseFactory.cs | 14 + .../Db/TestcontainersTestDatabase.cs | 75 + .../DbTesting.cs | 35 + .../FEProblemDetails.cs | 42 - .../Fakers/Answers.cs | 33 + .../Fakers/DateAnswerFaker.cs | 12 + .../Fakers/ElectionRoundFaker.cs | 15 + .../Fakers/FormSubmissionRequestFaker.cs | 18 + .../Fakers/MultiSelectAnswerFaker.cs | 32 + .../Fakers/NumberAnswerFaker.cs | 12 + .../Fakers/RatingAnswerFaker.cs | 12 + .../Fakers/SingleSelectAnswerFaker.cs | 25 + .../Fakers/TextAnswerFaker.cs | 12 + .../Features/Coalition/CreateTests.cs | 210 + .../Features/Coalition/DeleteTests.cs | 238 + .../Features/Coalition/FormAccessTests.cs | 206 + .../Features/Coalition/UpdateTests.cs | 309 + .../GlobalUsings.cs | 14 +- .../HttpClientExtensions.cs | 146 + .../HttpServerFixture.cs | 107 - .../IDataSeeder.cs | 9 - .../AuditTrailInterceptorTests.cs | 53 +- .../Interceptors/AuditingInterceptorTests.cs | 44 +- .../Models/CreateFormRequest.cs | 20 + .../Models/FormSubmissionRequest.cs | 12 + .../Models/ResponseWithId.cs | 6 + .../NoopDataSeeder.cs | 11 - .../Scenarios/CoalitionFormScenarioBuilder.cs | 40 + .../Scenarios/CoalitionScenarioBuilder.cs | 77 + .../Scenarios/Dummy.cs | 185 + .../Scenarios/ElectionRoundScenarioBuilder.cs | 90 + .../MonitoringNgoFormScenarioBuilder.cs | 58 + .../Scenarios/MonitoringNgoScenarioBuilder.cs | 67 + .../Scenarios/NgoScenarioBuilder.cs | 37 + .../Scenarios/ScenarioBuilder.cs | 114 + .../Scenarios/ScenarioData.cs | 36 + .../Services/NoopCurrentUserProvider.cs | 23 + .../Vote.Monitor.Api.IntegrationTests.csproj | 44 +- .../appsettings.json | 5 + .../LanguagesTranslationStatusTests.cs | 4 +- .../TranslatedStringTests.cs | 2 +- .../Entities/BaseFormTests.cs | 2 +- .../FormTests.AddTranslations.cs | 4 +- .../FormAggregate/FormTests.Duplicate.cs | 2 +- .../FormTests.RemoveTranslation.cs | 4 +- .../FormAggregate/FormTestsTestData.cs | 4 +- .../Entities/FormAggregateTests.cs | 36 +- .../FormTemplateTests.AddTranslations.cs | 3 +- .../FormTemplateTests.Clone.cs | 1 - .../FormTemplateTests.Duplicate.cs | 2 +- .../FormTemplateTests.RemoveTranslation.cs | 3 +- .../FormTemplateAggregate/FormTests.cs | 6 +- .../QuestionsMapperTests.cs | 6 +- .../Validators/ValidatorsTestData.cs | 2 +- .../Fakes/Aggregates/FormAggregateFaker.cs | 4 +- .../PollingStationInformationFormFaker.cs | 4 +- api/tests/Vote.Monitor.TestUtils/TestData.cs | 2 +- .../Clients/Citizen/ICitizenApi.cs | 3 +- .../Models/IncidentReportRequest.cs | 2 +- .../Fakers/IncidentReportFaker.cs | 2 +- .../Forms/CitizenReportingFormData.cs | 2 +- utils/SubmissionsFaker/Program.cs | 2 +- .../Seeders/CitizenReportingFormSeeder.cs | 8 +- .../Seeders/IncidentReportingFormSeeder.cs | 8 +- .../SubmissionsFaker/SubmissionsFaker.csproj | 4 +- web/pnpm-lock.yaml | 26 +- web/src/common/prev-data-source-store.ts | 23 + web/src/common/types.ts | 32 +- .../DataSourceSwitcher/DataSourceSwitcher.tsx | 62 + web/src/components/layout/Header/Header.tsx | 4 +- web/src/context/election-round.store.tsx | 8 +- .../ElectionEventDetails.tsx | 110 +- .../election-event/hooks/coalition-hooks.ts | 26 + .../filtering/components/ActiveFilters.tsx | 15 +- web/src/features/filtering/filtering-enums.ts | 1 + .../components/Dashboard/Dashboard.tsx | 643 +- .../hooks/statistics-queries.ts | 1 + .../CitizenReportDetails.tsx | 51 +- .../CitizenReportsTab/CitizenReportsTab.tsx | 2 +- .../components/Dashboard/Dashboard.tsx | 106 +- .../QuickReportDetails/QuickReportDetails.tsx | 4 +- .../responses/models/citizen-report.ts | 5 + .../responses/models/search-params.ts | 86 +- .../features/responses/utils/column-defs.tsx | 35 + web/src/locales/en.json | 13 +- web/src/locales/ro.json | 9 +- web/src/routes/index.tsx | 14 +- 353 files changed, 19576 insertions(+), 1228 deletions(-) create mode 100644 api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs create mode 100644 api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs create mode 100644 api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs create mode 100644 api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs create mode 100644 api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs create mode 100644 api/src/Feature.Coalitions/Create/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/Create/Request.cs create mode 100644 api/src/Feature.Coalitions/Create/Validator.cs create mode 100644 api/src/Feature.Coalitions/Delete/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/Delete/Request.cs create mode 100644 api/src/Feature.Coalitions/Delete/Validator.cs create mode 100644 api/src/Feature.Coalitions/EnableTesting.cs create mode 100644 api/src/Feature.Coalitions/Feature.Coalitions.csproj create mode 100644 api/src/Feature.Coalitions/FormAccess/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/FormAccess/Request.cs create mode 100644 api/src/Feature.Coalitions/FormAccess/Validator.cs create mode 100644 api/src/Feature.Coalitions/Get/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/Get/Request.cs create mode 100644 api/src/Feature.Coalitions/Get/Validator.cs create mode 100644 api/src/Feature.Coalitions/GetMy/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/GetMy/Request.cs create mode 100644 api/src/Feature.Coalitions/GetMy/Validator.cs create mode 100644 api/src/Feature.Coalitions/GlobalUsings.cs create mode 100644 api/src/Feature.Coalitions/List/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/List/Request.cs create mode 100644 api/src/Feature.Coalitions/List/Validator.cs create mode 100644 api/src/Feature.Coalitions/Models/CoalitionMember.cs create mode 100644 api/src/Feature.Coalitions/Models/CoalitionModel.cs create mode 100644 api/src/Feature.Coalitions/Services/FormSubmissionsCleanupService.cs create mode 100644 api/src/Feature.Coalitions/Services/IFormSubmissionsCleanupService.cs create mode 100644 api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs create mode 100644 api/src/Feature.Coalitions/Update/Endpoint.cs create mode 100644 api/src/Feature.Coalitions/Update/Request.cs create mode 100644 api/src/Feature.Coalitions/Update/Validator.cs create mode 100644 api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs delete mode 100644 api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs create mode 100644 api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs delete mode 100644 api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs delete mode 100644 api/src/Vote.Monitor.Api/appsettings.Testing.json create mode 100644 api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs create mode 100644 api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs create mode 100644 api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs create mode 100644 api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs create mode 100644 api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs create mode 100644 api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/FormSubmissionRequestFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs rename api/tests/{Vote.Monitor.Domain.UnitTests => Vote.Monitor.Api.IntegrationTests}/Interceptors/AuditTrailInterceptorTests.cs (91%) rename api/tests/{Vote.Monitor.Domain.UnitTests => Vote.Monitor.Api.IntegrationTests}/Interceptors/AuditingInterceptorTests.cs (82%) create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Services/NoopCurrentUserProvider.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json create mode 100644 web/src/common/prev-data-source-store.ts create mode 100644 web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx create mode 100644 web/src/features/election-event/hooks/coalition-hooks.ts diff --git a/api/Vote.Monitor.sln b/api/Vote.Monitor.sln index 7393db588..a0147dee7 100644 --- a/api/Vote.Monitor.sln +++ b/api/Vote.Monitor.sln @@ -164,6 +164,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.IncidentReports.Att EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Citizen.Notifications", "src\Feature.Citizen.Notifications\Feature.Citizen.Notifications.csproj", "{A43F29F9-5765-4143-AB9A-C3ECFCDD402F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Coalitions", "src\Feature.Coalitions\Feature.Coalitions.csproj", "{5F573792-C095-44F5-8DDD-05257CA41C9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -470,6 +472,10 @@ Global {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A43F29F9-5765-4143-AB9A-C3ECFCDD402F}.Release|Any CPU.Build.0 = Release|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F573792-C095-44F5-8DDD-05257CA41C9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -550,6 +556,7 @@ Global {0567FED6-2464-462D-BE59-3A4FA50BF438} = {3441EE1D-E3C6-45BE-A020-553816015081} {DF354318-6856-4925-96A0-0C458E530091} = {3441EE1D-E3C6-45BE-A020-553816015081} {A43F29F9-5765-4143-AB9A-C3ECFCDD402F} = {17945B3C-5A4C-4279-8022-65ABC606A510} + {5F573792-C095-44F5-8DDD-05257CA41C9E} = {17945B3C-5A4C-4279-8022-65ABC606A510} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {50C20C9F-F2AF-45D8-994A-06661772B31C} diff --git a/api/src/Authorization.Policies/PoliciesInstaller.cs b/api/src/Authorization.Policies/PoliciesInstaller.cs index 7eb696ce6..52f9da5ed 100644 --- a/api/src/Authorization.Policies/PoliciesInstaller.cs +++ b/api/src/Authorization.Policies/PoliciesInstaller.cs @@ -15,6 +15,7 @@ public static IServiceCollection AddAuthorizationPolicies(this IServiceCollectio services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddAuthorization(options => { diff --git a/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs new file mode 100644 index 000000000..63e1731fd --- /dev/null +++ b/api/src/Authorization.Policies/RequirementHandlers/CoalitionLeaderAuthorizationHandler.cs @@ -0,0 +1,49 @@ +using Authorization.Policies.Requirements; +using Authorization.Policies.Specifications; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.RequirementHandlers; + +internal class CoalitionLeaderAuthorizationHandler( + ICurrentUserProvider currentUserProvider, + ICurrentUserRoleProvider currentUserRoleProvider, + IReadRepository coalitionRepository) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + CoalitionLeaderRequirement requirement) + { + if (!currentUserRoleProvider.IsNgoAdmin()) + { + context.Fail(); + return; + } + + var ngoId = currentUserProvider.GetNgoId(); + if (ngoId is null) + { + context.Fail(); + return; + } + + var coalitionLeaderSpecification = new GetCoalitionLeaderDetailsSpecification(requirement.ElectionRoundId, requirement.CoalitionId, ngoId.Value); + var result = await coalitionRepository.FirstOrDefaultAsync(coalitionLeaderSpecification); + + if (result is null) + { + context.Fail(); + return; + } + + if (result.ElectionRoundStatus == ElectionRoundStatus.Archived + || result.NgoStatus == NgoStatus.Deactivated + || result.MonitoringNgoStatus == MonitoringNgoStatus.Suspended) + { + context.Fail(); + return; + } + + context.Succeed(requirement); + } +} diff --git a/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs b/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs new file mode 100644 index 000000000..b36f03d98 --- /dev/null +++ b/api/src/Authorization.Policies/Requirements/CoalitionLeaderRequirement.cs @@ -0,0 +1,7 @@ +namespace Authorization.Policies.Requirements; + +public class CoalitionLeaderRequirement(Guid electionRoundId, Guid coalitionId) : IAuthorizationRequirement +{ + public Guid ElectionRoundId { get; } = electionRoundId; + public Guid CoalitionId { get; } = coalitionId; +} diff --git a/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs b/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs new file mode 100644 index 000000000..9bf1c0586 --- /dev/null +++ b/api/src/Authorization.Policies/Specifications/CoalitionLeaderView.cs @@ -0,0 +1,13 @@ +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.Specifications; + +internal class CoalitionLeaderView +{ + public required Guid ElectionRoundId { get; set; } + public required ElectionRoundStatus ElectionRoundStatus { get; set; } + public required Guid NgoId { get; set; } + public required NgoStatus NgoStatus { get; set; } + public required Guid MonitoringNgoId { get; set; } + public required MonitoringNgoStatus MonitoringNgoStatus { get; set; } +} diff --git a/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs b/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs new file mode 100644 index 000000000..323aeb7e6 --- /dev/null +++ b/api/src/Authorization.Policies/Specifications/GetCoalitionLeaderDetailsSpecification.cs @@ -0,0 +1,26 @@ +using Ardalis.Specification; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Authorization.Policies.Specifications; + +internal sealed class GetCoalitionLeaderDetailsSpecification : SingleResultSpecification +{ + public GetCoalitionLeaderDetailsSpecification(Guid electionRoundId, Guid coalitionId, Guid ngoId) + { + Query + .Include(x => x.ElectionRound) + .Include(x => x.Leader) + .Where(x => x.Leader.NgoId == ngoId && x.ElectionRoundId == electionRoundId && x.Id == coalitionId) + .AsNoTracking(); + + Query.Select(x => new CoalitionLeaderView + { + ElectionRoundId = x.ElectionRoundId, + ElectionRoundStatus = x.ElectionRound.Status, + NgoId = x.Leader.NgoId, + NgoStatus = x.Leader.Ngo.Status, + MonitoringNgoId = x.Id, + MonitoringNgoStatus = x.Leader.Status + }); + } +} diff --git a/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs b/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs index 01c1e6c24..99819c7f4 100644 --- a/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs +++ b/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs @@ -22,4 +22,4 @@ public GetMonitoringNgoSpecification(Guid electionRoundId, Guid ngoId) MonitoringNgoStatus = x.Status }); } -} \ No newline at end of file +} diff --git a/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs index 51afff2c3..359b08a10 100644 --- a/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Authorization.Policies/Specifications/GetMonitoringObserverSpecification.cs @@ -9,7 +9,10 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid? ngoId, Gui Query .Include(x => x.MonitoringNgo) .ThenInclude(x => x.ElectionRound) - .Where(x => x.ObserverId == observerId && x.ElectionRoundId == electionRoundId && x.MonitoringNgo.NgoId == ngoId); + .Where(x => x.ObserverId == observerId + && x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.MonitoringNgo.ElectionRoundId == electionRoundId); Query.Select(x => new MonitoringObserverView { @@ -31,7 +34,7 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) Query .Include(x => x.MonitoringNgo) .ThenInclude(x => x.ElectionRound) - .Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId) + .Where(x => x.ObserverId == observerId && x.ElectionRoundId == electionRoundId && x.MonitoringNgo.ElectionRoundId == electionRoundId) .AsNoTracking(); Query.Select(x => new MonitoringObserverView diff --git a/api/src/Feature.Attachments/Create/Endpoint.cs b/api/src/Feature.Attachments/Create/Endpoint.cs index 305e31364..0cdb651e9 100644 --- a/api/src/Feature.Attachments/Create/Endpoint.cs +++ b/api/src/Feature.Attachments/Create/Endpoint.cs @@ -80,7 +80,7 @@ public override async Task, NotFound, BadRequest, BadRequest>, NotFound, BadReque ElectionRoundId = attachment.ElectionRoundId, PollingStationId = attachment.PollingStationId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs b/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs index 2dfc917ef..1236291a1 100644 --- a/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs +++ b/api/src/Feature.Attachments/Specifications/GetAttachmentByIdSpecification.cs @@ -7,6 +7,8 @@ public sealed class GetAttachmentByIdSpecification : SingleResultSpecification x.ElectionRoundId == electionRoundId - && x.MonitoringObserver.ObserverId == observerId && x.Id == attachmentId); + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId + && x.Id == attachmentId); } } diff --git a/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs b/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs index 79edec906..b75d4f027 100644 --- a/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs +++ b/api/src/Feature.Attachments/Specifications/GetObserverAttachmentsSpecification.cs @@ -10,6 +10,8 @@ public GetObserverAttachmentsSpecification(Guid electionRoundId, Guid pollingSta .Where(x => x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.IsDeleted == false && x.IsCompleted == true); diff --git a/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs index 445308fae..be3c11711 100644 --- a/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs +++ b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs @@ -94,7 +94,7 @@ public override async Task, NotFound>> ExecuteAsyn return TypedResults.Ok(citizenGuideModel with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }); } diff --git a/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs b/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs index 94f2cc926..1555a2598 100644 --- a/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/Get/Endpoint.cs @@ -33,7 +33,7 @@ public override async Task, NotFound>> Exec CN."Title", CN."Body", CN."CreatedOn" "SentAt", - U."FirstName" || ' ' || U."LastName" "Sender" + U."DisplayName" "Sender" FROM "CitizenNotifications" CN INNER JOIN "NgoAdmins" NA ON CN."SenderId" = NA."Id" @@ -49,7 +49,7 @@ public override async Task, NotFound>> Exec { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - id = req.Id, + id = req.Id }; CitizenNotificationModel? notification; @@ -61,4 +61,4 @@ public override async Task, NotFound>> Exec return notification == null ? TypedResults.NotFound() : TypedResults.Ok(notification); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs b/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs index a65585203..4b3a2b03b 100644 --- a/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/ListReceived/Endpoint.cs @@ -43,7 +43,7 @@ ORDER BY var queryArgs = new { - electionRoundId = req.ElectionRoundId, + electionRoundId = req.ElectionRoundId }; string? ngoName; diff --git a/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs b/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs index 133339a97..ffd3e359c 100644 --- a/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs +++ b/api/src/Feature.Citizen.Notifications/ListSent/Endpoint.cs @@ -45,7 +45,7 @@ public override async Task>, CN."Title", CN."Body", CN."CreatedOn" AS "SentAt", - U."FirstName" || ' ' || U."LastName" AS "Sender" + U."DisplayName" AS "Sender" FROM "CitizenNotifications" CN INNER JOIN "ElectionRounds" ER ON CN."ElectionRoundId" = ER."Id" @@ -64,7 +64,7 @@ OFFSET @offset ROWS electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), - pageSize = req.PageSize, + pageSize = req.PageSize }; int totalRowCount; @@ -80,4 +80,4 @@ OFFSET @offset ROWS return TypedResults.Ok( new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs b/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs index b81ba0276..2bf2e4090 100644 --- a/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs +++ b/api/src/Feature.CitizenReports.Attachments/Get/Endpoint.cs @@ -45,7 +45,7 @@ await repository.FirstOrDefaultAsync( ElectionRoundId = attachment.ElectionRoundId, CitizenReportId = attachment.CitizenReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }); } } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs b/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs index 49517317f..0f311aa09 100644 --- a/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs +++ b/api/src/Feature.CitizenReports.Attachments/List/Endpoint.cs @@ -43,7 +43,7 @@ public override async Task ElectionRoundId = attachment.ElectionRoundId, CitizenReportId = attachment.CitizenReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs b/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs index 49af2efcf..da2d7a10f 100644 --- a/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs +++ b/api/src/Feature.CitizenReports.Attachments/Specifications/ListCitizenReportAttachmentsSpecification.cs @@ -9,8 +9,9 @@ public ListCitizenReportAttachmentsSpecification(Guid electionRoundId, Guid citi Query .Where(x => x.ElectionRoundId == electionRoundId && x.FormId == formId + && x.Form.ElectionRoundId == electionRoundId && x.CitizenReportId == citizenReportId && !x.IsDeleted && x.IsCompleted); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs b/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs index a42c3a6ab..1da4c4e89 100644 --- a/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs +++ b/api/src/Feature.CitizenReports.Notes/Specifications/ListNotesSpecification.cs @@ -6,8 +6,11 @@ public sealed class ListNotesSpecification : Specification x.ElectionRoundId == electionRoundId && x.FormId == citizenReportId); - + Query.Where(x => + x.ElectionRoundId == electionRoundId && x.FormId == citizenReportId && + x.Form.ElectionRoundId == electionRoundId + ); + Query.Select(note => CitizenReportNoteModel.FromEntity(note)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetById/Endpoint.cs b/api/src/Feature.CitizenReports/GetById/Endpoint.cs index 419bf3198..fae655abf 100644 --- a/api/src/Feature.CitizenReports/GetById/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetById/Endpoint.cs @@ -37,6 +37,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Id == req.CitizenReportId) .AsSplitQuery() .FirstOrDefaultAsync(ct); @@ -64,7 +65,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -89,9 +90,9 @@ public override async Task, NotFound>> ExecuteAsync(Request LocationLevel2 = citizenReport.Location.Level2, LocationLevel3 = citizenReport.Location.Level3, LocationLevel4 = citizenReport.Location.Level4, - LocationLevel5 = citizenReport.Location.Level5, + LocationLevel5 = citizenReport.Location.Level5 }; return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs b/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs index 4d0c939d2..320aaa508 100644 --- a/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetFilters/Endpoint.cs @@ -50,7 +50,7 @@ SELECT MIN(COALESCE(CR."LastModifiedOn", CR."CreatedOn")) AS "FirstSubmissionTim var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -66,7 +66,7 @@ SELECT MIN(COALESCE(CR."LastModifiedOn", CR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs index 5ffd4d149..25b1861da 100644 --- a/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.CitizenReports/GetSubmissionsAggregated/Endpoint.cs @@ -37,6 +37,7 @@ public override async Task, NotFound>> ExecuteAsync(Citizen .Forms .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Id == req.FormId) .AsNoTracking() .FirstOrDefaultAsync(ct); @@ -58,6 +59,8 @@ private async Task, NotFound>> AggregateCitizenReportsAsync .Include(x => x.Attachments) .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId) .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || EF.Functions.ILike(x.Location.Level1, req.Level1Filter)) @@ -131,8 +134,8 @@ private async Task, NotFound>> AggregateCitizenReportsAsync Level5Filter = req.Level5Filter, HasFlaggedAnswers = req.HasFlaggedAnswers, QuestionsAnswered = req.QuestionsAnswered, - FollowUpStatus = req.FollowUpStatus, + FollowUpStatus = req.FollowUpStatus } }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs b/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs index 67f57649e..26f6f6226 100644 --- a/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.CitizenReports/ListEntries/Endpoint.cs @@ -229,7 +229,7 @@ OFFSET @offset ROWS hasAttachments = req.HasAttachments, hasNotes = req.HasNotes, questionsAnswered = req.QuestionsAnswered?.ToString(), - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; diff --git a/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs b/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs index 4eaa0e342..c5fca31dd 100644 --- a/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs +++ b/api/src/Feature.CitizenReports/Models/CitizenReportModel.cs @@ -23,6 +23,6 @@ public class CitizenReportModel Answers = entity.Answers .Select(AnswerMapper.ToModel) .ToList(), - FollowUpStatus = entity.FollowUpStatus, + FollowUpStatus = entity.FollowUpStatus }; } \ No newline at end of file diff --git a/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs b/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs index 7270d217b..09d163797 100644 --- a/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.CitizenReports/UpdateStatus/Endpoint.cs @@ -16,10 +16,11 @@ public override async Task> ExecuteAsync(Request re { await context.CitizenReports .Where(x => x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs b/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs new file mode 100644 index 000000000..d13a7a281 --- /dev/null +++ b/api/src/Feature.Coalitions/CoalitionsFeatureInstaller.cs @@ -0,0 +1,19 @@ +using Dapper; +using Feature.NgoCoalitions.Models; +using Feature.NgoCoalitions.Services; +using Microsoft.Extensions.DependencyInjection; +using Vote.Monitor.Core.Converters; + +namespace Feature.NgoCoalitions; + +public static class CoalitionsFeatureInstaller +{ + public static IServiceCollection AddCoalitionsFeature(this IServiceCollection services) + { + SqlMapper.AddTypeHandler(typeof(CoalitionMember[]), new JsonToObjectConverter()); + + services.AddScoped(); + + return services; + } +} diff --git a/api/src/Feature.Coalitions/Create/Endpoint.cs b/api/src/Feature.Coalitions/Create/Endpoint.cs new file mode 100644 index 000000000..263037f76 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Endpoint.cs @@ -0,0 +1,64 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Create; + +public class Endpoint(VoteMonitorContext context) : Endpoint, Conflict, NotFound>> +{ + public override void Configure() + { + Post("/api/election-rounds/{electionRoundId}/coalitions"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, Conflict, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) + { + var electionRound = await context.ElectionRounds.FirstOrDefaultAsync(x => x.Id == req.ElectionRoundId, ct); + if (electionRound == null) + { + return TypedResults.NotFound(); + } + + var coalitionWithSameNameExists = await context + .Coalitions + .AnyAsync(x => EF.Functions.ILike(x.Name, req.CoalitionName) && x.ElectionRoundId == req.ElectionRoundId, + ct); + + if (coalitionWithSameNameExists) + { + return TypedResults.Conflict(); + } + + var ngoIds = req.NgoMembersIds.Union([req.LeaderId]).Distinct().ToList(); + var ngos = await context.Ngos.Where(x => ngoIds.Contains(x.Id)).ToListAsync(ct); + + var monitoringNgos = await context + .MonitoringNgos + .Include(x => x.Ngo) + .Where(x => ngoIds.Contains(x.NgoId) && x.ElectionRoundId == req.ElectionRoundId) + .ToListAsync(ct); + + var ngosToAddAsMonitoringNgos = ngos.Where(ngo => monitoringNgos.All(x => x.NgoId != ngo.Id)).ToList(); + + foreach (var ngo in ngosToAddAsMonitoringNgos) + { + var monitoringNgo = electionRound.AddMonitoringNgo(ngo); + monitoringNgos.Add(monitoringNgo); + context.MonitoringNgos.Add(monitoringNgo); + } + + var coalition = Coalition.Create(electionRound, + req.CoalitionName, + monitoringNgos.First(x => x.NgoId == req.LeaderId), + monitoringNgos); + + context.Coalitions.Add(coalition); + + await context.SaveChangesAsync(ct); + + return TypedResults.Ok(CoalitionModel.FromEntity(coalition)); + } +} diff --git a/api/src/Feature.Coalitions/Create/Request.cs b/api/src/Feature.Coalitions/Create/Request.cs new file mode 100644 index 000000000..1bf6d5036 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Request.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.Create; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public string CoalitionName { get; set; } + public Guid LeaderId { get; set; } + public Guid[] NgoMembersIds { get; set; } +} diff --git a/api/src/Feature.Coalitions/Create/Validator.cs b/api/src/Feature.Coalitions/Create/Validator.cs new file mode 100644 index 000000000..e0b0e1aa5 --- /dev/null +++ b/api/src/Feature.Coalitions/Create/Validator.cs @@ -0,0 +1,12 @@ +namespace Feature.NgoCoalitions.Create; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.LeaderId).NotEmpty(); + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + RuleFor(x => x.CoalitionName).NotEmpty().MaximumLength(256); + } +} diff --git a/api/src/Feature.Coalitions/Delete/Endpoint.cs b/api/src/Feature.Coalitions/Delete/Endpoint.cs new file mode 100644 index 000000000..b50eebafe --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Endpoint.cs @@ -0,0 +1,47 @@ +using Feature.NgoCoalitions.Services; + +namespace Feature.NgoCoalitions.Delete; + +public class Endpoint(VoteMonitorContext context, IFormSubmissionsCleanupService cleanupService) + : Endpoint> +{ + public override void Configure() + { + Delete("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task> ExecuteAsync(Request req, CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .Where(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId) + .FirstOrDefaultAsync(ct); + + if (coalition == null) + { + return TypedResults.NotFound(); + } + + // members data should be purged but coalition leader keeps their data + var membersToRemove = coalition + .Memberships + .Where(x => x.MonitoringNgoId != coalition.LeaderId) + .Select(x => x.MonitoringNgoId) + .ToList(); + + // Delete orphaned data + if (membersToRemove.Any()) + { + await Task.WhenAll(membersToRemove.Select(monitoringNgoId => + cleanupService.CleanupFormSubmissionsAsync(req.ElectionRoundId, req.CoalitionId, monitoringNgoId))); + } + + context.Coalitions.Remove(coalition); + await context.SaveChangesAsync(ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Coalitions/Delete/Request.cs b/api/src/Feature.Coalitions/Delete/Request.cs new file mode 100644 index 000000000..832909b04 --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.NgoCoalitions.Delete; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } +} diff --git a/api/src/Feature.Coalitions/Delete/Validator.cs b/api/src/Feature.Coalitions/Delete/Validator.cs new file mode 100644 index 000000000..82909d1fd --- /dev/null +++ b/api/src/Feature.Coalitions/Delete/Validator.cs @@ -0,0 +1,10 @@ +namespace Feature.NgoCoalitions.Delete; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/EnableTesting.cs b/api/src/Feature.Coalitions/EnableTesting.cs new file mode 100644 index 000000000..237ecb7ec --- /dev/null +++ b/api/src/Feature.Coalitions/EnableTesting.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Feature.Coalitions.UnitTests")] +[assembly: InternalsVisibleTo("Vote.Monitor.Api.IntegrationTests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/api/src/Feature.Coalitions/Feature.Coalitions.csproj b/api/src/Feature.Coalitions/Feature.Coalitions.csproj new file mode 100644 index 000000000..8c9f1c863 --- /dev/null +++ b/api/src/Feature.Coalitions/Feature.Coalitions.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + Feature.NgoCoalitions + + + + + + + + + + + + + + + diff --git a/api/src/Feature.Coalitions/FormAccess/Endpoint.cs b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs new file mode 100644 index 000000000..df84dd405 --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs @@ -0,0 +1,98 @@ +using Authorization.Policies.Requirements; +using Feature.NgoCoalitions.Services; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.FormAccess; + +public class Endpoint( + VoteMonitorContext context, + IFormSubmissionsCleanupService cleanupService, + IAuthorizationService authorizationService) + : Endpoint> +{ + public override void Configure() + { + Post("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task> ExecuteAsync(Request req, + CancellationToken ct) + { + var authorizationResult = await authorizationService.AuthorizeAsync(User, + new CoalitionLeaderRequirement(req.ElectionRoundId, req.CoalitionId)); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var form = await context.Forms + .Where(f => f.ElectionRoundId == req.ElectionRoundId + && f.Id == req.FormId + && f.ElectionRoundId == req.ElectionRoundId + && f.MonitoringNgo.NgoId == req.NgoId + && f.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) + .Select(f => new { f.Id, f.Status }) + .AsNoTracking() + .FirstOrDefaultAsync(ct); + + if (form is null) + { + return TypedResults.NotFound(); + } + + var coalition = await context.Coalitions + .Where(c => c.ElectionRoundId == req.ElectionRoundId && c.Id == req.CoalitionId) + .Include(x => x.FormAccess.Where(fa => fa.FormId == form.Id && fa.Form.ElectionRoundId == req.ElectionRoundId)) + .Include(x => x.Memberships) + .FirstOrDefaultAsync(ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + var requestNgoMembers = req.NgoMembersIds.Distinct().ToList(); + var coalitionMembersIds = coalition + .Memberships + .Select(x => x.MonitoringNgoId) + .Distinct() + .ToList(); + + var monitoringNgosWithAccessToForm = await context.MonitoringNgos + .Where(x => x.ElectionRoundId == req.ElectionRoundId + && requestNgoMembers.Contains(x.NgoId) + && coalitionMembersIds.Contains(x.Id)) + .Select(x => x.Id) + .ToListAsync(ct); + + var ngosWithRevokedAccess = + coalition.FormAccess + .Where(x => !monitoringNgosWithAccessToForm.Contains(x.MonitoringNgoId)) + .ToList(); + + var ngosWithFormAccess = + monitoringNgosWithAccessToForm.Where(x => coalition.FormAccess.All(fa => fa.MonitoringNgoId != x)) + .Select(id => CoalitionFormAccess.Create(coalition.Id, id, req.FormId)) + .ToList(); + + if (ngosWithRevokedAccess.Any()) + { + context.CoalitionFormAccess.RemoveRange(ngosWithRevokedAccess); + + await Task.WhenAll(ngosWithRevokedAccess.Select(memberId => cleanupService.CleanupFormSubmissionsAsync(req.ElectionRoundId, req.CoalitionId, memberId.MonitoringNgoId, form.Id))); + } + + if (ngosWithFormAccess.Any()) + { + await context.CoalitionFormAccess.AddRangeAsync(ngosWithFormAccess, ct); + } + + await context.SaveChangesAsync(ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Coalitions/FormAccess/Request.cs b/api/src/Feature.Coalitions/FormAccess/Request.cs new file mode 100644 index 000000000..314ce218b --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Request.cs @@ -0,0 +1,13 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.NgoCoalitions.FormAccess; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + public Guid CoalitionId { get; set; } + public Guid FormId { get; set; } + public Guid[] NgoMembersIds { get; set; } = []; +} diff --git a/api/src/Feature.Coalitions/FormAccess/Validator.cs b/api/src/Feature.Coalitions/FormAccess/Validator.cs new file mode 100644 index 000000000..be989da77 --- /dev/null +++ b/api/src/Feature.Coalitions/FormAccess/Validator.cs @@ -0,0 +1,12 @@ +namespace Feature.NgoCoalitions.FormAccess; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + RuleFor(x => x.FormId).NotEmpty(); + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/Get/Endpoint.cs b/api/src/Feature.Coalitions/Get/Endpoint.cs new file mode 100644 index 000000000..61cd37c64 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Endpoint.cs @@ -0,0 +1,34 @@ +using Feature.NgoCoalitions.Models; + +namespace Feature.NgoCoalitions.Get; + +public class Endpoint(VoteMonitorContext context) : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .ThenInclude(x => x.MonitoringNgo) + .ThenInclude(x => x.Ngo) + .Include(x => x.Leader) + .ThenInclude(x => x.Ngo) + .Where(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId) + .Select(x => CoalitionModel.FromEntity(x)) + .FirstOrDefaultAsync(ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(coalition); + } +} diff --git a/api/src/Feature.Coalitions/Get/Request.cs b/api/src/Feature.Coalitions/Get/Request.cs new file mode 100644 index 000000000..45f05fc36 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.NgoCoalitions.Get; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } +} diff --git a/api/src/Feature.Coalitions/Get/Validator.cs b/api/src/Feature.Coalitions/Get/Validator.cs new file mode 100644 index 000000000..be41754a9 --- /dev/null +++ b/api/src/Feature.Coalitions/Get/Validator.cs @@ -0,0 +1,10 @@ +namespace Feature.NgoCoalitions.Get; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.CoalitionId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/GetMy/Endpoint.cs b/api/src/Feature.Coalitions/GetMy/Endpoint.cs new file mode 100644 index 000000000..c1175cad3 --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Endpoint.cs @@ -0,0 +1,45 @@ +using Authorization.Policies.Requirements; +using Feature.NgoCoalitions.Models; +using Microsoft.AspNetCore.Authorization; + +namespace Feature.NgoCoalitions.GetMy; + +public class Endpoint(IAuthorizationService authorizationService, VoteMonitorContext context) + : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions:my"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Summary(s => + { + s.Summary = "Gets coalition details for current ngo and selected election round"; + }); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var result = + await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); + if (!result.Succeeded) + { + return TypedResults.NotFound(); + } + + var coalition = await context.Coalitions + .Include(x=>x.Memberships) + .ThenInclude(x=>x.MonitoringNgo) + .ThenInclude(x=>x.Ngo) + .Include(x=>x.Leader) + .ThenInclude(x=>x.Ngo) + .Where(x => x.Memberships.Any(m => m.ElectionRoundId == req.ElectionRoundId + && m.MonitoringNgo.NgoId == req.NgoId + && m.MonitoringNgo.ElectionRoundId == req.ElectionRoundId)) + .Select(c => CoalitionModel.FromEntity(c)) + .FirstOrDefaultAsync(ct); + + return TypedResults.Ok(coalition); + } +} diff --git a/api/src/Feature.Coalitions/GetMy/Request.cs b/api/src/Feature.Coalitions/GetMy/Request.cs new file mode 100644 index 000000000..db26448a7 --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Request.cs @@ -0,0 +1,11 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.NgoCoalitions.GetMy; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } +} diff --git a/api/src/Feature.Coalitions/GetMy/Validator.cs b/api/src/Feature.Coalitions/GetMy/Validator.cs new file mode 100644 index 000000000..c79543d6e --- /dev/null +++ b/api/src/Feature.Coalitions/GetMy/Validator.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.GetMy; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.NgoId).NotEmpty(); + } +} diff --git a/api/src/Feature.Coalitions/GlobalUsings.cs b/api/src/Feature.Coalitions/GlobalUsings.cs new file mode 100644 index 000000000..ae4f6427c --- /dev/null +++ b/api/src/Feature.Coalitions/GlobalUsings.cs @@ -0,0 +1,11 @@ +// Global using directives + +global using Ardalis.Specification; +global using Authorization.Policies; +global using FastEndpoints; +global using FluentValidation; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Http.HttpResults; +global using Microsoft.EntityFrameworkCore; +global using Vote.Monitor.Domain; +global using Vote.Monitor.Domain.Repository; diff --git a/api/src/Feature.Coalitions/List/Endpoint.cs b/api/src/Feature.Coalitions/List/Endpoint.cs new file mode 100644 index 000000000..251663540 --- /dev/null +++ b/api/src/Feature.Coalitions/List/Endpoint.cs @@ -0,0 +1,141 @@ +using Dapper; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.ConnectionFactory; +using Vote.Monitor.Domain.Specifications; + +namespace Feature.NgoCoalitions.List; + +public class Endpoint(INpgsqlConnectionFactory dbConnectionFactory) + : Endpoint>, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/coalitions"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task>, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) + { + var sql = """ + SELECT + COUNT(*) COUNT + FROM + "Coalitions" C + WHERE + ( + @searchText IS NULL + OR @searchText = '' + OR C."Id"::TEXT ILIKE @searchText + OR C."Name" ILIKE @searchText + ) + AND C."ElectionRoundId" = @electionRoundId; + + SELECT + C."Id", + C."Name", + C."Members", + C."LeaderId", + C."LeaderName" + FROM + ( + SELECT + C."Id", + C."Name", + N."Id" as "LeaderId", + N."Name" as "LeaderName", + COALESCE( + ( + SELECT + JSONB_AGG( + JSONB_BUILD_OBJECT('Id', MN."NgoId", 'Name', N."Name") + ) + FROM + "CoalitionMemberships" CM + INNER JOIN "MonitoringNgos" MN ON CM."MonitoringNgoId" = MN."Id" + INNER JOIN "Ngos" N ON MN."NgoId" = N."Id" + WHERE + CM."ElectionRoundId" = @electionRoundId + AND CM."CoalitionId" = C."Id" + ), + '[]'::JSONB + ) AS "Members" + FROM + "Coalitions" C + INNER JOIN "MonitoringNgos" MN on C."LeaderId" = MN."Id" + INNER JOIN "Ngos" N on MN."NgoId" = N."Id" + WHERE + C."ElectionRoundId" = @electionRoundId + AND ( + @searchText IS NULL + OR @searchText = '' + OR C."Id"::TEXT ILIKE @searchText + OR C."Name" ILIKE @searchText + ) + ) C + ORDER BY + CASE + WHEN @sortExpression = 'Name ASC' THEN "Name" + END ASC, + CASE + WHEN @sortExpression = 'Name DESC' THEN "Name" + END DESC, + CASE + WHEN @sortExpression = 'NumberOfMembers ASC' THEN JSONB_ARRAY_LENGTH("Members") + END ASC, + CASE + WHEN @sortExpression = 'NumberOfMembers DESC' THEN JSONB_ARRAY_LENGTH("Members") + END DESC + OFFSET @offset ROWS + FETCH NEXT @pageSize ROWS ONLY; + """; + + var queryArgs = new + { + electionRoundId = req.ElectionRoundId, + offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), + pageSize = req.PageSize, + searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) + }; + + int totalRowCount = 0; + List entries; + + using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) + { + using var multi = await dbConnection.QueryMultipleAsync(sql, queryArgs); + totalRowCount = multi.Read().Single(); + entries = multi.Read().ToList(); + } + + return TypedResults.Ok( + new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize)); + } + + private static string GetSortExpression(string? sortColumnName, bool isAscendingSorting) + { + if (string.IsNullOrWhiteSpace(sortColumnName)) + { + return "Name ASC"; + } + + var sortOrder = isAscendingSorting ? "ASC" : "DESC"; + + if (string.Equals(sortColumnName, nameof(CoalitionModel.Name), StringComparison.InvariantCultureIgnoreCase)) + { + return $"{nameof(CoalitionModel.Name)} {sortOrder}"; + } + + if (string.Equals(sortColumnName, nameof(CoalitionModel.NumberOfMembers), + StringComparison.InvariantCultureIgnoreCase)) + { + return $"{nameof(CoalitionModel.NumberOfMembers)} {sortOrder}"; + } + + return "Name ASC"; + } +} diff --git a/api/src/Feature.Coalitions/List/Request.cs b/api/src/Feature.Coalitions/List/Request.cs new file mode 100644 index 000000000..c97e3fefa --- /dev/null +++ b/api/src/Feature.Coalitions/List/Request.cs @@ -0,0 +1,9 @@ +using Vote.Monitor.Core.Models; + +namespace Feature.NgoCoalitions.List; + +public class Request : BaseSortPaginatedRequest +{ + public Guid ElectionRoundId { get; set; } + [QueryParam] public string? SearchText { get; set; } +} diff --git a/api/src/Feature.Coalitions/List/Validator.cs b/api/src/Feature.Coalitions/List/Validator.cs new file mode 100644 index 000000000..f84974ec9 --- /dev/null +++ b/api/src/Feature.Coalitions/List/Validator.cs @@ -0,0 +1,14 @@ +namespace Feature.NgoCoalitions.List; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + + RuleFor(x => x.PageNumber) + .GreaterThanOrEqualTo(1); + + RuleFor(x => x.PageSize).InclusiveBetween(1, 100); + } +} diff --git a/api/src/Feature.Coalitions/Models/CoalitionMember.cs b/api/src/Feature.Coalitions/Models/CoalitionMember.cs new file mode 100644 index 000000000..216cc1741 --- /dev/null +++ b/api/src/Feature.Coalitions/Models/CoalitionMember.cs @@ -0,0 +1,14 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.NgoCoalitions.Models; + +public class CoalitionMember +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + + public static CoalitionMember FromEntity(MonitoringNgo ngo) + { + return new CoalitionMember { Id = ngo.NgoId, Name = ngo.Ngo.Name }; + } +} diff --git a/api/src/Feature.Coalitions/Models/CoalitionModel.cs b/api/src/Feature.Coalitions/Models/CoalitionModel.cs new file mode 100644 index 000000000..f7077a46f --- /dev/null +++ b/api/src/Feature.Coalitions/Models/CoalitionModel.cs @@ -0,0 +1,28 @@ +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Models; + +public class CoalitionModel +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public Guid LeaderId { get; set; } + public string LeaderName { get; set; } = string.Empty; + + public int NumberOfMembers => Members.Length; + public CoalitionMember[] Members { get; init; } = []; + + public static CoalitionModel? FromEntity(Coalition? coalition) + { + return coalition is null + ? null + : new CoalitionModel + { + Id = coalition.Id, + Name = coalition.Name, + LeaderId = coalition.Leader.NgoId, + LeaderName = coalition.Leader.Ngo.Name, + Members = coalition.Memberships.Select(x => CoalitionMember.FromEntity(x.MonitoringNgo)).ToArray() + }; + } +} diff --git a/api/src/Feature.Coalitions/Services/FormSubmissionsCleanupService.cs b/api/src/Feature.Coalitions/Services/FormSubmissionsCleanupService.cs new file mode 100644 index 000000000..57a879840 --- /dev/null +++ b/api/src/Feature.Coalitions/Services/FormSubmissionsCleanupService.cs @@ -0,0 +1,99 @@ +namespace Feature.NgoCoalitions.Services; + +public class FormSubmissionsCleanupService(VoteMonitorContext context) : IFormSubmissionsCleanupService +{ + public async Task CleanupFormSubmissionsAsync(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId) + { + var formIds = await context.CoalitionFormAccess.Where(x => + x.CoalitionId == coalitionId && x.Coalition.ElectionRoundId == electionRoundId) + .Select(x => x.FormId) + .Distinct() + .ToListAsync(); + + await context + .FormSubmissions + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => formIds.Contains(x.FormId)) + .ExecuteDeleteAsync(); + + await context + .Attachments + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId && formIds.Contains(x.FormId)) + .ExecuteDeleteAsync(); + + await context + .Notes + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => formIds.Contains(x.FormId)) + .ExecuteDeleteAsync(); + + await context + .IncidentReports + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => formIds.Contains(x.FormId)) + .ExecuteDeleteAsync(); + + await context + .IncidentReportAttachments + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.IncidentReport.MonitoringObserver.MonitoringNgoId) + .Where(x => formIds.Contains(x.FormId)) + .ExecuteDeleteAsync(); + } + + public async Task CleanupFormSubmissionsAsync(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId, + Guid formId) + { + if (!await context.CoalitionFormAccess.AnyAsync(x => + x.Form.ElectionRoundId == electionRoundId + && x.FormId == formId + && x.CoalitionId == coalitionId + && x.Coalition.ElectionRoundId == electionRoundId)) + { + return; + } + + await context + .FormSubmissions + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => x.FormId == formId) + .Where(x => x.Form.ElectionRoundId == electionRoundId) + .ExecuteDeleteAsync(); + + await context + .Attachments + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => x.FormId == formId) + .Where(x => x.Form.ElectionRoundId == electionRoundId) + .ExecuteDeleteAsync(); + + await context + .Notes + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => x.FormId == formId) + .Where(x => x.Form.ElectionRoundId == electionRoundId) + .ExecuteDeleteAsync(); + + await context + .IncidentReports + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.MonitoringObserver.MonitoringNgoId) + .Where(x => x.Form.ElectionRoundId == electionRoundId) + .Where(x => x.FormId == formId) + .ExecuteDeleteAsync(); + + await context + .IncidentReportAttachments + .Where(x => x.ElectionRoundId == electionRoundId) + .Where(x => monitoringNgoId == x.IncidentReport.MonitoringObserver.MonitoringNgoId) + .Where(x => x.FormId == formId) + .Where(x => x.Form.ElectionRoundId == electionRoundId) + .ExecuteDeleteAsync(); + } +} diff --git a/api/src/Feature.Coalitions/Services/IFormSubmissionsCleanupService.cs b/api/src/Feature.Coalitions/Services/IFormSubmissionsCleanupService.cs new file mode 100644 index 000000000..26e856d5c --- /dev/null +++ b/api/src/Feature.Coalitions/Services/IFormSubmissionsCleanupService.cs @@ -0,0 +1,23 @@ +namespace Feature.NgoCoalitions.Services; + +public interface IFormSubmissionsCleanupService +{ + /// + /// Removes all data submitted by a ngo ,ex member of the coalition + /// + /// + /// + /// + /// + Task CleanupFormSubmissionsAsync(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId); + + /// + /// Removes all data submitted by a ngo for a form when form access is revoked + /// + /// + /// + /// + /// + /// + Task CleanupFormSubmissionsAsync(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId, Guid formId); +} diff --git a/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs b/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs new file mode 100644 index 000000000..d63dbac68 --- /dev/null +++ b/api/src/Feature.Coalitions/Specifications/GetMonitoringNgos.cs @@ -0,0 +1,8 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.NgoCoalitions.Specifications; + +public class GetMonitoringNgos: Specification +{ + +} diff --git a/api/src/Feature.Coalitions/Update/Endpoint.cs b/api/src/Feature.Coalitions/Update/Endpoint.cs new file mode 100644 index 000000000..8e7324083 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Endpoint.cs @@ -0,0 +1,75 @@ +using Feature.NgoCoalitions.Models; +using Feature.NgoCoalitions.Services; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.NgoCoalitions.Update; + +public class Endpoint(VoteMonitorContext context, IFormSubmissionsCleanupService cleanupService) + : Endpoint, NotFound, ProblemDetails>> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + DontAutoTag(); + Options(x => x.WithTags("coalitions")); + Policies(PolicyNames.PlatformAdminsOnly); + } + + public override async Task, NotFound, ProblemDetails>> ExecuteAsync(Request req, + CancellationToken ct) + { + var coalition = await context.Coalitions + .Include(x => x.Memberships) + .ThenInclude(x => x.MonitoringNgo) + .Include(x => x.Leader) + .ThenInclude(x => x.Ngo) + .Include(x => x.ElectionRound) + .FirstOrDefaultAsync(x => x.Id == req.CoalitionId && x.ElectionRoundId == req.ElectionRoundId, ct); + + if (coalition is null) + { + return TypedResults.NotFound(); + } + + var ngoIds = req.NgoMembersIds.Distinct().ToList(); + var newMembers = await context.Ngos.Where(x => ngoIds.Contains(x.Id)).ToListAsync(ct); + + var coalitionMembers = await context + .MonitoringNgos + .Include(x => x.Ngo) + .Where(x => ngoIds.Contains(x.NgoId) && x.ElectionRoundId == req.ElectionRoundId) + .ToListAsync(ct); + + var ngosToAddAsMonitoringNgos = newMembers.Where(ngo => coalitionMembers.All(x => x.NgoId != ngo.Id)).ToList(); + + foreach (var ngo in ngosToAddAsMonitoringNgos) + { + var monitoringNgo = coalition.ElectionRound.AddMonitoringNgo(ngo); + coalitionMembers.Add(monitoringNgo); + context.MonitoringNgos.Add(monitoringNgo); + } + + var oldMembers = coalition.Memberships; + + coalition.Update(req.CoalitionName, + coalitionMembers.Select(x => CoalitionMembership.Create(req.ElectionRoundId, req.CoalitionId, x.Id))); + + var removedMembers = oldMembers + .Where(cm=>cm.MonitoringNgo.Id != coalition.LeaderId) + .Where(cm => coalitionMembers.All(x => x.Id != cm.MonitoringNgo.Id)) + .Select(x => x.MonitoringNgo.Id) + .ToList(); + + // Delete orphaned data + if (removedMembers.Any()) + { + foreach (var memberId in removedMembers) + { + await cleanupService.CleanupFormSubmissionsAsync(req.ElectionRoundId, req.CoalitionId, memberId); + } + } + + await context.SaveChangesAsync(ct); + return TypedResults.Ok(CoalitionModel.FromEntity(coalition)); + } +} diff --git a/api/src/Feature.Coalitions/Update/Request.cs b/api/src/Feature.Coalitions/Update/Request.cs new file mode 100644 index 000000000..db6663648 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Request.cs @@ -0,0 +1,9 @@ +namespace Feature.NgoCoalitions.Update; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid CoalitionId { get; set; } + public string CoalitionName { get; set; } + public Guid[] NgoMembersIds { get; set; } = []; +} diff --git a/api/src/Feature.Coalitions/Update/Validator.cs b/api/src/Feature.Coalitions/Update/Validator.cs new file mode 100644 index 000000000..42e3d25e1 --- /dev/null +++ b/api/src/Feature.Coalitions/Update/Validator.cs @@ -0,0 +1,19 @@ +namespace Feature.NgoCoalitions.Update; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId) + .NotEmpty(); + + RuleFor(x => x.CoalitionId) + .NotEmpty(); + + RuleFor(x => x.CoalitionName) + .NotEmpty() + .MaximumLength(256); + + RuleForEach(x => x.NgoMembersIds).NotEmpty(); + } +} diff --git a/api/src/Feature.Form.Submissions/Any/Endpoint.cs b/api/src/Feature.Form.Submissions/Any/Endpoint.cs index 43c83f0a9..d9b93cc4e 100644 --- a/api/src/Feature.Form.Submissions/Any/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Any/Endpoint.cs @@ -46,7 +46,7 @@ AND JSONB_ARRAY_LENGTH(FS."Answers") > 0 { electionRoundId = req.ElectionRoundId, observerId = req.ObserverId, - pollingStationId = req.PollingStationId, + pollingStationId = req.PollingStationId }; hasSubmissionsForPollingStation = await dbConnection.QuerySingleAsync(sql, queryParams); diff --git a/api/src/Feature.Form.Submissions/Delete/Endpoint.cs b/api/src/Feature.Form.Submissions/Delete/Endpoint.cs index 75d6de9d5..b61c0b2ba 100644 --- a/api/src/Feature.Form.Submissions/Delete/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Delete/Endpoint.cs @@ -28,24 +28,30 @@ public override async Task> ExecuteAsync(Request re await context.FormSubmissions .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteDeleteAsync(ct); await context.Notes .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteDeleteAsync(ct); await context.Attachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(a => a.IsDeleted, true), ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs index ef0e8f1fc..728e7ed91 100644 --- a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs @@ -39,6 +39,7 @@ public override async Task, NotFound>> ExecuteAsync(FormSub .Forms .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Id == req.FormId) .Where(x => x.Status == FormStatus.Published) .Where(x => x.FormType != FormType.CitizenReporting && x.FormType != FormType.IncidentReporting) @@ -75,6 +76,8 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId) .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || EF.Functions.ILike(x.PollingStation.Level1, req.Level1Filter)) @@ -105,22 +108,26 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA ? context.Notes.Count(n => n.MonitoringObserverId == x.MonitoringObserverId && n.FormId == x.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && n.PollingStationId == x.PollingStationId && n.ElectionRoundId == x.ElectionRoundId) > 0 : context.Notes.Count(n => n.MonitoringObserverId == x.MonitoringObserverId && n.FormId == x.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && n.PollingStationId == x.PollingStationId && n.ElectionRoundId == x.ElectionRoundId) == 0)) .Where(x => req.HasAttachments == null || (req.HasAttachments.Value ? context.Attachments.Count(a => a.MonitoringObserverId == x.MonitoringObserverId && a.FormId == x.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && a.PollingStationId == x.PollingStationId && a.ElectionRoundId == x.ElectionRoundId) > 0 : context.Attachments.Count(a => a.MonitoringObserverId == x.MonitoringObserverId && a.FormId == x.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && a.PollingStationId == x.PollingStationId && a.ElectionRoundId == x.ElectionRoundId) == 0)) .Where(x => req.IsCompletedFilter == null || x.IsCompleted == req.IsCompletedFilter) @@ -241,7 +248,7 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA HasFlaggedAnswers = req.HasFlaggedAnswers, IsCompletedFilter = req.IsCompletedFilter, MonitoringObserverStatus = req.MonitoringObserverStatus, - PollingStationNumberFilter = req.PollingStationNumberFilter, + PollingStationNumberFilter = req.PollingStationNumberFilter } }); } @@ -258,6 +265,7 @@ private async Task, NotFound>> AggregatePSIFormSubmissionsA .ThenInclude(x => x.Observer) .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId) .Where(x => string.IsNullOrWhiteSpace(req.Level1Filter) || EF.Functions.ILike(x.PollingStation.Level1, req.Level1Filter)) @@ -303,4 +311,4 @@ private async Task, NotFound>> AggregatePSIFormSubmissionsA Attachments = [] }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetById/Endpoint.cs b/api/src/Feature.Form.Submissions/GetById/Endpoint.cs index 28c737a82..0d790a894 100644 --- a/api/src/Feature.Form.Submissions/GetById/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetById/Endpoint.cs @@ -106,7 +106,7 @@ UNION ALL ps."Level5", ps."Number", s."MonitoringObserverId", - u."FirstName" || ' ' || u."LastName" "ObserverName", + u."DisplayName" "ObserverName", u."Email", u."PhoneNumber", mo."Tags", @@ -135,7 +135,7 @@ ORDER BY "TimeSubmitted" desc { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - submissionId = req.SubmissionId, + submissionId = req.SubmissionId }; Response submission = null; @@ -163,4 +163,4 @@ ORDER BY "TimeSubmitted" desc return TypedResults.Ok(submission); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs index 9392ade66..bdfded7e6 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs @@ -77,7 +77,7 @@ UNION ALL var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -93,7 +93,7 @@ UNION ALL return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs index 516c1ba59..ea3133386 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs @@ -37,7 +37,7 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter); SELECT @@ -54,7 +54,7 @@ SELECT COUNT(*) count FROM ( SELECT MO."Id" "MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" "ObserverName", + U."DisplayName" "ObserverName", U."PhoneNumber", U."Email", MO."Tags", @@ -147,7 +147,7 @@ ELSE NULL WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) ) T WHERE @@ -190,7 +190,7 @@ OFFSET @offset ROWS tagsFilter = req.TagsFilter ?? [], searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), - needsFollowUp = req.FollowUpStatus?.ToString(), + needsFollowUp = req.FollowUpStatus?.ToString() }; int totalRowCount = 0; @@ -266,4 +266,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(ObserverSubmissionOverview.ObserverName)} ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs index b8f77232e..f7ce2d069 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs @@ -39,7 +39,7 @@ SELECT SUM(count) WHERE mn."ElectionRoundId" = @electionRoundId AND mn."NgoId" = @ngoId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@formType IS NULL OR 'PSI' = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) @@ -71,7 +71,7 @@ UNION ALL SELECT count(*) AS count WHERE mn."ElectionRoundId" = @electionRoundId AND mn."NgoId" = @ngoId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@formType IS NULL OR f."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) @@ -190,7 +190,7 @@ SELECT COUNT(1) ps."Level5", ps."Number", s."MonitoringObserverId", - u."FirstName" || ' ' || u."LastName" AS "ObserverName", + u."DisplayName" AS "ObserverName", u."Email", u."PhoneNumber", mo."Status", @@ -216,8 +216,7 @@ UNION ALL AND mn."NgoId" = @ngoId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) AND (@searchText IS NULL OR @searchText = '' - OR u."FirstName" ILIKE @searchText - OR u."LastName" ILIKE @searchText + OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@formType IS NULL OR s."FormType" = @formType) @@ -257,8 +256,8 @@ ORDER BY CASE WHEN @sortExpression = 'Level5 DESC' THEN ps."Level5" END DESC, CASE WHEN @sortExpression = 'Number ASC' THEN ps."Number" END ASC, CASE WHEN @sortExpression = 'Number DESC' THEN ps."Number" END DESC, - CASE WHEN @sortExpression = 'ObserverName ASC' THEN u."FirstName" || ' ' || u."LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN u."FirstName" || ' ' || u."LastName" END DESC, + CASE WHEN @sortExpression = 'ObserverName ASC' THEN u."DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN u."DisplayName" END DESC, CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers ASC' THEN s."NumberOfFlaggedAnswers" END ASC, CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers DESC' THEN s."NumberOfFlaggedAnswers" END DESC, CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered ASC' THEN s."NumberOfQuestionsAnswered" END ASC, @@ -435,4 +434,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(FormSubmissionEntry.TimeSubmitted)} DESC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs b/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs index b16dcd42c..bee59e7fb 100644 --- a/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/SetCompletion/Endpoint.cs @@ -27,11 +27,13 @@ public override async Task> ExecuteAsync(Request re await context.FormSubmissions .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs new file mode 100644 index 000000000..a236a6aa2 --- /dev/null +++ b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs @@ -0,0 +1,20 @@ +using Ardalis.Specification; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.Form.Submissions.Specifications; + +public sealed class GetCoalitionFormSpecification : SingleResultSpecification +{ + public GetCoalitionFormSpecification(Guid electionRondId, Guid observerId, Guid formId) + { + Query.Where(x => x.ElectionRoundId == electionRondId) + .Where(x => x.Memberships.Any(cm => + cm.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId))) + .Where(x => x.FormAccess.Any(fa => + fa.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId && fa.FormId == formId))); + + Query.Include(x => x.FormAccess).ThenInclude(x => x.Form).ThenInclude(x => x.ElectionRound); + + Query.Select(x => x.FormAccess.First(f => f.FormId == formId).Form); + } +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs deleted file mode 100644 index 826632012..000000000 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSpecification.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ardalis.Specification; - -namespace Feature.Form.Submissions.Specifications; - -public sealed class GetFormSpecification : SingleResultSpecification -{ - public GetFormSpecification(Guid electionRondId, Guid formId) - { - Query.Where(x => x.ElectionRoundId == electionRondId && x.Id == formId); - Query.Include(x => x.ElectionRound); - } -} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs index d9dfbd80a..e77544bcb 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionForObserverSpecification.cs @@ -4,14 +4,16 @@ namespace Feature.Form.Submissions.Specifications; public sealed class GetFormSubmissionForObserverSpecification : Specification { - public GetFormSubmissionForObserverSpecification(Guid electionRoundId, Guid observerId, List? pollingStationIds) + public GetFormSubmissionForObserverSpecification(Guid electionRoundId, Guid observerId, + List? pollingStationIds) { Query.Where(x => - x.ElectionRoundId == electionRoundId && - x.MonitoringObserver.ObserverId == observerId) - .Where(x => pollingStationIds.Contains(x.PollingStationId), pollingStationIds != null && pollingStationIds.Any()); + x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId) + .Where(x => pollingStationIds.Contains(x.PollingStationId), + pollingStationIds != null && pollingStationIds.Any()); Query.Select(x => FormSubmissionModel.FromEntity(x)); - } } diff --git a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs index 169cecbd3..5cf81f325 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetFormSubmissionSpecification.cs @@ -9,7 +9,9 @@ public GetFormSubmissionSpecification(Guid electionRoundId, Guid pollingStationI Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } @@ -17,6 +19,7 @@ public GetFormSubmissionSpecification(Guid electionRoundId, Guid ngoId, Guid sub { Query.Where(x => x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId && x.Id == submissionId); } diff --git a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs new file mode 100644 index 000000000..d45dd6056 --- /dev/null +++ b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringNgoFormSpecification.cs @@ -0,0 +1,16 @@ +using Ardalis.Specification; + +namespace Feature.Form.Submissions.Specifications; + +public sealed class GetMonitoringNgoFormSpecification : SingleResultSpecification +{ + public GetMonitoringNgoFormSpecification(Guid electionRondId, Guid observerId, Guid formId) + { + Query.Where(x => + x.ElectionRoundId == electionRondId + && x.Id == formId && + x.MonitoringNgo.MonitoringObservers.Any(mo => + mo.ObserverId == observerId && mo.ElectionRoundId == electionRondId)); + Query.Include(x => x.ElectionRound); + } +} diff --git a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs index 1122644f3..452b9a9d2 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetMonitoringObserverSpecification.cs @@ -7,6 +7,6 @@ public sealed class GetMonitoringObserverSpecification: SingleResultSpecificatio { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs b/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs index 266da77b8..c80867fb1 100644 --- a/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/UpdateStatus/Endpoint.cs @@ -27,16 +27,18 @@ public override async Task> ExecuteAsync(Request re await context.PollingStationInformation .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); await context.FormSubmissions .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs b/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs index 9ed717af8..c67a5fb0c 100644 --- a/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/Upsert/Endpoint.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Answer.Module.Mappers; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormAnswerBase; using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; @@ -10,6 +11,7 @@ public class Endpoint( IRepository repository, IReadRepository pollingStationRepository, IReadRepository monitoringObserverRepository, + IReadRepository coalitionRepository, IReadRepository formRepository, IAuthorizationService authorizationService) : Endpoint, NotFound>> { @@ -37,8 +39,13 @@ public override async Task, NotFound>> ExecuteAs return TypedResults.NotFound(); } - var formSpecification = new GetFormSpecification(req.ElectionRoundId, req.FormId); - var form = await formRepository.FirstOrDefaultAsync(formSpecification, ct); + var coalitionFormSpecification = + new GetCoalitionFormSpecification(req.ElectionRoundId, req.ObserverId, req.FormId); + var ngoFormSpecification = + new GetMonitoringNgoFormSpecification(req.ElectionRoundId, req.ObserverId, req.FormId); + + var form = (await coalitionRepository.FirstOrDefaultAsync(coalitionFormSpecification, ct)) ?? + (await formRepository.FirstOrDefaultAsync(ngoFormSpecification, ct)); if (form is null) { return TypedResults.NotFound(); @@ -116,4 +123,4 @@ private void ValidateAnswers(List answers, FormAggregate form) ThrowIfAnyErrors(); } } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/FetchAll/Endpoint.cs b/api/src/Feature.Forms/FetchAll/Endpoint.cs index 434d9c6f0..27c5cf35f 100644 --- a/api/src/Feature.Forms/FetchAll/Endpoint.cs +++ b/api/src/Feature.Forms/FetchAll/Endpoint.cs @@ -1,11 +1,13 @@ using Feature.Forms.Models; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Vote.Monitor.Core.Helpers; using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.FormAggregate; namespace Feature.Forms.FetchAll; -public class Endpoint(VoteMonitorContext context, IMemoryCache cache) : Endpoint, NotFound>> + +public class Endpoint(VoteMonitorContext context) + : Endpoint, NotFound>> { public override void Configure() { @@ -20,17 +22,25 @@ public override void Configure() }); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) { var monitoringNgo = await context.MonitoringObservers .Include(x => x.MonitoringNgo) + .ThenInclude(x => x.Memberships.Where(m => m.ElectionRoundId == req.ElectionRoundId)) + .ThenInclude(cm => cm.Coalition) .Where(x => x.ObserverId == req.ObserverId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Select(x => new { x.MonitoringNgo.ElectionRoundId, x.MonitoringNgoId, - x.MonitoringNgo.FormsVersion + x.MonitoringNgo.FormsVersion, + IsCoalitionLeader = + x.MonitoringNgo.Memberships.Any(m => + m.ElectionRoundId == req.ElectionRoundId && m.Coalition.LeaderId == x.MonitoringNgoId), + IsInACoalition = x.MonitoringNgo.Memberships.Count != 0 }) .FirstOrDefaultAsync(ct); @@ -39,28 +49,35 @@ public override async Task, NotFound>> Execute return TypedResults.NotFound(); } - var cacheKey = $"election-rounds/{req.ElectionRoundId}/monitoring-ngo/{monitoringNgo.MonitoringNgoId}/forms/{monitoringNgo.FormsVersion}"; + List resultForms = new List(); + if (monitoringNgo.IsInACoalition || monitoringNgo.IsCoalitionLeader) + { + resultForms.AddRange(await context.CoalitionFormAccess + .Include(x => x.Form) + .Where(x => x.MonitoringNgoId == monitoringNgo.MonitoringNgoId && x.Coalition.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.Form.FormType != FormType.CitizenReporting) + .Select(f => FormFullModel.FromEntity(f.Form)) + .AsNoTracking() + .ToListAsync(ct)); + } - var cachedResponse = await cache.GetOrCreateAsync(cacheKey, async (e) => + if (!monitoringNgo.IsCoalitionLeader) { - var forms = await context.Forms + resultForms.AddRange(await context.Forms .Where(x => x.Status == FormStatus.Published) .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgoId == monitoringNgo.MonitoringNgoId) - .Where(x=>x.FormType != FormType.CitizenReporting) - .OrderBy(x => x.Code) - .ToListAsync(cancellationToken: ct); - - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); + .Where(x => x.FormType != FormType.CitizenReporting) + .Select(f => FormFullModel.FromEntity(f)) + .AsNoTracking() + .ToListAsync(cancellationToken: ct)); + } - return new NgoFormsResponseModel - { - ElectionRoundId = monitoringNgo.ElectionRoundId, - Version = monitoringNgo.FormsVersion.ToString(), - Forms = forms.Select(FormFullModel.FromEntity).ToList() - }; + return TypedResults.Ok(new NgoFormsResponseModel + { + ElectionRoundId = monitoringNgo.ElectionRoundId, + Version = DeterministicGuid.Create(resultForms.Select(x => x.Id)).ToString(), + Forms = resultForms }); - - return TypedResults.Ok(cachedResponse!); } } diff --git a/api/src/Feature.Forms/FromForm/Endpoint.cs b/api/src/Feature.Forms/FromForm/Endpoint.cs index 5f701965c..eb6848b1e 100644 --- a/api/src/Feature.Forms/FromForm/Endpoint.cs +++ b/api/src/Feature.Forms/FromForm/Endpoint.cs @@ -3,7 +3,6 @@ using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; -using Vote.Monitor.Form.Module.Mappers; namespace Feature.Forms.FromForm; diff --git a/api/src/Feature.Forms/FromForm/Validator.cs b/api/src/Feature.Forms/FromForm/Validator.cs index c3f91c31e..288fd1f82 100644 --- a/api/src/Feature.Forms/FromForm/Validator.cs +++ b/api/src/Feature.Forms/FromForm/Validator.cs @@ -1,5 +1,4 @@ using Vote.Monitor.Core.Validators; -using Vote.Monitor.Form.Module.Validators; namespace Feature.Forms.FromForm; diff --git a/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs b/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs index b82efac0f..21b6cdacd 100644 --- a/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs +++ b/api/src/Feature.Forms/GetFormsVersion/Endpoint.cs @@ -18,11 +18,13 @@ public override void Configure() }); } - public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound>> ExecuteAsync(Request req, + CancellationToken ct) { var monitoringNgo = await context.MonitoringObservers - .Include(x=>x.MonitoringNgo) - .Where(x=>x.ObserverId == req.ObserverId) + .Include(x => x.MonitoringNgo) + .Where(x => x.ObserverId == req.ObserverId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Select(x => new { x.MonitoringNgo.FormsVersion, x.MonitoringNgo.ElectionRoundId }) .FirstOrDefaultAsync(ct); @@ -34,8 +36,7 @@ public override async Task, NotFound>> Exec return TypedResults.Ok(new FormVersionResponseModel { - ElectionRoundId = monitoringNgo.ElectionRoundId, - CacheKey = monitoringNgo.FormsVersion.ToString() + ElectionRoundId = monitoringNgo.ElectionRoundId, CacheKey = monitoringNgo.FormsVersion.ToString() }); } } diff --git a/api/src/Feature.Forms/List/Endpoint.cs b/api/src/Feature.Forms/List/Endpoint.cs index f0ec7d6af..ff48079b4 100644 --- a/api/src/Feature.Forms/List/Endpoint.cs +++ b/api/src/Feature.Forms/List/Endpoint.cs @@ -72,8 +72,8 @@ OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText F."LanguagesTranslationStatus", F."Icon", COALESCE(F."LastModifiedOn", F."CreatedOn") as "LastModifiedOn", - COALESCE(UPDATER."FirstName" || ' ' || UPDATER."LastName", - CREATOR."FirstName" || ' ' || CREATOR."LastName") AS "LastModifiedBy" + COALESCE(UPDATER."DisplayName", + CREATOR."DisplayName") AS "LastModifiedBy" FROM "Forms" F INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" @@ -176,4 +176,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return "LastModifiedOn ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs b/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs index 5732f4bde..fa98c6d97 100644 --- a/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetFormByIdSpecification.cs @@ -5,6 +5,9 @@ public sealed class GetFormByIdSpecification : SingleResultSpecification - x.MonitoringNgo.ElectionRoundId == electionRoundId && x.MonitoringNgo.NgoId == ngoId && x.Id == id); + x.MonitoringNgo.ElectionRoundId == electionRoundId + && x.MonitoringNgo.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.Id == id); } } diff --git a/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs index 5dee605be..02beaa78e 100644 --- a/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetIncidentReportsForFormSpecification.cs @@ -8,6 +8,8 @@ public GetIncidentReportsForFormSpecification(Guid electionRoundId, Guid ngoId, { Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs index 8dbce52bf..5cbd03cc7 100644 --- a/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs +++ b/api/src/Feature.Forms/Specifications/GetSubmissionsForFormSpecification.cs @@ -8,6 +8,8 @@ public GetSubmissionsForFormSpecification(Guid electionRoundId, Guid ngoId, Guid { Query.Where(x => x.ElectionRoundId == electionRoundId && x.MonitoringObserver.MonitoringNgo.NgoId == ngoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs b/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs index 747727452..4e7a2931f 100644 --- a/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs +++ b/api/src/Feature.IncidentReports.Attachments/Get/Endpoint.cs @@ -44,7 +44,7 @@ await repository.FirstOrDefaultAsync( ElectionRoundId = attachment.ElectionRoundId, IncidentReportId = attachment.IncidentReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }); } } \ No newline at end of file diff --git a/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs b/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs index 5926a1eed..c345d7eb1 100644 --- a/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs +++ b/api/src/Feature.IncidentReports.Attachments/List/Endpoint.cs @@ -42,7 +42,7 @@ public override async Task ElectionRoundId = attachment.ElectionRoundId, IncidentReportId = attachment.IncidentReportId, FormId = attachment.FormId, - QuestionId = attachment.QuestionId, + QuestionId = attachment.QuestionId }; }); diff --git a/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs b/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs index e6d5b8f46..3f4b7b4b2 100644 --- a/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs +++ b/api/src/Feature.IncidentReports.Attachments/Specifications/ListIncidentReportAttachmentsSpecification.cs @@ -8,9 +8,10 @@ public ListIncidentReportAttachmentsSpecification(Guid electionRoundId, Guid inc { Query .Where(x => x.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.IncidentReportId == incidentReportId && !x.IsDeleted && x.IsCompleted); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs b/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs index e50b1e643..1143f9821 100644 --- a/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs +++ b/api/src/Feature.IncidentReports.Notes/Specifications/ListNotesSpecification.cs @@ -6,8 +6,8 @@ public sealed class ListNotesSpecification : Specification x.ElectionRoundId == electionRoundId && x.FormId == incidentReportId); + Query.Where(x => x.ElectionRoundId == electionRoundId && x.IncidentReportId == incidentReportId); Query.Select(note => IncidentReportNoteModel.FromEntity(note)); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetById/Endpoint.cs b/api/src/Feature.IncidentReports/GetById/Endpoint.cs index 3dbe5607d..dfbbadfd8 100644 --- a/api/src/Feature.IncidentReports/GetById/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetById/Endpoint.cs @@ -35,6 +35,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId && x.Id == req.IncidentReportId) .Select(incidentReport => new @@ -59,7 +60,7 @@ public override async Task, NotFound>> ExecuteAsync(Request LocationDescription = incidentReport.LocationDescription, PollingStation = incidentReport.PollingStation, - IsCompleted = incidentReport.IsCompleted, + IsCompleted = incidentReport.IsCompleted }) .AsSplitQuery() .FirstOrDefaultAsync(ct); @@ -80,7 +81,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -117,4 +118,4 @@ public override async Task, NotFound>> ExecuteAsync(Request return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs b/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs index 26cb0500f..402ed3884 100644 --- a/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetFilters/Endpoint.cs @@ -51,7 +51,7 @@ SELECT MIN(COALESCE(IR."LastModifiedOn", IR."CreatedOn")) AS "FirstSubmissionTim var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -67,7 +67,7 @@ SELECT MIN(COALESCE(IR."LastModifiedOn", IR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { TimestampsFilterOptions = timestampFilterOptions, - FormFilterOptions = formFilterOptions, + FormFilterOptions = formFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs index f0f81f91f..5bf130428 100644 --- a/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs +++ b/api/src/Feature.IncidentReports/GetSubmissionsAggregated/Endpoint.cs @@ -33,6 +33,7 @@ public override async Task, NotFound>> ExecuteAsync(Inciden var form = await context .Forms .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.MonitoringNgo.NgoId == req.NgoId && x.Id == req.FormId) .AsNoTracking() @@ -56,6 +57,8 @@ private async Task, NotFound>> AggregateIncidentReportsAsyn .Include(x => x.MonitoringObserver).ThenInclude(x => x.Observer).ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.Form.MonitoringNgo.NgoId == req.NgoId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.Form.ElectionRoundId == req.ElectionRoundId && x.FormId == req.FormId) .Where(x => x.PollingStation != null && (string.IsNullOrWhiteSpace(req.Level1Filter) @@ -146,4 +149,4 @@ private async Task, NotFound>> AggregateIncidentReportsAsyn } }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs index a321733d7..96261f8b3 100644 --- a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs @@ -38,7 +38,7 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter); SELECT "MonitoringObserverId", @@ -51,7 +51,7 @@ SELECT COUNT(*) count "NumberOfCompletedForms", "FollowUpStatus" FROM (SELECT MO."Id" AS "MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" AS "ObserverName", + U."DisplayName" AS "ObserverName", U."PhoneNumber", U."Email", MO."Tags", @@ -87,8 +87,7 @@ ELSE NULL INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."FirstName" ILIKE @searchText OR - u."LastName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter)) T WHERE (@needsFollowUp IS NULL OR T."FollowUpStatus" = 'NeedsFollowUp') ORDER BY @@ -123,7 +122,7 @@ OFFSET @offset ROWS tagsFilter = req.TagsFilter ?? [], searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), - needsFollowUp = req.FollowUpStatus?.ToString(), + needsFollowUp = req.FollowUpStatus?.ToString() }; int totalRowCount = 0; diff --git a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs index 94b16f705..b084bf45b 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs @@ -45,8 +45,7 @@ public override async Task>, AND (@monitoringObserverId IS NULL OR MO."Id" = @monitoringObserverId) AND (@searchText IS NULL OR @searchText = '' - OR U."FirstName" ILIKE @searchText - OR U."LastName" ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText ) @@ -168,7 +167,7 @@ INCIDENT_REPORTS AS ( PS."Level5", PS."Number" "PollingStationNumber", IR."MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" AS "ObserverName", + U."DisplayName" AS "ObserverName", U."Email", U."PhoneNumber", MO."Status", @@ -193,8 +192,7 @@ INCIDENT_REPORTS IR AND ( @searchText IS NULL OR @searchText = '' - OR U."FirstName" ILIKE @searchText - OR U."LastName" ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText ) @@ -235,8 +233,8 @@ ORDER BY CASE WHEN @sortExpression = 'Level5 DESC' THEN PS."Level5" END DESC, CASE WHEN @sortExpression = 'PollingStationNumber ASC' THEN PS."Number" END ASC, CASE WHEN @sortExpression = 'PollingStationNumber DESC' THEN PS."Number" END DESC, - CASE WHEN @sortExpression = 'ObserverName ASC' THEN U."FirstName" || ' ' || U."LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN U."FirstName" || ' ' || U."LastName" END DESC + CASE WHEN @sortExpression = 'ObserverName ASC' THEN U."DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN U."DisplayName" END DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY; """; @@ -367,4 +365,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(IncidentReportEntryModel.TimeSubmitted)} DESC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListMy/Endpoint.cs b/api/src/Feature.IncidentReports/ListMy/Endpoint.cs index e65692f3e..15d4fcc1e 100644 --- a/api/src/Feature.IncidentReports/ListMy/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListMy/Endpoint.cs @@ -35,6 +35,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .ThenInclude(x => x.ApplicationUser) .Where(x => x.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId) .Select(incidentReport => new { @@ -55,7 +56,7 @@ public override async Task, NotFound>> ExecuteAsync(Request LocationType = incidentReport.LocationType, LocationDescription = incidentReport.LocationDescription, - PollingStation = incidentReport.PollingStation, + PollingStation = incidentReport.PollingStation }) .AsSplitQuery() .ToListAsync(ct); @@ -75,7 +76,7 @@ public override async Task, NotFound>> ExecuteAsync(Request return attachment with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }; }).ToArray(); @@ -103,4 +104,4 @@ public override async Task, NotFound>> ExecuteAsync(Request return TypedResults.Ok(response); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs b/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs index 07043f0fb..979a7cefa 100644 --- a/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs +++ b/api/src/Feature.IncidentReports/SetCompletion/Endpoint.cs @@ -23,10 +23,11 @@ public override async Task> ExecuteAsync(Request re await context.IncidentReports .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs b/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs index 8d143bca3..f1390cc08 100644 --- a/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs +++ b/api/src/Feature.IncidentReports/Specifications/GetIncidentReportSpecification.cs @@ -6,8 +6,10 @@ public GetIncidentReportSpecification(Guid electionRoundId, Guid observerId, Gui { Query.Where(x => x.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId && x.Id == incidentReportId); } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs index 1d3c4045d..0c80cd805 100644 --- a/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.IncidentReports/Specifications/GetMonitoringObserverSpecification.cs @@ -2,10 +2,12 @@ namespace Feature.IncidentReports.Specifications; -public sealed class GetMonitoringObserverSpecification: SingleResultSpecification +public sealed class GetMonitoringObserverSpecification : SingleResultSpecification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => + x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs b/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs index ea08512e1..8488550a8 100644 --- a/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.IncidentReports/UpdateStatus/Endpoint.cs @@ -24,9 +24,11 @@ public override async Task> ExecuteAsync(Request re await context.IncidentReports .Where(x => x.Form.MonitoringNgo.NgoId == req.NgoId && x.ElectionRoundId == req.ElectionRoundId + && x.Form.MonitoringNgo.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgoId == x.Form.MonitoringNgoId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs b/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs index c175868d5..fe91ece96 100644 --- a/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs +++ b/api/src/Feature.Monitoring/Specifications/GetMonitoringNgoSpecification.cs @@ -14,7 +14,7 @@ public GetMonitoringNgoSpecification(Guid electionRoundId) NgoId = x.NgoId, Name = x.Ngo.Name, NgoStatus = x.Ngo.Status, - MonitoringNgoStatus = x.Status, + MonitoringNgoStatus = x.Status }); } } diff --git a/api/src/Feature.MonitoringObservers/Assign/Request.cs b/api/src/Feature.MonitoringObservers/Assign/Request.cs index 14714b882..a600dfc46 100644 --- a/api/src/Feature.MonitoringObservers/Assign/Request.cs +++ b/api/src/Feature.MonitoringObservers/Assign/Request.cs @@ -3,7 +3,7 @@ public class Request { public Guid ElectionRoundId { get; set; } - public Guid ObserverId { get; set; } public Guid MonitoringNgoId { get; set; } + public Guid ObserverId { get; set; } } diff --git a/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs b/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs index f4520d6db..0d352482f 100644 --- a/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ClearTags/Endpoint.cs @@ -34,6 +34,7 @@ await context .Where(x => req.MonitoringObserverIds.Contains(x.Id)) .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .Where(x => x.MonitoringNgo.NgoId == req.NgoId) + .Where(x=>x.ElectionRoundId == req.ElectionRoundId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.Tags, b => Array.Empty()), cancellationToken: ct); return TypedResults.NoContent(); diff --git a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs index d50578435..74e3a8d91 100644 --- a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs @@ -49,7 +49,7 @@ public override async Task HandleAsync(Request req, CancellationToken ct) var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; IEnumerable monitoringObservers = []; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) diff --git a/api/src/Feature.MonitoringObservers/List/Endpoint.cs b/api/src/Feature.MonitoringObservers/List/Endpoint.cs index 7e2336188..e084ca438 100644 --- a/api/src/Feature.MonitoringObservers/List/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/List/Endpoint.cs @@ -32,7 +32,7 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status); @@ -112,7 +112,7 @@ GROUP BY WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL OR @searchText = '' OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status) GROUP BY @@ -126,8 +126,8 @@ GROUP BY ) T ORDER BY - CASE WHEN @sortExpression = 'ObserverName ASC' THEN "FirstName" || ' ' || "LastName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN "FirstName" || ' ' || "LastName" END DESC, + CASE WHEN @sortExpression = 'ObserverName ASC' THEN "DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN "DisplayName" END DESC, CASE WHEN @sortExpression = 'FirstName ASC' THEN "FirstName" END ASC, CASE WHEN @sortExpression = 'FirstName DESC' THEN "FirstName" END DESC, @@ -160,7 +160,7 @@ OFFSET @offset ROWS tagsFilter = req.Tags ?? [], searchText = $"%{req.SearchText?.Trim() ?? string.Empty}%", status = req.Status?.ToString(), - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; diff --git a/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs b/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs index c21478d17..759b1639d 100644 --- a/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ListTags/Endpoint.cs @@ -26,6 +26,7 @@ public override async Task, NotFound>> ExecuteAsync(Request var tags = await context .MonitoringObservers + .Where(x=>x.ElectionRoundId == request.ElectionRoundId) .Where(x => x.MonitoringNgo.NgoId == request.NgoId && x.MonitoringNgo.ElectionRoundId == request.ElectionRoundId) .Where(x => x.Tags.Any()) diff --git a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs index b137f5680..364527967 100644 --- a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs +++ b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModel.cs @@ -5,7 +5,7 @@ public class MonitoringObserverImportModel public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } public string[] Tags { get; set; } = []; public override int GetHashCode() @@ -21,4 +21,4 @@ public override bool Equals(object obj) MonitoringObserverImportModel other = (MonitoringObserverImportModel)obj; return Email == other.Email; } -} \ No newline at end of file +} diff --git a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs index e422ad63f..c6960d8ae 100644 --- a/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs +++ b/api/src/Feature.MonitoringObservers/Parser/MonitoringObserverImportModelValidator.cs @@ -10,7 +10,5 @@ public MonitoringObserverImportModelValidator() RuleFor(x => x.Email) .NotEmpty() .EmailAddress(); - - RuleFor(x => x.PhoneNumber).MinimumLength(3); } } diff --git a/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs b/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs index 6a9318488..f0b73333d 100644 --- a/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/ResendInvites/Endpoint.cs @@ -12,12 +12,13 @@ namespace Feature.MonitoringObservers.ResendInvites; -public class Endpoint(IAuthorizationService authorizationService, +public class Endpoint( + IAuthorizationService authorizationService, VoteMonitorContext context, IJobService jobService, IEmailTemplateFactory emailFactory, IOptions apiConfig - ) : Endpoint> +) : Endpoint> { private readonly ApiConfiguration _apiConfig = apiConfig.Value; @@ -55,31 +56,33 @@ public override async Task> ExecuteAsync(Request re .FirstAsync(ct); var pendingInviteMonitoringObservers = await context.MonitoringObservers - .Include(x=>x.Observer) - .ThenInclude(x=>x.ApplicationUser) - .Where(x => x.MonitoringNgo.NgoId == req.NgoId) - .Where(x => x.ElectionRoundId == req.ElectionRoundId) - .Where(x => x.Status == MonitoringObserverStatus.Pending) - .Where(x => req.Ids.Count == 0 || req.Ids.Contains(x.Id)) - .Select(x => new - { - InvitationToken = x.Observer.ApplicationUser.InvitationToken, - FullName = x.Observer.ApplicationUser.FirstName + " " + x.Observer.ApplicationUser.LastName, - Email = x.Observer.ApplicationUser.Email - }).ToListAsync(ct); + .Include(x => x.Observer) + .ThenInclude(x => x.ApplicationUser) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.MonitoringNgo.NgoId == req.NgoId && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.ElectionRoundId == req.ElectionRoundId) + .Where(x => x.Status == MonitoringObserverStatus.Pending) + .Where(x => req.Ids.Count == 0 || req.Ids.Contains(x.Id)) + .Select(x => new + { + InvitationToken = x.Observer.ApplicationUser.InvitationToken, + FullName = x.Observer.ApplicationUser.FirstName + " " + x.Observer.ApplicationUser.LastName, + Email = x.Observer.ApplicationUser.Email + }).ToListAsync(ct); foreach (var monitoringObserver in pendingInviteMonitoringObservers) { var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite")); - string acceptInviteUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", monitoringObserver.InvitationToken!); + string acceptInviteUrl = QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", + monitoringObserver.InvitationToken!); var invitationNewUserEmailProps = new InvitationNewUserEmailProps(FullName: monitoringObserver.FullName, CdnUrl: _apiConfig.WebAppUrl, AcceptUrl: acceptInviteUrl, NgoName: ngoName, ElectionRoundDetails: electionRoundName); - + var email = emailFactory.GenerateNewUserInvitationEmail(invitationNewUserEmailProps); jobService.EnqueueSendEmail(monitoringObserver.Email!, email.Subject, email.Body); } diff --git a/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs b/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs index 38119380f..de2e00ad5 100644 --- a/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs +++ b/api/src/Feature.MonitoringObservers/Specifications/GetMonitoringObserverForElectionRound.cs @@ -1,8 +1,11 @@ namespace Feature.MonitoringObservers.Specifications; + public sealed class GetMonitoringObserverForElectionRound : SingleResultSpecification { public GetMonitoringObserverForElectionRound(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ObserverId == observerId); + Query.Where(x => + x.MonitoringNgo.ElectionRoundId == electionRoundId && x.ObserverId == observerId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs index b0fc00257..c6482c327 100644 --- a/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetMonitoringObserverSpecification.cs @@ -2,10 +2,13 @@ using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; namespace Feature.Notes.Specifications; + public sealed class GetMonitoringObserverSpecification : Specification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId); + Query.Where(o => + o.ElectionRoundId == electionRoundId && o.ObserverId == observerId && + o.MonitoringNgo.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs b/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs index f8edf4788..028f87fe1 100644 --- a/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetNoteByIdSpecification.cs @@ -7,6 +7,9 @@ public sealed class GetNoteByIdSpecification : SingleResultSpecification x.ElectionRoundId == electionRoundId && x.MonitoringObserver.ObserverId == observerId && x.Id == id); + .Where(x => x.ElectionRoundId == electionRoundId + && x.MonitoringObserver.ObserverId == observerId + && x.Id == id + && x.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs b/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs index 538c68d57..f6f1eb9b3 100644 --- a/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs +++ b/api/src/Feature.Notes/Specifications/GetNotesSpecification.cs @@ -10,6 +10,8 @@ public GetNotesSpecification(Guid electionRoundId, Guid pollingStationId, Guid o .Where(x => x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId + && x.Form.ElectionRoundId == electionRoundId && x.FormId == formId); } } diff --git a/api/src/Feature.Notifications/Get/Endpoint.cs b/api/src/Feature.Notifications/Get/Endpoint.cs index 80aa147ed..34c2d7e75 100644 --- a/api/src/Feature.Notifications/Get/Endpoint.cs +++ b/api/src/Feature.Notifications/Get/Endpoint.cs @@ -33,7 +33,7 @@ public override async Task, NotFound>> Exe N."Title", N."Body", N."CreatedOn" "SentAt", - U."FirstName" || ' ' || U."LastName" "Sender", + U."DisplayName" "Sender", ( SELECT JSONB_AGG( @@ -41,7 +41,7 @@ public override async Task, NotFound>> Exe 'Id', "MonitoringObserverId", 'Name', - MOU."FirstName" || ' ' || MOU."LastName", + MOU."DisplayName", 'HasReadNotification', MON."IsRead" ) @@ -68,7 +68,7 @@ public override async Task, NotFound>> Exe { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, - id = req.Id, + id = req.Id }; NotificationDetailedModel notification; @@ -80,4 +80,4 @@ public override async Task, NotFound>> Exe return notification == null ? TypedResults.NotFound() : TypedResults.Ok(notification); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListReceived/Endpoint.cs b/api/src/Feature.Notifications/ListReceived/Endpoint.cs index 042a2c3b8..54933cbfe 100644 --- a/api/src/Feature.Notifications/ListReceived/Endpoint.cs +++ b/api/src/Feature.Notifications/ListReceived/Endpoint.cs @@ -34,7 +34,7 @@ FETCH FIRST N."Id", N."Title", N."Body", - U."FirstName" || ' ' || U."LastName" "Sender", + U."DisplayName" "Sender", N."CreatedOn" "SentAt", MON."IsRead" FROM @@ -53,7 +53,7 @@ ORDER BY var queryArgs = new { electionRoundId = req.ElectionRoundId, - observerId = req.ObserverId, + observerId = req.ObserverId }; string? ngoName; diff --git a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs index 8b66f432d..42c2cb344 100644 --- a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs +++ b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs @@ -126,7 +126,7 @@ SELECT COUNT(DISTINCT OA."ObserverId") COUNT LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" WHERE (@searchText IS NULL OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText + OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText OR mo."Id"::text ILIKE @searchText) @@ -253,7 +253,7 @@ UNION ALL SELECT * FROM "ObserverPSI"), "FilteredObservers" AS (SELECT DISTINCT OA."MonitoringObserverId", - U."FirstName" || ' ' || U."LastName" "ObserverName", + U."DisplayName" "ObserverName", U."PhoneNumber", U."Email", MO."Tags", @@ -264,7 +264,7 @@ UNION ALL SELECT * LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" WHERE (@searchText IS NULL OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText + OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText OR mo."Id"::text ILIKE @searchText) @@ -337,7 +337,7 @@ ORDER BY CASE offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), pageSize = req.PageSize, - sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), + sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; int totalRowCount; @@ -393,4 +393,4 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending return $"{nameof(TargetedMonitoringObserverModel.ObserverName)} ASC"; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListSent/Endpoint.cs b/api/src/Feature.Notifications/ListSent/Endpoint.cs index 4f66e8bf4..68286bfb0 100644 --- a/api/src/Feature.Notifications/ListSent/Endpoint.cs +++ b/api/src/Feature.Notifications/ListSent/Endpoint.cs @@ -35,7 +35,7 @@ public override async Task> ExecuteAsync(Reques N."Title", N."Body", N."CreatedOn" AS "SentAt", - U."FirstName" || ' ' || U."LastName" AS "Sender", + U."DisplayName" AS "Sender", COUNT(MON."NotificationId") AS "NumberOfTargetedObservers", SUM(CASE WHEN MON."IsRead" = TRUE THEN 1 ELSE 0 END) AS "NumberOfReadNotifications" FROM @@ -59,7 +59,7 @@ OFFSET @offset ROWS electionRoundId = req.ElectionRoundId, ngoId = req.NgoId, offset = PaginationHelper.CalculateSkip(req.PageSize, req.PageNumber), - pageSize = req.PageSize, + pageSize = req.PageSize }; int totalRowCount; @@ -74,4 +74,4 @@ OFFSET @offset ROWS return new PagedResponse(entries, totalRowCount, req.PageNumber, req.PageSize); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Send/Endpoint.cs b/api/src/Feature.Notifications/Send/Endpoint.cs index 29cc57e25..726a6aa44 100644 --- a/api/src/Feature.Notifications/Send/Endpoint.cs +++ b/api/src/Feature.Notifications/Send/Endpoint.cs @@ -134,7 +134,7 @@ UNION ALL SELECT * LEFT JOIN "NotificationTokens" NT ON NT."ObserverId" = OA."ObserverId" WHERE (@searchText IS NULL OR @searchText = '' - OR (U."FirstName" || ' ' || U."LastName") ILIKE @searchText + OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText OR mo."Id"::text ILIKE @searchText) @@ -218,7 +218,7 @@ OR cardinality(@tagsFilter) = 0 hasQuickReports = req.HasQuickReports, quickReportFollowUpStatus = req.QuickReportFollowUpStatus?.ToString(), - quickReportIncidentCategory = req.QuickReportIncidentCategory?.ToString(), + quickReportIncidentCategory = req.QuickReportIncidentCategory?.ToString() }; IEnumerable result = []; @@ -254,7 +254,7 @@ OR cardinality(@tagsFilter) = 0 return TypedResults.Ok(new Response { - Status = "Success", + Status = "Success" }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs index f244d43ca..de626ba5a 100644 --- a/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.Notifications/Specifications/GetMonitoringObserverSpecification.cs @@ -8,8 +8,9 @@ public GetMonitoringObserverSpecification(Guid electionRoundId, Guid ngoId, List { Query .Where(x => - x.ElectionRoundId == electionRoundId - && x.MonitoringNgo.NgoId == ngoId - && monitoringObserverIds.Contains(x.Id)); + x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.MonitoringNgo.ElectionRoundId == electionRoundId + && monitoringObserverIds.Contains(x.Id)); } } diff --git a/api/src/Feature.ObserverGuide/GetById/Endpoint.cs b/api/src/Feature.ObserverGuide/GetById/Endpoint.cs index 07e10dc31..f04c93d2b 100644 --- a/api/src/Feature.ObserverGuide/GetById/Endpoint.cs +++ b/api/src/Feature.ObserverGuide/GetById/Endpoint.cs @@ -94,7 +94,7 @@ public override async Task, NotFound>> ExecuteAsy return TypedResults.Ok(citizenGuideModel with { PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, - UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0 }); } diff --git a/api/src/Feature.ObserverGuide/List/Endpoint.cs b/api/src/Feature.ObserverGuide/List/Endpoint.cs index cdf6dc9d8..c655b5e15 100644 --- a/api/src/Feature.ObserverGuide/List/Endpoint.cs +++ b/api/src/Feature.ObserverGuide/List/Endpoint.cs @@ -44,8 +44,8 @@ await authorizationService.AuthorizeAsync(User, var guides = await context .ObserversGuides .Where(x => x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted) - .Where(x => isObserver || x.MonitoringNgo.NgoId == ngoId) - .Where(x => isNgoAdmin || x.MonitoringNgo.MonitoringObservers.Any(mo => mo.ObserverId == observerId)) + .Where(x => isObserver || (x.MonitoringNgo.NgoId == ngoId && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId)) + .Where(x => isNgoAdmin || x.MonitoringNgo.MonitoringObservers.Any(mo => mo.ObserverId == observerId) && x.MonitoringNgo.ElectionRoundId == req.ElectionRoundId) .OrderByDescending(x => x.CreatedOn) .Join(context.NgoAdmins, guide => guide.LastModifiedBy == Guid.Empty ? guide.CreatedBy : guide.LastModifiedBy, user => user.Id, (guide, ngoAdmin) => new { @@ -119,4 +119,4 @@ await authorizationService.AuthorizeAsync(User, return TypedResults.Ok(new Response { Guides = mappedGuides.ToList() }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs b/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs index 4e367d1fc..f0ae8676c 100644 --- a/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs +++ b/api/src/Feature.PollingStation.Information/SetCompletion/Endpoint.cs @@ -30,10 +30,12 @@ public override async Task> ExecuteAsync(Request re await context.PollingStationInformation .Where(x => x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.PollingStationId == req.PollingStationId) .ExecuteUpdateAsync(x => x.SetProperty(p => p.IsCompleted, req.IsCompleted), cancellationToken: ct); return TypedResults.NoContent(); } -} \ No newline at end of file +} diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs index 6b42d3ea7..c6308e2c4 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetMonitoringObserverSpecification.cs @@ -3,10 +3,12 @@ namespace Feature.PollingStation.Information.Specifications; -public sealed class GetMonitoringObserverSpecification: SingleResultSpecification +public sealed class GetMonitoringObserverSpecification : SingleResultSpecification { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(x => x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId); + Query.Where(x => + x.ObserverId == observerId && x.MonitoringNgo.ElectionRoundId == electionRoundId && + x.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs index 5e139c1ca..d41ab16d9 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationForObserverSpecification.cs @@ -3,16 +3,20 @@ namespace Feature.PollingStation.Information.Specifications; -public sealed class GetPollingStationInformationForObserverSpecification : Specification +public sealed class + GetPollingStationInformationForObserverSpecification : Specification { - public GetPollingStationInformationForObserverSpecification(Guid electionRoundId, Guid observerId, List? pollingStationIds) + public GetPollingStationInformationForObserverSpecification(Guid electionRoundId, Guid observerId, + List? pollingStationIds) { Query.Where(x => - x.ElectionRoundId == electionRoundId && - x.MonitoringObserver.ObserverId == observerId) - .Where(x => pollingStationIds.Contains(x.PollingStationId), pollingStationIds != null && pollingStationIds.Any()); + x.ElectionRoundId == electionRoundId && + x.MonitoringObserver.ObserverId == observerId && + x.MonitoringObserver.ElectionRoundId == electionRoundId) + .Where(x => pollingStationIds.Contains(x.PollingStationId), + pollingStationIds != null && pollingStationIds.Any()); Query.Select(x => PollingStationInformationModel.FromEntity(x)); - } } diff --git a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs index 92cdc2782..267696b1d 100644 --- a/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs +++ b/api/src/Feature.PollingStation.Information/Specifications/GetPollingStationInformationSpecification.cs @@ -8,7 +8,9 @@ public sealed class GetPollingStationInformationSpecification : SingleResultSpec public GetPollingStationInformationSpecification(Guid electionRoundId, Guid pollingStationId, Guid observerId) { Query.Where(x => - x.ElectionRoundId == electionRoundId && x.PollingStationId == pollingStationId && - x.MonitoringObserver.ObserverId == observerId); + x.ElectionRoundId == electionRoundId + && x.PollingStationId == pollingStationId + && x.MonitoringObserver.ObserverId == observerId + && x.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs b/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs index b5a2e71a5..7913e630f 100644 --- a/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs +++ b/api/src/Feature.PollingStation.Visit/Delete/Endpoint.cs @@ -127,7 +127,7 @@ DELETE FROM "QuickReports" { electionRoundId = req.ElectionRoundId, observerId = req.ObserverId, - pollingStationId = req.PollingStationId, + pollingStationId = req.PollingStationId }; await connection.ExecuteAsync(deletePSISubmissionsSql, queryParams, transaction); diff --git a/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs b/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs index 73212f216..2c4651867 100644 --- a/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs +++ b/api/src/Feature.QuickReports/AddAttachment/Endpoint.cs @@ -73,7 +73,7 @@ public override async Task, NotFound, Bad FileName = attachment.FileName, PresignedUrl = result!.Url, MimeType = attachment.MimeType, - UrlValidityInSeconds = result.UrlValidityInSeconds, + UrlValidityInSeconds = result.UrlValidityInSeconds }); } } diff --git a/api/src/Feature.QuickReports/Delete/Endpoint.cs b/api/src/Feature.QuickReports/Delete/Endpoint.cs index d65a866ba..c967a7cda 100644 --- a/api/src/Feature.QuickReports/Delete/Endpoint.cs +++ b/api/src/Feature.QuickReports/Delete/Endpoint.cs @@ -55,6 +55,8 @@ private async Task DeleteAttachmentsAsync(Request req, CancellationToken ct) await context.QuickReportAttachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.QuickReportId == req.Id) .ExecuteUpdateAsync(setters => setters .SetProperty(qra => qra.IsDeleted, true) diff --git a/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs b/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs index 64fc3c9c5..2730121f9 100644 --- a/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs +++ b/api/src/Feature.QuickReports/DeleteAttachment/Endpoint.cs @@ -32,6 +32,8 @@ await context .QuickReportAttachments .Where(x => x.ElectionRoundId == req.ElectionRoundId && x.MonitoringObserver.ObserverId == req.ObserverId + && x.MonitoringObserver.ElectionRoundId == req.ElectionRoundId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.QuickReportId == req.QuickReportId && x.Id == req.Id) .ExecuteUpdateAsync(setters => setters diff --git a/api/src/Feature.QuickReports/GetFilters/Endpoint.cs b/api/src/Feature.QuickReports/GetFilters/Endpoint.cs index a0510700a..cc31ec06f 100644 --- a/api/src/Feature.QuickReports/GetFilters/Endpoint.cs +++ b/api/src/Feature.QuickReports/GetFilters/Endpoint.cs @@ -42,7 +42,7 @@ SELECT MIN(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "FirstSubmissionTim var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId, + ngoId = req.NgoId }; SubmissionsTimestampsFilterOptions timestampFilterOptions; @@ -55,7 +55,7 @@ SELECT MIN(COALESCE(QR."LastModifiedOn", QR."CreatedOn")) AS "FirstSubmissionTim return TypedResults.Ok(new Response { - TimestampsFilterOptions = timestampFilterOptions, + TimestampsFilterOptions = timestampFilterOptions }); } } \ No newline at end of file diff --git a/api/src/Feature.QuickReports/List/Endpoint.cs b/api/src/Feature.QuickReports/List/Endpoint.cs index 55324d470..207a1f2c5 100644 --- a/api/src/Feature.QuickReports/List/Endpoint.cs +++ b/api/src/Feature.QuickReports/List/Endpoint.cs @@ -69,7 +69,7 @@ @level5 IS NULL QR."IncidentCategory", QR."FollowUpStatus", COUNT(QRA."Id") FILTER(WHERE QRA."IsDeleted" = FALSE AND QRA."IsCompleted" = TRUE) AS "NumberOfAttachments", - O."FirstName" || ' ' ||O."LastName" "ObserverName", + O."DisplayName" "ObserverName", O."Email", O."PhoneNumber", QR."PollingStationDetails", diff --git a/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs b/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs index db7515df2..dafcb1f19 100644 --- a/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/GetMonitoringObserverSpecification.cs @@ -6,6 +6,6 @@ public sealed class GetMonitoringObserverSpecification : SingleResultSpecificati { public GetMonitoringObserverSpecification(Guid electionRoundId, Guid observerId) { - Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId); + Query.Where(o => o.ElectionRoundId == electionRoundId && o.ObserverId == observerId && o.MonitoringNgo.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs b/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs index 21be6ed92..5662483ab 100644 --- a/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/GetQuickReportAttachmentByIdSpecification.cs @@ -10,6 +10,7 @@ public GetQuickReportAttachmentByIdSpecification(Guid electionRoundId, Guid obse Query.Where(qr => qr.Id == id && qr.QuickReportId == quickReportId && qr.ElectionRoundId == electionRoundId - && qr.MonitoringObserver.ObserverId == observerId); + && qr.MonitoringObserver.ObserverId == observerId + && qr.MonitoringObserver.ElectionRoundId == electionRoundId); } } diff --git a/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs b/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs index 0758b859e..8d1029efa 100644 --- a/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs +++ b/api/src/Feature.QuickReports/Specifications/ListObserverQuickReportsSpecification.cs @@ -6,7 +6,7 @@ public sealed class ListObserverQuickReportsSpecification : Specification qr.ElectionRoundId == electionRoundId && qr.MonitoringObserver.ObserverId == observerId) + Query.Where(qr => qr.ElectionRoundId == electionRoundId && qr.MonitoringObserver.ObserverId == observerId && qr.MonitoringObserver.ElectionRoundId == electionRoundId) .Include(x => x.PollingStation) .Include(x => x.MonitoringObserver) .ThenInclude(x => x.Observer) diff --git a/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs b/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs index b74b72d3d..0f8cae6bf 100644 --- a/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs +++ b/api/src/Feature.QuickReports/UpdateStatus/Endpoint.cs @@ -23,6 +23,7 @@ public override async Task ExecuteAsync(Request req, CancellationToke { await context.QuickReports .Where(x => x.MonitoringObserver.MonitoringNgo.NgoId == req.NgoId + && x.MonitoringObserver.MonitoringNgo.ElectionRoundId == req.ElectionRoundId && x.ElectionRoundId == req.ElectionRoundId && x.Id == req.Id) .ExecuteUpdateAsync(x => x.SetProperty(p => p.FollowUpStatus, req.FollowUpStatus), cancellationToken: ct); diff --git a/api/src/Feature.QuickReports/Upsert/Endpoint.cs b/api/src/Feature.QuickReports/Upsert/Endpoint.cs index 5572f7f24..6eb45605e 100644 --- a/api/src/Feature.QuickReports/Upsert/Endpoint.cs +++ b/api/src/Feature.QuickReports/Upsert/Endpoint.cs @@ -100,7 +100,7 @@ private async Task GetPresignedAttachments(Guid el PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, MimeType = attachment.MimeType, UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, - Id = attachment.Id, + Id = attachment.Id }; }); var attachments = await Task.WhenAll(tasks); diff --git a/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs b/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs index ee85fc3e3..829cda23a 100644 --- a/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs +++ b/api/src/Feature.Statistics/GetNgoAdminStatistics/Endpoint.cs @@ -547,7 +547,7 @@ SELECT DATE_TRUNC('hour', TIMEZONE('utc', COALESCE(IR."LastModifiedOn", IR."Crea }).ToArray(), QuickReportsHistogram = quickReportsHistogram.ToArray(), IncidentReportsHistogram = incidentReportsHistogram.ToArray(), - CitizenReportsHistogram = citizenReportsHistogram.ToArray(), + CitizenReportsHistogram = citizenReportsHistogram.ToArray() }; } } \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs index 27ed55945..d047068d2 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Create/Endpoint.cs @@ -58,7 +58,7 @@ public override async Task, Conflict ExecuteAsync(CancellationToken ct) CountryName = x.Country.Name, CountryFullName = x.Country.FullName, StartDate = x.StartDate, - Title = x.Title, + Title = x.Title }) .ToListAsync(ct); diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs index bb07762fc..fa205e834 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/Endpoint.cs @@ -1,8 +1,9 @@ -using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Domain; namespace Vote.Monitor.Api.Feature.ElectionRound.Monitoring; -public class Endpoint(IReadRepository repository) +public class Endpoint(VoteMonitorContext context) : Endpoint> { public override void Configure() @@ -21,7 +22,36 @@ public override void Configure() public override async Task> ExecuteAsync(Request req, CancellationToken ct) { - var electionRounds = await repository.ListAsync(new GetNgoElectionSpecification(req.NgoId), ct); + var electionRounds = await context.MonitoringNgos + .Include(x => x.ElectionRound) + .ThenInclude(x => x.MonitoringNgoForCitizenReporting) + .Where(x => x.NgoId == req.NgoId) + .Select(x => new NgoElectionRoundView + { + MonitoringNgoId = x.Id, + ElectionRoundId = x.ElectionRoundId, + Title = x.ElectionRound.Title, + EnglishTitle = x.ElectionRound.EnglishTitle, + StartDate = x.ElectionRound.StartDate, + Country = x.ElectionRound.Country.FullName, + CountryId = x.ElectionRound.CountryId, + IsMonitoringNgoForCitizenReporting = x.ElectionRound.CitizenReportingEnabled && + x.ElectionRound.MonitoringNgoForCitizenReporting.NgoId == + req.NgoId, + IsCoalitionLeader = + context.Coalitions.Any(c => c.Leader.NgoId == req.NgoId && c.ElectionRoundId == x.ElectionRoundId), + Status = x.ElectionRound.Status, + CoalitionName = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Name) + .FirstOrDefault(), + CoalitionId = context.Coalitions + .Where(c => + c.Memberships.Any(m => m.MonitoringNgoId == x.Id) && c.ElectionRoundId == x.ElectionRoundId) + .Select(c => c.Id) + .FirstOrDefault() + }).ToListAsync(ct); return TypedResults.Ok(new Result { ElectionRounds = electionRounds }); } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs index 4a584f033..5d2adb7ba 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Monitoring/NgoElectionRoundView.cs @@ -10,5 +10,9 @@ public class NgoElectionRoundView public string Country { get; set; } public Guid CountryId { get; set; } public bool IsMonitoringNgoForCitizenReporting { get; set; } + public bool IsCoalitionLeader { get; set; } public ElectionRoundStatus Status { get; set; } + + public Guid? CoalitionId { get; set; } + public string? CoalitionName { get; set; } } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs deleted file mode 100644 index 7f7e0a4ae..000000000 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetNgoElectionSpecification.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Vote.Monitor.Api.Feature.ElectionRound.Monitoring; -using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; - -namespace Vote.Monitor.Api.Feature.ElectionRound.Specifications; - -public sealed class GetNgoElectionSpecification : Specification -{ - public GetNgoElectionSpecification(Guid ngoId) - { - Query - .Include(x => x.ElectionRound) - .Where(x => x.NgoId == ngoId); - - Query.Select(x => new NgoElectionRoundView - { - MonitoringNgoId = x.Id, - ElectionRoundId = x.ElectionRoundId, - Title = x.ElectionRound.Title, - EnglishTitle = x.ElectionRound.EnglishTitle, - StartDate = x.ElectionRound.StartDate, - Country = x.ElectionRound.Country.FullName, - CountryId = x.ElectionRound.CountryId, - IsMonitoringNgoForCitizenReporting = x.ElectionRound.CitizenReportingEnabled && - x.ElectionRound.MonitoringNgoForCitizenReporting.NgoId == ngoId, - Status = x.ElectionRound.Status, - }); - } -} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs index c7b2bcc64..6a27bb9a1 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/GetObserverElectionSpecification.cs @@ -24,7 +24,7 @@ public GetObserverElectionSpecification(Guid observerId) CountryIso3 = x.Country.Iso3, CountryName = x.Country.Name, CountryFullName = x.Country.FullName, - CountryNumericCode = x.Country.NumericCode, + CountryNumericCode = x.Country.NumericCode }); } } diff --git a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs index 3410b232f..69ba5d4f8 100644 --- a/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.ElectionRound/Specifications/ListElectionRoundsSpecification.cs @@ -27,7 +27,7 @@ public ListElectionRoundsSpecification(List.Request request) CountryIso3 = x.Country.Iso3, CountryName = x.Country.Name, CountryFullName = x.Country.FullName, - CountryNumericCode = x.Country.NumericCode, + CountryNumericCode = x.Country.NumericCode }); } } diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs index 099bcec6d..c738f29c6 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Request.cs @@ -6,5 +6,5 @@ public class Request public string LastName { get; set; } public string Email { get; set; } public string Password { get; set; } - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } } diff --git a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs index cd1bc9e48..19c474f0e 100644 --- a/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs +++ b/api/src/Vote.Monitor.Api.Feature.Observer/Create/Validator.cs @@ -19,8 +19,6 @@ public Validator() .NotEmpty(); RuleFor(x => x.PhoneNumber) - .NotEmpty() - .MinimumLength(3) - .MaximumLength(32); + .MaximumLength(32); } } diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs index 160aee0be..dc0347ee0 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Create/Endpoint.cs @@ -19,7 +19,14 @@ public override void Configure() public override async Task, Conflict, NotFound>> ExecuteAsync(Request req, CancellationToken ct) { - var specification = new GetPollingStationSpecification(req.ElectionRoundId, req.Address, req.Tags); + var specification = new GetPollingStationSpecification(req.ElectionRoundId, + req.Level1, + req.Level2, + req.Level3, + req.Level4, + req.Level5, + req.Number, + req.Address); var hasIdenticalPollingStation = await repository.AnyAsync(specification, ct); if (hasIdenticalPollingStation) diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs index e989e1b2e..0b5f3263b 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/FetchLevels/Endpoint.cs @@ -41,7 +41,7 @@ public override async Task, NotFound>> ExecuteAsync(Request x.Level2, x.Level3, x.Level4, - x.Level5, + x.Level5 }) .Distinct() .ToListAsync(cancellationToken: ct); diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs index efca31b6b..e5b46a02c 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Specifications/GetPollingStationSpecification.cs @@ -2,11 +2,24 @@ public sealed class GetPollingStationSpecification : Specification { - public GetPollingStationSpecification(Guid electionRoundId, string address, Dictionary tags) + public GetPollingStationSpecification(Guid electionRoundId, + string level1, + string level2, + string level3, + string level4, + string level5, + string number, + string address) { Query .Where(x => x.ElectionRoundId == electionRoundId) - .Search(x => x.Address, "%" + address + "%", !string.IsNullOrEmpty(address)) - .Where(x => EF.Functions.JsonContains(x.Tags, tags), tags.Any()); + .Where(x => x.Level1 == level1) + .Where(x => x.Level2 == level2) + .Where(x => x.Level3 == level3) + .Where(x => x.Level4 == level4) + .Where(x => x.Level5 == level5) + .Where(x => x.Address == address) + .Where(x => x.Number == number); + } } diff --git a/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs b/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs index 127e1f398..7a1131abc 100644 --- a/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs +++ b/api/src/Vote.Monitor.Api.Feature.PollingStation/Update/Endpoint.cs @@ -30,7 +30,14 @@ public override async Task, Conflict return TypedResults.NotFound(new ProblemDetails(ValidationFailures)); } - var specification = new GetPollingStationSpecification(req.ElectionRoundId, req.Address, req.Tags); + var specification = new GetPollingStationSpecification(req.ElectionRoundId, + req.Level1, + req.Level2, + req.Level3, + req.Level4, + req.Level5, + req.Number, + req.Address); var hasIdenticalPollingStation = await repository.AnyAsync(specification, ct); if (hasIdenticalPollingStation) { diff --git a/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs b/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs index 908c9c79f..3932b7927 100644 --- a/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs +++ b/api/src/Vote.Monitor.Api/Extensions/CustomSentryUserFactory.cs @@ -70,7 +70,7 @@ public CustomSentryUserFactory(IHttpContextAccessor httpContextAccessor) : new SentryUser { Id = identifier, - Other = extraData, + Other = extraData }; } diff --git a/api/src/Vote.Monitor.Api/Program.cs b/api/src/Vote.Monitor.Api/Program.cs index 3cb5aa88a..74df159be 100644 --- a/api/src/Vote.Monitor.Api/Program.cs +++ b/api/src/Vote.Monitor.Api/Program.cs @@ -44,6 +44,7 @@ using Feature.IncidentReports.Notes; using Feature.Locations; using Feature.Monitoring; +using Feature.NgoCoalitions; using Feature.Statistics; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; using Microsoft.AspNetCore.Http.Features; @@ -166,6 +167,7 @@ builder.Services.AddIncidentReportsNotesFeature(); builder.Services.AddIncidentReportAttachmentsFeature(); builder.Services.AddCitizenNotificationsFeature(); +builder.Services.AddCoalitionsFeature(); builder.Services.AddAuthorization(); diff --git a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj index a02d5d1e7..b9df6707e 100644 --- a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj +++ b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj @@ -60,6 +60,7 @@ + @@ -90,9 +91,6 @@ Always - - Always - .dockerignore diff --git a/api/src/Vote.Monitor.Api/appsettings.Testing.json b/api/src/Vote.Monitor.Api/appsettings.Testing.json deleted file mode 100644 index 1fec789fc..000000000 --- a/api/src/Vote.Monitor.Api/appsettings.Testing.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Domain": { - "DbConnectionConfig": { - "Server": "localhost", - "Port": "5432", - "Database": "vote-monitor", - "UserId": "postgres", - "Password": "docker" - } - }, - "Core": { - "EnableHangfire": false, - "HangfireConnectionConfig": { - "Server": "localhost", - "Port": "5432", - "Database": "vote-monitor-hangfire", - "UserId": "postgres", - "Password": "docker" - } - }, - "Sentry": { - "Enabled": false, - "Dsn": "", - "TracesSampleRate": 0.2 - }, - "Seeders": { - "PlatformAdminSeeder": { - "FirstName": "John", - "LastName": "Doe", - "Email": "admin@example.com", - "PhoneNumber": "1234567890", - "Password": "toTallyNotTestPassw0rd" - } - } -} diff --git a/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs b/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs index 80694914c..fb86370fb 100644 --- a/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs +++ b/api/src/Vote.Monitor.Core/Extensions/ConfigurationExtensions.cs @@ -14,7 +14,7 @@ public static string GetNpgsqlConnectionString(this IConfiguration config, strin Database = config[$"{section}:Database"]!, Username = config[$"{section}:UserId"], Password = config[$"{section}:Password"], - IncludeErrorDetail = true, + IncludeErrorDetail = true }; return connectionStringBuilder.ToString(); diff --git a/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs b/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs index ed0cee007..a778142e7 100644 --- a/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs +++ b/api/src/Vote.Monitor.Core/Helpers/DeterministicGuid.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using System.Text.Json; namespace Vote.Monitor.Core.Helpers; @@ -15,4 +16,17 @@ public static Guid Create(string text) return guid; } + + public static Guid Create(IEnumerable guids) + { + var guidsArray = guids.Distinct().Order().ToArray(); + + using var hasher = SHA256.Create(); + + var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(guidsArray))); + + var guid = new Guid(hash.Take(16).ToArray()); + + return guid; + } } diff --git a/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs b/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs index c084dfcc3..853890cde 100644 --- a/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs +++ b/api/src/Vote.Monitor.Core/Services/Csv/CsvReader.cs @@ -9,7 +9,7 @@ public IEnumerable Read(Stream stream) where TMap : ClassMap { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { - PrepareHeaderForMatch = args => args.Header.ToLower(), + PrepareHeaderForMatch = args => args.Header.ToLower() }; using var reader = new StreamReader(stream); @@ -24,7 +24,7 @@ public IEnumerable Read(Stream stream) { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { - PrepareHeaderForMatch = args => args.Header.ToLower(), + PrepareHeaderForMatch = args => args.Header.ToLower() }; using var reader = new StreamReader(stream); diff --git a/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs b/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs index fada97298..31a508af9 100644 --- a/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs +++ b/api/src/Vote.Monitor.Core/Services/EmailTemplating/Helpers/EmailTemplateLoader.cs @@ -27,7 +27,7 @@ internal static class EmailTemplateLoader { EmailTemplateType.AttachmentFragment, "Fragments/attachment-fragment.html" }, { EmailTemplateType.AnswerNotesFragment, "Fragments/answer-notes-fragment.html" }, - { EmailTemplateType.NoteFragment, "Fragments/note-fragment.html" }, + { EmailTemplateType.NoteFragment, "Fragments/note-fragment.html" } }; public static string GetTemplate(EmailTemplateType templateType) diff --git a/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs b/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs index 5e92caf11..aa26095e8 100644 --- a/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs +++ b/api/src/Vote.Monitor.Core/Services/FileStorage/S3/S3FileStorageService.cs @@ -53,7 +53,7 @@ public async Task GetPresignedUrlAsync(string uploadPath, { BucketName = _options.BucketName, Key = GetFileKey(uploadPath, fileName), - Expires = DateTime.UtcNow.AddSeconds(_options.PresignedUrlValidityInSeconds), + Expires = DateTime.UtcNow.AddSeconds(_options.PresignedUrlValidityInSeconds) }; try @@ -80,7 +80,7 @@ public async Task CreateMultipartUploadAsync(string uploa { BucketName = _options.BucketName, Key = fileKey, - ContentType = contentType, + ContentType = contentType }; var response = await client.InitiateMultipartUploadAsync(request, ct); @@ -95,7 +95,7 @@ public async Task CreateMultipartUploadAsync(string uploa PartNumber = partNumber, Key = fileKey, Verb = HttpVerb.PUT, - Expires = DateTime.UtcNow.AddHours(24), + Expires = DateTime.UtcNow.AddHours(24) }); presignedUrls.Add(partNumber, partPresignedUrl); diff --git a/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs b/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs index ee34b19bb..472a72af9 100644 --- a/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs +++ b/api/src/Vote.Monitor.Core/Services/Serialization/SerializerService.cs @@ -8,7 +8,7 @@ public class SerializerService(ILogger logger) : ISerializerS private readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true, - ReferenceHandler = ReferenceHandler.IgnoreCycles, + ReferenceHandler = ReferenceHandler.IgnoreCycles }; public string Serialize(T obj) diff --git a/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj b/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj index 301361ef7..ab286901a 100644 --- a/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj +++ b/api/src/Vote.Monitor.Core/Vote.Monitor.Core.csproj @@ -33,8 +33,6 @@ - - diff --git a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs index ebce442ca..b77f5fb62 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ApplicationUserAggregate/ApplicationUser.cs @@ -8,20 +8,21 @@ public class ApplicationUser : IdentityUser, IAggregateRoot #pragma warning disable CS8618 // Required by Entity Framework protected ApplicationUser() { - } #pragma warning restore CS8618 public UserRole Role { get; private set; } public string FirstName { get; private set; } public string LastName { get; private set; } + public string DisplayName { get; private set; } public string? RefreshToken { get; private set; } public DateTime RefreshTokenExpiryTime { get; private set; } public UserStatus Status { get; private set; } public UserPreferences Preferences { get; private set; } public string? InvitationToken { get; private set; } = null; - private ApplicationUser(UserRole role, string firstName, string lastName, string email, string phoneNumber, string password) + private ApplicationUser(UserRole role, string firstName, string lastName, string email, string? phoneNumber, + string password) { Role = role; FirstName = firstName.Trim(); @@ -30,7 +31,7 @@ private ApplicationUser(UserRole role, string firstName, string lastName, string UserName = email.Trim(); NormalizedEmail = email.Trim().ToUpperInvariant(); NormalizedUserName = email.Trim().ToUpperInvariant(); - PhoneNumber = phoneNumber.Trim(); + PhoneNumber = phoneNumber?.Trim(); Status = UserStatus.Active; Preferences = UserPreferences.Defaults; @@ -46,18 +47,21 @@ private ApplicationUser(UserRole role, string firstName, string lastName, string } } - public static ApplicationUser Invite(string firstName, string lastName, string email, string phoneNumber) => + public static ApplicationUser Invite(string firstName, string lastName, string email, string? phoneNumber) => new(UserRole.Observer, firstName, lastName, email, phoneNumber, string.Empty); - public static ApplicationUser CreatePlatformAdmin(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.PlatformAdmin, firstName, lastName, email, phoneNumber, password); + public static ApplicationUser CreatePlatformAdmin(string firstName, string lastName, string email, string password) => + new(UserRole.PlatformAdmin, firstName, lastName, email, null, password); + + public static ApplicationUser CreateNgoAdmin(string firstName, string lastName, string email, string? phoneNumber, + string password) => + new(UserRole.NgoAdmin, firstName, lastName, email, phoneNumber, password); - public static ApplicationUser CreateNgoAdmin(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.NgoAdmin, firstName, lastName, email, phoneNumber, password); - public static ApplicationUser CreateObserver(string firstName, string lastName, string email, string phoneNumber, string password) => - new(UserRole.Observer, firstName, lastName, email, phoneNumber, password); + public static ApplicationUser CreateObserver(string firstName, string lastName, string email, string? phoneNumber, + string password) => + new(UserRole.Observer, firstName, lastName, email, phoneNumber, password); - public void UpdateDetails(string firstName, string lastName, string phoneNumber) + public void UpdateDetails(string firstName, string lastName, string? phoneNumber) { FirstName = firstName; LastName = lastName; @@ -69,6 +73,7 @@ public void UpdateRefreshToken(string refreshToken, DateTime refreshTokenExpiryT RefreshToken = refreshToken; RefreshTokenExpiryTime = refreshTokenExpiryTime; } + public void AcceptInvite(string password) { InvitationToken = null; @@ -93,6 +98,4 @@ public void Deactivate() // TODO: handle invariants Status = UserStatus.Deactivated; } - - } diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs new file mode 100644 index 000000000..0ff7b3c40 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/Coalition.cs @@ -0,0 +1,51 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class Coalition : AuditableBaseEntity, IAggregateRoot +{ + public Guid Id { get; private set; } + public Guid ElectionRoundId { get; private set; } + public ElectionRound ElectionRound { get; private set; } + public string Name { get; private set; } + + public Guid LeaderId { get; private set; } + public MonitoringNgo Leader { get; private set; } + public virtual List Memberships { get; internal set; } = []; + public virtual List FormAccess { get; internal set; } = []; + + internal Coalition(ElectionRound electionRound, string name, MonitoringNgo leader, + IEnumerable members) + { + Id = Guid.NewGuid(); + ElectionRound = electionRound; + ElectionRoundId = electionRound.Id; + Name = name; + Leader = leader; + LeaderId = leader.Id; + Memberships = members + .Select(member=>CoalitionMembership.Create(electionRound, this, member)) + .DistinctBy(x => x.MonitoringNgoId).ToList(); + } + + public static Coalition Create( + ElectionRound electionRound, + string name, + MonitoringNgo leaderNgo, + IEnumerable members) => new(electionRound, name, leaderNgo, members); + + public void Update(string coalitionName, IEnumerable memberships) + { + var leader = Memberships.FirstOrDefault(x => x.MonitoringNgoId == LeaderId) ?? + CoalitionMembership.Create(ElectionRoundId, Id, LeaderId); + Memberships = memberships.Union([leader]).DistinctBy(x => x.MonitoringNgoId).ToList(); + Name = coalitionName; + } + +#pragma warning disable CS8618 // Required by Entity Framework + private Coalition() + { + } +#pragma warning restore CS8618 + +} diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs new file mode 100644 index 000000000..c4ea20358 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionFormAccess.cs @@ -0,0 +1,39 @@ +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class CoalitionFormAccess +{ + public Guid CoalitionId { get; set; } + public Coalition Coalition { get; set; } + + public Guid MonitoringNgoId { get; set; } + public MonitoringNgo MonitoringNgo { get; set; } + + public Guid FormId { get; set; } + public Form Form { get; set; } + + public static CoalitionFormAccess Create(Guid coalitionId, Guid monitoringNgoId, Guid formId) => new() + { + CoalitionId = coalitionId, MonitoringNgoId = monitoringNgoId, FormId = formId + }; + + public static CoalitionFormAccess Create(Coalition coalition, + MonitoringNgo monitoringNgo, + Form form) => new() + { + Form = form, + FormId = form.Id, + Coalition = coalition, + CoalitionId = coalition.Id, + MonitoringNgo = monitoringNgo, + MonitoringNgoId = monitoringNgo.Id + }; + +#pragma warning disable CS8618 // Required by Entity Framework + private CoalitionFormAccess() + { + } +#pragma warning restore CS8618 +} diff --git a/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs new file mode 100644 index 000000000..48218e918 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CoalitionAggregate/CoalitionMembership.cs @@ -0,0 +1,38 @@ +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Vote.Monitor.Domain.Entities.CoalitionAggregate; + +public class CoalitionMembership +{ + public Guid ElectionRoundId { get; set; } + public ElectionRound ElectionRound { get; set; } + + public Guid CoalitionId { get; set; } + public Coalition Coalition { get; set; } + + public Guid MonitoringNgoId { get; set; } + public MonitoringNgo MonitoringNgo { get; set; } + + public static CoalitionMembership Create(Guid electionRoundId, Guid coalitionId, Guid monitoringNgoId) => new() + { + ElectionRoundId = electionRoundId, CoalitionId = coalitionId, MonitoringNgoId = monitoringNgoId + }; + + public static CoalitionMembership Create(ElectionRound electionRound, + Coalition coalition, + MonitoringNgo monitoringNgo) => new() + { + ElectionRound = electionRound, + Coalition = coalition, + MonitoringNgo = monitoringNgo, + ElectionRoundId = electionRound.Id, + CoalitionId = coalition.Id, + MonitoringNgoId = monitoringNgo.Id + }; + +#pragma warning disable CS8618 // Required by Entity Framework + private CoalitionMembership() + { + } +#pragma warning restore CS8618 +} diff --git a/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs b/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs index 513863e7e..06d757d40 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormBase/BaseForm.cs @@ -8,7 +8,6 @@ using Vote.Monitor.Domain.Entities.FormAnswerBase.Answers; using Vote.Monitor.Domain.Entities.FormBase.Questions; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; using Vote.Monitor.Domain.Entities.PollingStationInfoAggregate; using Form = Vote.Monitor.Domain.Entities.FormAggregate.Form; diff --git a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs index 0b77cb6df..029637f05 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs @@ -40,7 +40,7 @@ internal Form(Guid id, Languages = languages; } - private Form(FormType formTemplateType, + private Form(FormType formType, string code, string defaultLanguage, TranslatedString name, @@ -49,7 +49,7 @@ private Form(FormType formTemplateType, IEnumerable questions) { Id = Guid.NewGuid(); - FormType = formTemplateType; + FormType = formType; Code = code; DefaultLanguage = defaultLanguage; Name = name; @@ -59,14 +59,14 @@ private Form(FormType formTemplateType, Questions = questions.ToList(); } - public static Form Create(FormType formTemplateType, + public static Form Create(FormType formType, string code, string defaultLanguage, TranslatedString name, TranslatedString description, IEnumerable languages, IEnumerable questions) => - new(formTemplateType, code, defaultLanguage, name, description, languages, questions); + new(formType, code, defaultLanguage, name, description, languages, questions); public PublishResult Publish() { @@ -92,7 +92,7 @@ public void UpdateDetails(string code, string defaultLanguage, TranslatedString name, TranslatedString description, - FormType formTemplateType, + FormType formType, IEnumerable languages, IEnumerable questions) { @@ -100,7 +100,7 @@ public void UpdateDetails(string code, DefaultLanguage = defaultLanguage; Name = name; Description = description; - FormType = formTemplateType; + FormType = formType; Languages = languages.ToArray(); Questions = questions.ToList().AsReadOnly(); NumberOfQuestions = Questions.Count; diff --git a/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs b/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs index f2ea19d08..b1bd7cc19 100644 --- a/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs +++ b/api/src/Vote.Monitor.Domain/Entities/MonitoringNgoAggregate/MonitoringNgo.cs @@ -1,4 +1,5 @@ -using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; using Vote.Monitor.Domain.Entities.NgoAggregate; using Vote.Monitor.Domain.Entities.ObserverAggregate; @@ -15,6 +16,7 @@ public class MonitoringNgo : AuditableBaseEntity, IAggregateRoot public virtual List MonitoringObservers { get; internal set; } = []; public MonitoringNgoStatus Status { get; private set; } + public virtual List Memberships { get; internal set; } = []; internal MonitoringNgo(ElectionRound electionRound, Ngo ngo) { diff --git a/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs b/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs index 7976d8d4b..db0cf42f5 100644 --- a/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs +++ b/api/src/Vote.Monitor.Domain/Entities/NotificationAggregate/MonitoringObserverNotification.cs @@ -20,7 +20,7 @@ private MonitoringObserverNotification(Guid monitoringObserverId, Guid notificat public static MonitoringObserverNotification Create(Guid monitoringObserverId, Guid notificationId) => new(monitoringObserverId, notificationId); - public MonitoringObserverNotification() + private MonitoringObserverNotification() { } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs index 00e14307a..ebf4ccc45 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ApplicationUserConfiguration.cs @@ -13,6 +13,11 @@ public void Configure(EntityTypeBuilder builder) builder.Property(u => u.RefreshTokenExpiryTime); builder.Property(u => u.InvitationToken).HasMaxLength(256); + builder + .Property(p => p.DisplayName) + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", stored: true) + .ValueGeneratedOnAddOrUpdate(); + builder.OwnsOne(u => u.Preferences, b => { b.ToJson(); diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs new file mode 100644 index 000000000..963a54f2c --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).IsRequired(); + builder.Property(x => x.Name).IsRequired().HasMaxLength(256); + builder.HasIndex(x => x.ElectionRoundId); + + builder.HasOne(x => x.ElectionRound).WithMany().HasForeignKey(e => e.ElectionRoundId); + builder.HasOne(x => x.Leader).WithMany().HasForeignKey(e => e.LeaderId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs new file mode 100644 index 000000000..df45fdd4e --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionFormAccessConfiguration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionFormAccessConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(m => new { m.CoalitionId, m.MonitoringNgoId , m.FormId}); // Composite primary key + + builder + .HasOne(m => m.MonitoringNgo) + .WithMany() + .HasForeignKey(m => m.MonitoringNgoId); + + builder + .HasOne(m => m.Coalition) + .WithMany(c => c.FormAccess) + .HasForeignKey(m => m.CoalitionId); + + builder + .HasOne(m => m.Form) + .WithMany() + .HasForeignKey(m => m.FormId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs new file mode 100644 index 000000000..64e94b7c0 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CoalitionMembershipConfiguration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +public class CoalitionMembershipConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(m => new { m.MonitoringNgoId, m.CoalitionId }); // Composite primary key + + builder.HasIndex(m => new { m.MonitoringNgoId, m.CoalitionId, m.ElectionRoundId }).IsUnique(); + builder.HasIndex(m => new { m.MonitoringNgoId, m.ElectionRoundId }).IsUnique(); // only one membership per election + + builder + .HasOne(m => m.MonitoringNgo) + .WithMany(mn => mn.Memberships) + .HasForeignKey(m => m.MonitoringNgoId); + + builder + .HasOne(m => m.Coalition) + .WithMany(c => c.Memberships) + .HasForeignKey(m => m.CoalitionId); + + builder.HasOne(x => x.ElectionRound).WithMany().HasForeignKey(e => e.ElectionRoundId); + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs new file mode 100644 index 000000000..276fbc72c --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.Designer.cs @@ -0,0 +1,6967 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241111172627_AddCoalitions")] + partial class AddCoalitions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs new file mode 100644 index 000000000..84792c393 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241111172627_AddCoalitions.cs @@ -0,0 +1,162 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddCoalitions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Coalitions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ElectionRoundId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + LeaderId = table.Column(type: "uuid", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + LastModifiedOn = table.Column(type: "timestamp with time zone", nullable: true), + LastModifiedBy = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Coalitions", x => x.Id); + table.ForeignKey( + name: "FK_Coalitions_ElectionRounds_ElectionRoundId", + column: x => x.ElectionRoundId, + principalTable: "ElectionRounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Coalitions_MonitoringNgos_LeaderId", + column: x => x.LeaderId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CoalitionFormAccess", + columns: table => new + { + CoalitionId = table.Column(type: "uuid", nullable: false), + MonitoringNgoId = table.Column(type: "uuid", nullable: false), + FormId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CoalitionFormAccess", x => new { x.CoalitionId, x.MonitoringNgoId, x.FormId }); + table.ForeignKey( + name: "FK_CoalitionFormAccess_Coalitions_CoalitionId", + column: x => x.CoalitionId, + principalTable: "Coalitions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionFormAccess_Forms_FormId", + column: x => x.FormId, + principalTable: "Forms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionFormAccess_MonitoringNgos_MonitoringNgoId", + column: x => x.MonitoringNgoId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CoalitionMemberships", + columns: table => new + { + CoalitionId = table.Column(type: "uuid", nullable: false), + MonitoringNgoId = table.Column(type: "uuid", nullable: false), + ElectionRoundId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CoalitionMemberships", x => new { x.MonitoringNgoId, x.CoalitionId }); + table.ForeignKey( + name: "FK_CoalitionMemberships_Coalitions_CoalitionId", + column: x => x.CoalitionId, + principalTable: "Coalitions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionMemberships_ElectionRounds_ElectionRoundId", + column: x => x.ElectionRoundId, + principalTable: "ElectionRounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CoalitionMemberships_MonitoringNgos_MonitoringNgoId", + column: x => x.MonitoringNgoId, + principalTable: "MonitoringNgos", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionFormAccess_FormId", + table: "CoalitionFormAccess", + column: "FormId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionFormAccess_MonitoringNgoId", + table: "CoalitionFormAccess", + column: "MonitoringNgoId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_CoalitionId", + table: "CoalitionMemberships", + column: "CoalitionId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_ElectionRoundId", + table: "CoalitionMemberships", + column: "ElectionRoundId"); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_MonitoringNgoId_CoalitionId_ElectionRo~", + table: "CoalitionMemberships", + columns: new[] { "MonitoringNgoId", "CoalitionId", "ElectionRoundId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CoalitionMemberships_MonitoringNgoId_ElectionRoundId", + table: "CoalitionMemberships", + columns: new[] { "MonitoringNgoId", "ElectionRoundId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Coalitions_ElectionRoundId", + table: "Coalitions", + column: "ElectionRoundId"); + + migrationBuilder.CreateIndex( + name: "IX_Coalitions_LeaderId", + table: "Coalitions", + column: "LeaderId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CoalitionFormAccess"); + + migrationBuilder.DropTable( + name: "CoalitionMemberships"); + + migrationBuilder.DropTable( + name: "Coalitions"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs new file mode 100644 index 000000000..7754de642 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.Designer.cs @@ -0,0 +1,6973 @@ +// +using System; +using System.Collections.Generic; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vote.Monitor.Domain; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + [DbContext(typeof(VoteMonitorContext))] + [Migration("20241112130734_AddDisplayNameInAspNetUsers")] + partial class AddDisplayNameInAspNetUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("265e94b0-50fe-4546-b21c-83cb7e94aeff"), + Name = "PlatformAdmin", + NormalizedName = "PLATFORMADMIN" + }, + new + { + Id = new Guid("3239f803-dda8-408b-93ad-0ed973a04e45"), + Name = "NgoAdmin", + NormalizedName = "NGOADMIN" + }, + new + { + Id = new Guid("d1cbef39-62e0-4120-a42b-b01b029dc6ad"), + Name = "Observer", + NormalizedName = "OBSERVER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitationToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Attachments", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.Auditing.Trail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AffectedColumns") + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NewValues") + .HasColumnType("text"); + + b.Property("OldValues") + .HasColumnType("text"); + + b.Property("PrimaryKey") + .HasColumnType("text"); + + b.Property("TableName") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("AuditTrails"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("CitizenNotifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("LocationId"); + + b.ToTable("CitizenReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("CitizenReportId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.ToTable("CitizenReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.HasKey("Id"); + + b.HasIndex("Iso2") + .IsUnique(); + + b.HasIndex("Iso3") + .IsUnique(); + + b.HasIndex("NumericCode") + .IsUnique(); + + b.ToTable("Countries"); + + b.HasData( + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Afghanistan", + Iso2 = "AF", + Iso3 = "AFG", + Name = "Afghanistan", + NumericCode = "004" + }, + new + { + Id = new Guid("a96fe9bb-4ef4-fca0-f38b-0ec729822f37"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Åland Islands", + Iso2 = "AX", + Iso3 = "ALA", + Name = "Åland Islands", + NumericCode = "248" + }, + new + { + Id = new Guid("5aa0aeb7-4dc8-6a29-fc2f-35daec1541dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Albania", + Iso2 = "AL", + Iso3 = "ALB", + Name = "Albania", + NumericCode = "008" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Democratic Republic of Algeria", + Iso2 = "DZ", + Iso3 = "DZA", + Name = "Algeria", + NumericCode = "012" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "American Samoa", + Iso2 = "AS", + Iso3 = "ASM", + Name = "American Samoa", + NumericCode = "016" + }, + new + { + Id = new Guid("bd4bbfc7-d8bc-9d8d-7f7c-7b299c94e9e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Andorra", + Iso2 = "AD", + Iso3 = "AND", + Name = "Andorra", + NumericCode = "020" + }, + new + { + Id = new Guid("478786f7-1842-8c1e-921c-12e7ed5329c5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Angola", + Iso2 = "AO", + Iso3 = "AGO", + Name = "Angola", + NumericCode = "024" + }, + new + { + Id = new Guid("2b68fb11-a0e0-3d23-5fb8-99721ecfc182"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Anguilla", + Iso2 = "AI", + Iso3 = "AIA", + Name = "Anguilla", + NumericCode = "660" + }, + new + { + Id = new Guid("a0098040-b7a0-59a1-e64b-0a9778b7f74c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antarctica (the territory South of 60 deg S)", + Iso2 = "AQ", + Iso3 = "ATA", + Name = "Antarctica", + NumericCode = "010" + }, + new + { + Id = new Guid("f3eef99a-661e-2c68-7a4c-3053e2f28007"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Antigua and Barbuda", + Iso2 = "AG", + Iso3 = "ATG", + Name = "Antigua and Barbuda", + NumericCode = "028" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Argentine Republic", + Iso2 = "AR", + Iso3 = "ARG", + Name = "Argentina", + NumericCode = "032" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Armenia", + Iso2 = "AM", + Iso3 = "ARM", + Name = "Armenia", + NumericCode = "051" + }, + new + { + Id = new Guid("e6c7651f-182e-cf9c-1ef9-6293b95b500c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Aruba", + Iso2 = "AW", + Iso3 = "ABW", + Name = "Aruba", + NumericCode = "533" + }, + new + { + Id = new Guid("15639386-e4fc-120c-6916-c0c980e24be1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Australia", + Iso2 = "AU", + Iso3 = "AUS", + Name = "Australia", + NumericCode = "036" + }, + new + { + Id = new Guid("704254eb-6959-8ddc-a5df-ac8f9658dc68"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Austria", + Iso2 = "AT", + Iso3 = "AUT", + Name = "Austria", + NumericCode = "040" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Azerbaijan", + Iso2 = "AZ", + Iso3 = "AZE", + Name = "Azerbaijan", + NumericCode = "031" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Bahamas", + Iso2 = "BS", + Iso3 = "BHS", + Name = "Bahamas", + NumericCode = "044" + }, + new + { + Id = new Guid("44caa0f4-1e78-d2fb-96be-d01b3224bdc1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bahrain", + Iso2 = "BH", + Iso3 = "BHR", + Name = "Bahrain", + NumericCode = "048" + }, + new + { + Id = new Guid("809c3424-8654-b82c-cbd4-d857d096943e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of Bangladesh", + Iso2 = "BD", + Iso3 = "BGD", + Name = "Bangladesh", + NumericCode = "050" + }, + new + { + Id = new Guid("316c68fc-9144-f6e1-8bf1-899fc54b2327"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Barbados", + Iso2 = "BB", + Iso3 = "BRB", + Name = "Barbados", + NumericCode = "052" + }, + new + { + Id = new Guid("d97b5460-11ab-45c5-9a6f-ffa441ed70d6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Belarus", + Iso2 = "BY", + Iso3 = "BLR", + Name = "Belarus", + NumericCode = "112" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Belgium", + Iso2 = "BE", + Iso3 = "BEL", + Name = "Belgium", + NumericCode = "056" + }, + new + { + Id = new Guid("c89e02a0-9506-90df-5545-b98a2453cd63"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Belize", + Iso2 = "BZ", + Iso3 = "BLZ", + Name = "Belize", + NumericCode = "084" + }, + new + { + Id = new Guid("96a22cee-9af7-8f03-b483-b3e774a36d3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Benin", + Iso2 = "BJ", + Iso3 = "BEN", + Name = "Benin", + NumericCode = "204" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bermuda", + Iso2 = "BM", + Iso3 = "BMU", + Name = "Bermuda", + NumericCode = "060" + }, + new + { + Id = new Guid("8ed6a34e-8135-27fa-f86a-caa247b29768"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Bhutan", + Iso2 = "BT", + Iso3 = "BTN", + Name = "Bhutan", + NumericCode = "064" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Plurinational State of Bolivia", + Iso2 = "BO", + Iso3 = "BOL", + Name = "Bolivia", + NumericCode = "068" + }, + new + { + Id = new Guid("d8101f9d-8313-4054-c5f3-42c7a1c72862"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bonaire, Sint Eustatius and Saba", + Iso2 = "BQ", + Iso3 = "BES", + Name = "Bonaire, Sint Eustatius and Saba", + NumericCode = "535" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bosnia and Herzegovina", + Iso2 = "BA", + Iso3 = "BIH", + Name = "Bosnia and Herzegovina", + NumericCode = "070" + }, + new + { + Id = new Guid("14f190c6-97c9-3e12-2eba-db17c59d6a04"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Botswana", + Iso2 = "BW", + Iso3 = "BWA", + Name = "Botswana", + NumericCode = "072" + }, + new + { + Id = new Guid("32da0208-9048-1339-a8ee-6955cfff4c12"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bouvet Island (Bouvetøya)", + Iso2 = "BV", + Iso3 = "BVT", + Name = "Bouvet Island (Bouvetøya)", + NumericCode = "074" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federative Republic of Brazil", + Iso2 = "BR", + Iso3 = "BRA", + Name = "Brazil", + NumericCode = "076" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Indian Ocean Territory (Chagos Archipelago)", + Iso2 = "IO", + Iso3 = "IOT", + Name = "British Indian Ocean Territory (Chagos Archipelago)", + NumericCode = "086" + }, + new + { + Id = new Guid("39be5e86-aea5-f64f-fd7e-1017fe24e543"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "British Virgin Islands", + Iso2 = "VG", + Iso3 = "VGB", + Name = "British Virgin Islands", + NumericCode = "092" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Brunei Darussalam", + Iso2 = "BN", + Iso3 = "BRN", + Name = "Brunei Darussalam", + NumericCode = "096" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Bulgaria", + Iso2 = "BG", + Iso3 = "BGR", + Name = "Bulgaria", + NumericCode = "100" + }, + new + { + Id = new Guid("42697d56-52cf-b411-321e-c51929f02f90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Burkina Faso", + Iso2 = "BF", + Iso3 = "BFA", + Name = "Burkina Faso", + NumericCode = "854" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Burundi", + Iso2 = "BI", + Iso3 = "BDI", + Name = "Burundi", + NumericCode = "108" + }, + new + { + Id = new Guid("c9702851-1f67-f2a6-89d4-37b3fbb12044"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Cambodia", + Iso2 = "KH", + Iso3 = "KHM", + Name = "Cambodia", + NumericCode = "116" + }, + new + { + Id = new Guid("c0b7e39e-223a-ebb0-b899-5404573bbdb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cameroon", + Iso2 = "CM", + Iso3 = "CMR", + Name = "Cameroon", + NumericCode = "120" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Canada", + Iso2 = "CA", + Iso3 = "CAN", + Name = "Canada", + NumericCode = "124" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cabo Verde", + Iso2 = "CV", + Iso3 = "CPV", + Name = "Cabo Verde", + NumericCode = "132" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cayman Islands", + Iso2 = "KY", + Iso3 = "CYM", + Name = "Cayman Islands", + NumericCode = "136" + }, + new + { + Id = new Guid("b4e0625c-7597-c185-b8ae-cfb35a731f2f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Central African Republic", + Iso2 = "CF", + Iso3 = "CAF", + Name = "Central African Republic", + NumericCode = "140" + }, + new + { + Id = new Guid("2a1ca5b6-fba0-cfa8-9928-d7a2382bc4d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chad", + Iso2 = "TD", + Iso3 = "TCD", + Name = "Chad", + NumericCode = "148" + }, + new + { + Id = new Guid("ad4f938a-bf7b-684b-2c9e-e824d3fa3863"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Chile", + Iso2 = "CL", + Iso3 = "CHL", + Name = "Chile", + NumericCode = "152" + }, + new + { + Id = new Guid("8250c49f-9438-7c2e-f403-54d962db0c18"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "People's Republic of China", + Iso2 = "CN", + Iso3 = "CHN", + Name = "China", + NumericCode = "156" + }, + new + { + Id = new Guid("0f1ba59e-ade5-23e5-6fce-e2fd3282e114"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Christmas Island", + Iso2 = "CX", + Iso3 = "CXR", + Name = "Christmas Island", + NumericCode = "162" + }, + new + { + Id = new Guid("a16263a5-810c-bf6a-206d-72cb914e2d5c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cocos (Keeling) Islands", + Iso2 = "CC", + Iso3 = "CCK", + Name = "Cocos (Keeling) Islands", + NumericCode = "166" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Colombia", + Iso2 = "CO", + Iso3 = "COL", + Name = "Colombia", + NumericCode = "170" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Union of the Comoros", + Iso2 = "KM", + Iso3 = "COM", + Name = "Comoros", + NumericCode = "174" + }, + new + { + Id = new Guid("1258ec90-c47e-ff72-b7e3-f90c3ee320f8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of the Congo", + Iso2 = "CD", + Iso3 = "COD", + Name = "Congo", + NumericCode = "180" + }, + new + { + Id = new Guid("1934954c-66c2-6226-c5b6-491065a3e4c0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Congo", + Iso2 = "CG", + Iso3 = "COG", + Name = "Congo", + NumericCode = "178" + }, + new + { + Id = new Guid("af79558d-51fb-b08d-185b-afeb983ab99b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Cook Islands", + Iso2 = "CK", + Iso3 = "COK", + Name = "Cook Islands", + NumericCode = "184" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Costa Rica", + Iso2 = "CR", + Iso3 = "CRI", + Name = "Costa Rica", + NumericCode = "188" + }, + new + { + Id = new Guid("5be18efe-6db8-a727-7f2a-62bd71bc6593"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cote d'Ivoire", + Iso2 = "CI", + Iso3 = "CIV", + Name = "Cote d'Ivoire", + NumericCode = "384" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Croatia", + Iso2 = "HR", + Iso3 = "HRV", + Name = "Croatia", + NumericCode = "191" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cuba", + Iso2 = "CU", + Iso3 = "CUB", + Name = "Cuba", + NumericCode = "192" + }, + new + { + Id = new Guid("3345e205-3e72-43ed-de1b-ac6e050543e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Curaçao", + Iso2 = "CW", + Iso3 = "CUW", + Name = "Curaçao", + NumericCode = "531" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Cyprus", + Iso2 = "CY", + Iso3 = "CYP", + Name = "Cyprus", + NumericCode = "196" + }, + new + { + Id = new Guid("9d4ec95b-974a-f5bb-bb4b-ba6747440631"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Czech Republic", + Iso2 = "CZ", + Iso3 = "CZE", + Name = "Czechia", + NumericCode = "203" + }, + new + { + Id = new Guid("8a4fcb23-f3e6-fb5b-8cda-975872f600d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Denmark", + Iso2 = "DK", + Iso3 = "DNK", + Name = "Denmark", + NumericCode = "208" + }, + new + { + Id = new Guid("37a79267-d38a-aaef-577a-aa68a96880ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Djibouti", + Iso2 = "DJ", + Iso3 = "DJI", + Name = "Djibouti", + NumericCode = "262" + }, + new + { + Id = new Guid("19ea3a6a-1a76-23c8-8e4e-1d298f15207f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Dominica", + Iso2 = "DM", + Iso3 = "DMA", + Name = "Dominica", + NumericCode = "212" + }, + new + { + Id = new Guid("b2c4d2d7-7ada-7864-426f-10a28d9f9eba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Dominican Republic", + Iso2 = "DO", + Iso3 = "DOM", + Name = "Dominican Republic", + NumericCode = "214" + }, + new + { + Id = new Guid("49c82f1b-968d-b5e7-8559-e39567d46787"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ecuador", + Iso2 = "EC", + Iso3 = "ECU", + Name = "Ecuador", + NumericCode = "218" + }, + new + { + Id = new Guid("ee5dfc29-80f1-86ae-cde7-02484a18907a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Arab Republic of Egypt", + Iso2 = "EG", + Iso3 = "EGY", + Name = "Egypt", + NumericCode = "818" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of El Salvador", + Iso2 = "SV", + Iso3 = "SLV", + Name = "El Salvador", + NumericCode = "222" + }, + new + { + Id = new Guid("824392e8-a6cc-0cd4-af13-3067dad3258e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Equatorial Guinea", + Iso2 = "GQ", + Iso3 = "GNQ", + Name = "Equatorial Guinea", + NumericCode = "226" + }, + new + { + Id = new Guid("8b5a477a-070a-a84f-bd3b-f54dc2a172de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Eritrea", + Iso2 = "ER", + Iso3 = "ERI", + Name = "Eritrea", + NumericCode = "232" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Estonia", + Iso2 = "EE", + Iso3 = "EST", + Name = "Estonia", + NumericCode = "233" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Democratic Republic of Ethiopia", + Iso2 = "ET", + Iso3 = "ETH", + Name = "Ethiopia", + NumericCode = "231" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Faroe Islands", + Iso2 = "FO", + Iso3 = "FRO", + Name = "Faroe Islands", + NumericCode = "234" + }, + new + { + Id = new Guid("b86375dc-edbb-922c-9ed4-2f724094a5a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Falkland Islands (Malvinas)", + Iso2 = "FK", + Iso3 = "FLK", + Name = "Falkland Islands (Malvinas)", + NumericCode = "238" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Fiji", + Iso2 = "FJ", + Iso3 = "FJI", + Name = "Fiji", + NumericCode = "242" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Finland", + Iso2 = "FI", + Iso3 = "FIN", + Name = "Finland", + NumericCode = "246" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Republic", + Iso2 = "FR", + Iso3 = "FRA", + Name = "France", + NumericCode = "250" + }, + new + { + Id = new Guid("ac6cde6e-f645-d04e-8afc-0391ecf38a70"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Guiana", + Iso2 = "GF", + Iso3 = "GUF", + Name = "French Guiana", + NumericCode = "254" + }, + new + { + Id = new Guid("11dbce82-a154-7aee-7b5e-d5981f220572"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Polynesia", + Iso2 = "PF", + Iso3 = "PYF", + Name = "French Polynesia", + NumericCode = "258" + }, + new + { + Id = new Guid("903bee63-bcf0-0264-6eaf-a8cde95c5f41"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "French Southern Territories", + Iso2 = "TF", + Iso3 = "ATF", + Name = "French Southern Territories", + NumericCode = "260" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gabonese Republic", + Iso2 = "GA", + Iso3 = "GAB", + Name = "Gabon", + NumericCode = "266" + }, + new + { + Id = new Guid("a40b91b3-cc13-2470-65f0-a0fdc946f2a2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Gambia", + Iso2 = "GM", + Iso3 = "GMB", + Name = "Gambia", + NumericCode = "270" + }, + new + { + Id = new Guid("980176e8-7d9d-9729-b3e9-ebc455fb8fc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Georgia", + Iso2 = "GE", + Iso3 = "GEO", + Name = "Georgia", + NumericCode = "268" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Germany", + Iso2 = "DE", + Iso3 = "DEU", + Name = "Germany", + NumericCode = "276" + }, + new + { + Id = new Guid("6d0c77a7-a4aa-c2bd-2db6-0e2ad2d61f8a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Ghana", + Iso2 = "GH", + Iso3 = "GHA", + Name = "Ghana", + NumericCode = "288" + }, + new + { + Id = new Guid("8e0de349-f9ab-2bca-3910-efd48bf1170a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Gibraltar", + Iso2 = "GI", + Iso3 = "GIB", + Name = "Gibraltar", + NumericCode = "292" + }, + new + { + Id = new Guid("4fc1a9dc-cc74-f6ce-5743-c5cee8d709ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hellenic Republic of Greece", + Iso2 = "GR", + Iso3 = "GRC", + Name = "Greece", + NumericCode = "300" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Greenland", + Iso2 = "GL", + Iso3 = "GRL", + Name = "Greenland", + NumericCode = "304" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grenada", + Iso2 = "GD", + Iso3 = "GRD", + Name = "Grenada", + NumericCode = "308" + }, + new + { + Id = new Guid("3bcd2aad-fb69-09f4-1ad7-2c7f5fa23f9f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guadeloupe", + Iso2 = "GP", + Iso3 = "GLP", + Name = "Guadeloupe", + NumericCode = "312" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Guam", + Iso2 = "GU", + Iso3 = "GUM", + Name = "Guam", + NumericCode = "316" + }, + new + { + Id = new Guid("d24b46ba-8e9d-2a09-7995-e35e8ae54f6b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guatemala", + Iso2 = "GT", + Iso3 = "GTM", + Name = "Guatemala", + NumericCode = "320" + }, + new + { + Id = new Guid("5b0ee3be-596d-bdc1-f101-00ef33170655"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Guernsey", + Iso2 = "GG", + Iso3 = "GGY", + Name = "Guernsey", + NumericCode = "831" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea", + Iso2 = "GN", + Iso3 = "GIN", + Name = "Guinea", + NumericCode = "324" + }, + new + { + Id = new Guid("a9a5f440-a9bd-487d-e7f4-914df0d52fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Guinea-Bissau", + Iso2 = "GW", + Iso3 = "GNB", + Name = "Guinea-Bissau", + NumericCode = "624" + }, + new + { + Id = new Guid("a9949ac7-8d2d-32b5-3f4f-e2a3ef291a67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Co-operative Republic of Guyana", + Iso2 = "GY", + Iso3 = "GUY", + Name = "Guyana", + NumericCode = "328" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Haiti", + Iso2 = "HT", + Iso3 = "HTI", + Name = "Haiti", + NumericCode = "332" + }, + new + { + Id = new Guid("592b4658-a210-ab0a-5660-3dcc673dc581"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Heard Island and McDonald Islands", + Iso2 = "HM", + Iso3 = "HMD", + Name = "Heard Island and McDonald Islands", + NumericCode = "334" + }, + new + { + Id = new Guid("d0e11a85-6623-69f5-bd95-3779dfeec297"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Holy See (Vatican City State)", + Iso2 = "VA", + Iso3 = "VAT", + Name = "Holy See (Vatican City State)", + NumericCode = "336" + }, + new + { + Id = new Guid("0aebadaa-91b2-8794-c153-4f903a2a1004"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Honduras", + Iso2 = "HN", + Iso3 = "HND", + Name = "Honduras", + NumericCode = "340" + }, + new + { + Id = new Guid("500bb0de-61f5-dc9b-0488-1c507456ea4d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hong Kong Special Administrative Region of China", + Iso2 = "HK", + Iso3 = "HKG", + Name = "Hong Kong", + NumericCode = "344" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hungary", + Iso2 = "HU", + Iso3 = "HUN", + Name = "Hungary", + NumericCode = "348" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Iceland", + Iso2 = "IS", + Iso3 = "ISL", + Name = "Iceland", + NumericCode = "352" + }, + new + { + Id = new Guid("72d8d1fe-d5f6-f440-1185-82ec69427027"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of India", + Iso2 = "IN", + Iso3 = "IND", + Name = "India", + NumericCode = "356" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Indonesia", + Iso2 = "ID", + Iso3 = "IDN", + Name = "Indonesia", + NumericCode = "360" + }, + new + { + Id = new Guid("b3460bab-2a35-57bc-17e2-4e117748bbb1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Iran", + Iso2 = "IR", + Iso3 = "IRN", + Name = "Iran", + NumericCode = "364" + }, + new + { + Id = new Guid("6c8be2e6-8c2e-cd80-68a6-d18c80d0eedc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Iraq", + Iso2 = "IQ", + Iso3 = "IRQ", + Name = "Iraq", + NumericCode = "368" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ireland", + Iso2 = "IE", + Iso3 = "IRL", + Name = "Ireland", + NumericCode = "372" + }, + new + { + Id = new Guid("a1b83be0-6a9b-c8a9-2cce-531705a29664"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Isle of Man", + Iso2 = "IM", + Iso3 = "IMN", + Name = "Isle of Man", + NumericCode = "833" + }, + new + { + Id = new Guid("7ffa909b-8a6a-3028-9589-fcc3dfa530a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Israel", + Iso2 = "IL", + Iso3 = "ISR", + Name = "Israel", + NumericCode = "376" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Italy", + Iso2 = "IT", + Iso3 = "ITA", + Name = "Italy", + NumericCode = "380" + }, + new + { + Id = new Guid("6699efd5-0939-7812-315e-21f37b279ee9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Jamaica", + Iso2 = "JM", + Iso3 = "JAM", + Name = "Jamaica", + NumericCode = "388" + }, + new + { + Id = new Guid("13c69e56-375d-8a7e-c326-be2be2fd4cd8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Japan", + Iso2 = "JP", + Iso3 = "JPN", + Name = "Japan", + NumericCode = "392" + }, + new + { + Id = new Guid("65d871be-4a1d-a632-9cdb-62e3ff04928d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bailiwick of Jersey", + Iso2 = "JE", + Iso3 = "JEY", + Name = "Jersey", + NumericCode = "832" + }, + new + { + Id = new Guid("9ae7ad80-9ce7-6657-75cf-28b4c0254238"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Hashemite Kingdom of Jordan", + Iso2 = "JO", + Iso3 = "JOR", + Name = "Jordan", + NumericCode = "400" + }, + new + { + Id = new Guid("b723594d-7800-0f37-db86-0f6b85bb6cf9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kazakhstan", + Iso2 = "KZ", + Iso3 = "KAZ", + Name = "Kazakhstan", + NumericCode = "398" + }, + new + { + Id = new Guid("b32fe2b5-a06e-0d76-ffd2-f186c3e64b15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kenya", + Iso2 = "KE", + Iso3 = "KEN", + Name = "Kenya", + NumericCode = "404" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Kiribati", + Iso2 = "KI", + Iso3 = "KIR", + Name = "Kiribati", + NumericCode = "296" + }, + new + { + Id = new Guid("f70ae426-f130-5637-0383-a5b63a06c500"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic People's Republic of Korea", + Iso2 = "KP", + Iso3 = "PRK", + Name = "Korea", + NumericCode = "408" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Korea", + Iso2 = "KR", + Iso3 = "KOR", + Name = "Korea", + NumericCode = "410" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Kuwait", + Iso2 = "KW", + Iso3 = "KWT", + Name = "Kuwait", + NumericCode = "414" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kyrgyz Republic", + Iso2 = "KG", + Iso3 = "KGZ", + Name = "Kyrgyz Republic", + NumericCode = "417" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lao People's Democratic Republic", + Iso2 = "LA", + Iso3 = "LAO", + Name = "Lao People's Democratic Republic", + NumericCode = "418" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Latvia", + Iso2 = "LV", + Iso3 = "LVA", + Name = "Latvia", + NumericCode = "428" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Lebanese Republic", + Iso2 = "LB", + Iso3 = "LBN", + Name = "Lebanon", + NumericCode = "422" + }, + new + { + Id = new Guid("bf210ee6-6c75-cf08-052e-5c3e608aed15"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Lesotho", + Iso2 = "LS", + Iso3 = "LSO", + Name = "Lesotho", + NumericCode = "426" + }, + new + { + Id = new Guid("ee926d09-799c-7c6a-2419-a6ff814b2c03"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Liberia", + Iso2 = "LR", + Iso3 = "LBR", + Name = "Liberia", + NumericCode = "430" + }, + new + { + Id = new Guid("695c85b3-a6c6-c217-9be8-3baebc7719ce"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Libya", + Iso2 = "LY", + Iso3 = "LBY", + Name = "Libya", + NumericCode = "434" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Liechtenstein", + Iso2 = "LI", + Iso3 = "LIE", + Name = "Liechtenstein", + NumericCode = "438" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Lithuania", + Iso2 = "LT", + Iso3 = "LTU", + Name = "Lithuania", + NumericCode = "440" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Grand Duchy of Luxembourg", + Iso2 = "LU", + Iso3 = "LUX", + Name = "Luxembourg", + NumericCode = "442" + }, + new + { + Id = new Guid("8d32a12d-3230-1431-8fbb-72c789184345"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Macao Special Administrative Region of China", + Iso2 = "MO", + Iso3 = "MAC", + Name = "Macao", + NumericCode = "446" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Madagascar", + Iso2 = "MG", + Iso3 = "MDG", + Name = "Madagascar", + NumericCode = "450" + }, + new + { + Id = new Guid("fbf4479d-d70d-c76e-b053-699362443a17"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malawi", + Iso2 = "MW", + Iso3 = "MWI", + Name = "Malawi", + NumericCode = "454" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Malaysia", + Iso2 = "MY", + Iso3 = "MYS", + Name = "Malaysia", + NumericCode = "458" + }, + new + { + Id = new Guid("1d2aa3ab-e1c3-8c76-9be6-7a3b3eca35da"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Maldives", + Iso2 = "MV", + Iso3 = "MDV", + Name = "Maldives", + NumericCode = "462" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mali", + Iso2 = "ML", + Iso3 = "MLI", + Name = "Mali", + NumericCode = "466" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Malta", + Iso2 = "MT", + Iso3 = "MLT", + Name = "Malta", + NumericCode = "470" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Marshall Islands", + Iso2 = "MH", + Iso3 = "MHL", + Name = "Marshall Islands", + NumericCode = "584" + }, + new + { + Id = new Guid("fc78fa89-b372-dcf7-7f1c-1e1bb14ecbe7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Martinique", + Iso2 = "MQ", + Iso3 = "MTQ", + Name = "Martinique", + NumericCode = "474" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Mauritania", + Iso2 = "MR", + Iso3 = "MRT", + Name = "Mauritania", + NumericCode = "478" + }, + new + { + Id = new Guid("1b634ca2-2b90-7e54-715a-74cee7e4d294"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mauritius", + Iso2 = "MU", + Iso3 = "MUS", + Name = "Mauritius", + NumericCode = "480" + }, + new + { + Id = new Guid("08a999e4-e420-b864-2864-bef78c138448"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mayotte", + Iso2 = "YT", + Iso3 = "MYT", + Name = "Mayotte", + NumericCode = "175" + }, + new + { + Id = new Guid("a9940e91-93ef-19f7-79c0-00d31c6a9f87"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Mexican States", + Iso2 = "MX", + Iso3 = "MEX", + Name = "Mexico", + NumericCode = "484" + }, + new + { + Id = new Guid("a2da72dc-5866-ba2f-6283-6575af00ade5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federated States of Micronesia", + Iso2 = "FM", + Iso3 = "FSM", + Name = "Micronesia", + NumericCode = "583" + }, + new + { + Id = new Guid("daf6bc7a-92c4-ef47-3111-e13199b86b90"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Moldova", + Iso2 = "MD", + Iso3 = "MDA", + Name = "Moldova", + NumericCode = "498" + }, + new + { + Id = new Guid("5cab34ca-8c74-0766-c7ca-4a826b44c5bd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Principality of Monaco", + Iso2 = "MC", + Iso3 = "MCO", + Name = "Monaco", + NumericCode = "492" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Mongolia", + Iso2 = "MN", + Iso3 = "MNG", + Name = "Mongolia", + NumericCode = "496" + }, + new + { + Id = new Guid("86db2170-be87-fd1d-bf57-05ff61ae83a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montenegro", + Iso2 = "ME", + Iso3 = "MNE", + Name = "Montenegro", + NumericCode = "499" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Montserrat", + Iso2 = "MS", + Iso3 = "MSR", + Name = "Montserrat", + NumericCode = "500" + }, + new + { + Id = new Guid("915805f0-9ff0-48ff-39b3-44a4af5e0482"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Morocco", + Iso2 = "MA", + Iso3 = "MAR", + Name = "Morocco", + NumericCode = "504" + }, + new + { + Id = new Guid("10b58d9b-42ef-edb8-54a3-712636fda55a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Mozambique", + Iso2 = "MZ", + Iso3 = "MOZ", + Name = "Mozambique", + NumericCode = "508" + }, + new + { + Id = new Guid("015a9f83-6e57-bc1e-8227-24a4e5248582"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Union of Myanmar", + Iso2 = "MM", + Iso3 = "MMR", + Name = "Myanmar", + NumericCode = "104" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Namibia", + Iso2 = "NA", + Iso3 = "NAM", + Name = "Namibia", + NumericCode = "516" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nauru", + Iso2 = "NR", + Iso3 = "NRU", + Name = "Nauru", + NumericCode = "520" + }, + new + { + Id = new Guid("e81c5db3-401a-e047-001e-045f39bef8ef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Nepal", + Iso2 = "NP", + Iso3 = "NPL", + Name = "Nepal", + NumericCode = "524" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of the Netherlands", + Iso2 = "NL", + Iso3 = "NLD", + Name = "Netherlands", + NumericCode = "528" + }, + new + { + Id = new Guid("4b0729b6-f698-5730-767c-88e2d36691bb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Caledonia", + Iso2 = "NC", + Iso3 = "NCL", + Name = "New Caledonia", + NumericCode = "540" + }, + new + { + Id = new Guid("360e3c61-aaac-fa2f-d731-fc0824c05107"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "New Zealand", + Iso2 = "NZ", + Iso3 = "NZL", + Name = "New Zealand", + NumericCode = "554" + }, + new + { + Id = new Guid("cd0e8275-3def-1de4-8858-61aab36851c4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Nicaragua", + Iso2 = "NI", + Iso3 = "NIC", + Name = "Nicaragua", + NumericCode = "558" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Niger", + Iso2 = "NE", + Iso3 = "NER", + Name = "Niger", + NumericCode = "562" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Nigeria", + Iso2 = "NG", + Iso3 = "NGA", + Name = "Nigeria", + NumericCode = "566" + }, + new + { + Id = new Guid("3eea06f4-c085-f619-6d52-b76a5f6fd2b6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Niue", + Iso2 = "NU", + Iso3 = "NIU", + Name = "Niue", + NumericCode = "570" + }, + new + { + Id = new Guid("47804b6a-e705-b925-f4fd-4adf6500180b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Norfolk Island", + Iso2 = "NF", + Iso3 = "NFK", + Name = "Norfolk Island", + NumericCode = "574" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of North Macedonia", + Iso2 = "MK", + Iso3 = "MKD", + Name = "North Macedonia", + NumericCode = "807" + }, + new + { + Id = new Guid("6ac64a20-5688-ccd0-4eca-88d8a2560079"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of the Northern Mariana Islands", + Iso2 = "MP", + Iso3 = "MNP", + Name = "Northern Mariana Islands", + NumericCode = "580" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Norway", + Iso2 = "NO", + Iso3 = "NOR", + Name = "Norway", + NumericCode = "578" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sultanate of Oman", + Iso2 = "OM", + Iso3 = "OMN", + Name = "Oman", + NumericCode = "512" + }, + new + { + Id = new Guid("cc7fabfc-4c2b-d9ff-bb45-003bfc2e468a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Islamic Republic of Pakistan", + Iso2 = "PK", + Iso3 = "PAK", + Name = "Pakistan", + NumericCode = "586" + }, + new + { + Id = new Guid("057884bc-3c2e-dea9-6522-b003c9297f7a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Palau", + Iso2 = "PW", + Iso3 = "PLW", + Name = "Palau", + NumericCode = "585" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Palestine", + Iso2 = "PS", + Iso3 = "PSE", + Name = "Palestine", + NumericCode = "275" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Panama", + Iso2 = "PA", + Iso3 = "PAN", + Name = "Panama", + NumericCode = "591" + }, + new + { + Id = new Guid("c926f091-fe96-35b3-56b5-d418d17e0159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Papua New Guinea", + Iso2 = "PG", + Iso3 = "PNG", + Name = "Papua New Guinea", + NumericCode = "598" + }, + new + { + Id = new Guid("db6ce903-ab43-3793-960c-659529bae6df"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Paraguay", + Iso2 = "PY", + Iso3 = "PRY", + Name = "Paraguay", + NumericCode = "600" + }, + new + { + Id = new Guid("75634729-8e4a-4cfd-739d-9f679bfca3ab"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Peru", + Iso2 = "PE", + Iso3 = "PER", + Name = "Peru", + NumericCode = "604" + }, + new + { + Id = new Guid("c93bccaf-1835-3c02-e2ee-c113ced19e43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of the Philippines", + Iso2 = "PH", + Iso3 = "PHL", + Name = "Philippines", + NumericCode = "608" + }, + new + { + Id = new Guid("a5d0c9af-2022-2b43-9332-eb6a2ce4305d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Pitcairn Islands", + Iso2 = "PN", + Iso3 = "PCN", + Name = "Pitcairn Islands", + NumericCode = "612" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Poland", + Iso2 = "PL", + Iso3 = "POL", + Name = "Poland", + NumericCode = "616" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Portuguese Republic", + Iso2 = "PT", + Iso3 = "PRT", + Name = "Portugal", + NumericCode = "620" + }, + new + { + Id = new Guid("cd2c97c3-5473-0719-3803-fcacedfe2ea2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Commonwealth of Puerto Rico", + Iso2 = "PR", + Iso3 = "PRI", + Name = "Puerto Rico", + NumericCode = "630" + }, + new + { + Id = new Guid("067c9448-9ad0-2c21-a1dc-fbdf5a63d18d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "State of Qatar", + Iso2 = "QA", + Iso3 = "QAT", + Name = "Qatar", + NumericCode = "634" + }, + new + { + Id = new Guid("881b4bb8-b6da-c73e-55c0-c9f31c02aaef"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Réunion", + Iso2 = "RE", + Iso3 = "REU", + Name = "Réunion", + NumericCode = "638" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Romania", + Iso2 = "RO", + Iso3 = "ROU", + Name = "Romania", + NumericCode = "642" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Russian Federation", + Iso2 = "RU", + Iso3 = "RUS", + Name = "Russian Federation", + NumericCode = "643" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Rwanda", + Iso2 = "RW", + Iso3 = "RWA", + Name = "Rwanda", + NumericCode = "646" + }, + new + { + Id = new Guid("77f6f69b-ec41-8818-9395-8d39bf09e653"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Barthélemy", + Iso2 = "BL", + Iso3 = "BLM", + Name = "Saint Barthélemy", + NumericCode = "652" + }, + new + { + Id = new Guid("6a76d068-49e1-da80-ddb4-9ef3d11191e6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Helena, Ascension and Tristan da Cunha", + Iso2 = "SH", + Iso3 = "SHN", + Name = "Saint Helena, Ascension and Tristan da Cunha", + NumericCode = "654" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federation of Saint Kitts and Nevis", + Iso2 = "KN", + Iso3 = "KNA", + Name = "Saint Kitts and Nevis", + NumericCode = "659" + }, + new + { + Id = new Guid("220e980a-7363-0150-c250-89e83b967fb4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Lucia", + Iso2 = "LC", + Iso3 = "LCA", + Name = "Saint Lucia", + NumericCode = "662" + }, + new + { + Id = new Guid("899c2a9f-f35d-5a49-a6cd-f92531bb2266"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Martin (French part)", + Iso2 = "MF", + Iso3 = "MAF", + Name = "Saint Martin", + NumericCode = "663" + }, + new + { + Id = new Guid("5476986b-11a4-8463-9bd7-0f7354ec7a20"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Pierre and Miquelon", + Iso2 = "PM", + Iso3 = "SPM", + Name = "Saint Pierre and Miquelon", + NumericCode = "666" + }, + new + { + Id = new Guid("2f49855b-ff93-c399-d72a-121f2bf28bc9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Saint Vincent and the Grenadines", + Iso2 = "VC", + Iso3 = "VCT", + Name = "Saint Vincent and the Grenadines", + NumericCode = "670" + }, + new + { + Id = new Guid("a7c4c9db-8fe4-7d43-e830-1a70954970c3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Independent State of Samoa", + Iso2 = "WS", + Iso3 = "WSM", + Name = "Samoa", + NumericCode = "882" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of San Marino", + Iso2 = "SM", + Iso3 = "SMR", + Name = "San Marino", + NumericCode = "674" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Sao Tome and Principe", + Iso2 = "ST", + Iso3 = "STP", + Name = "Sao Tome and Principe", + NumericCode = "678" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Saudi Arabia", + Iso2 = "SA", + Iso3 = "SAU", + Name = "Saudi Arabia", + NumericCode = "682" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Senegal", + Iso2 = "SN", + Iso3 = "SEN", + Name = "Senegal", + NumericCode = "686" + }, + new + { + Id = new Guid("971c7e66-c6e3-71f4-580a-5caf2852f9f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Serbia", + Iso2 = "RS", + Iso3 = "SRB", + Name = "Serbia", + NumericCode = "688" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Seychelles", + Iso2 = "SC", + Iso3 = "SYC", + Name = "Seychelles", + NumericCode = "690" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sierra Leone", + Iso2 = "SL", + Iso3 = "SLE", + Name = "Sierra Leone", + NumericCode = "694" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Singapore", + Iso2 = "SG", + Iso3 = "SGP", + Name = "Singapore", + NumericCode = "702" + }, + new + { + Id = new Guid("141e589a-7046-a265-d2f6-b2f85e6eeadd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Sint Maarten (Dutch part)", + Iso2 = "SX", + Iso3 = "SXM", + Name = "Sint Maarten (Dutch part)", + NumericCode = "534" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Slovakia (Slovak Republic)", + Iso2 = "SK", + Iso3 = "SVK", + Name = "Slovakia (Slovak Republic)", + NumericCode = "703" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Slovenia", + Iso2 = "SI", + Iso3 = "SVN", + Name = "Slovenia", + NumericCode = "705" + }, + new + { + Id = new Guid("7453c201-ecf1-d3dd-0409-e94d0733173b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Solomon Islands", + Iso2 = "SB", + Iso3 = "SLB", + Name = "Solomon Islands", + NumericCode = "090" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Federal Republic of Somalia", + Iso2 = "SO", + Iso3 = "SOM", + Name = "Somalia", + NumericCode = "706" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Africa", + Iso2 = "ZA", + Iso3 = "ZAF", + Name = "South Africa", + NumericCode = "710" + }, + new + { + Id = new Guid("6af4d03e-edd0-d98a-bc7e-abc7df87d3dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "South Georgia and the South Sandwich Islands", + Iso2 = "GS", + Iso3 = "SGS", + Name = "South Georgia and the South Sandwich Islands", + NumericCode = "239" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of South Sudan", + Iso2 = "SS", + Iso3 = "SSD", + Name = "South Sudan", + NumericCode = "728" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Spain", + Iso2 = "ES", + Iso3 = "ESP", + Name = "Spain", + NumericCode = "724" + }, + new + { + Id = new Guid("687320c8-e841-c911-6d30-b14eb998feb6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Socialist Republic of Sri Lanka", + Iso2 = "LK", + Iso3 = "LKA", + Name = "Sri Lanka", + NumericCode = "144" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Sudan", + Iso2 = "SD", + Iso3 = "SDN", + Name = "Sudan", + NumericCode = "729" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Suriname", + Iso2 = "SR", + Iso3 = "SUR", + Name = "Suriname", + NumericCode = "740" + }, + new + { + Id = new Guid("d525de3a-aecc-07de-0426-68f32af2968e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Svalbard & Jan Mayen Islands", + Iso2 = "SJ", + Iso3 = "SJM", + Name = "Svalbard & Jan Mayen Islands", + NumericCode = "744" + }, + new + { + Id = new Guid("a32a9fc2-677f-43e0-97aa-9e83943d785c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Eswatini", + Iso2 = "SZ", + Iso3 = "SWZ", + Name = "Eswatini", + NumericCode = "748" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Sweden", + Iso2 = "SE", + Iso3 = "SWE", + Name = "Sweden", + NumericCode = "752" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Swiss Confederation", + Iso2 = "CH", + Iso3 = "CHE", + Name = "Switzerland", + NumericCode = "756" + }, + new + { + Id = new Guid("c1a923f6-b9ec-78f7-cc1c-7025e3d69d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Syrian Arab Republic", + Iso2 = "SY", + Iso3 = "SYR", + Name = "Syrian Arab Republic", + NumericCode = "760" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Taiwan, Province of China", + Iso2 = "TW", + Iso3 = "TWN", + Name = "Taiwan", + NumericCode = "158" + }, + new + { + Id = new Guid("2a848549-9777-cf48-a0f2-b32c6f942096"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Tajikistan", + Iso2 = "TJ", + Iso3 = "TJK", + Name = "Tajikistan", + NumericCode = "762" + }, + new + { + Id = new Guid("4736c1ad-54bd-c8e8-d9ee-492a88268de8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Republic of Tanzania", + Iso2 = "TZ", + Iso3 = "TZA", + Name = "Tanzania", + NumericCode = "834" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Thailand", + Iso2 = "TH", + Iso3 = "THA", + Name = "Thailand", + NumericCode = "764" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Democratic Republic of Timor-Leste", + Iso2 = "TL", + Iso3 = "TLS", + Name = "Timor-Leste", + NumericCode = "626" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Togolese Republic", + Iso2 = "TG", + Iso3 = "TGO", + Name = "Togo", + NumericCode = "768" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tokelau", + Iso2 = "TK", + Iso3 = "TKL", + Name = "Tokelau", + NumericCode = "772" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Kingdom of Tonga", + Iso2 = "TO", + Iso3 = "TON", + Name = "Tonga", + NumericCode = "776" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Trinidad and Tobago", + Iso2 = "TT", + Iso3 = "TTO", + Name = "Trinidad and Tobago", + NumericCode = "780" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tunisian Republic", + Iso2 = "TN", + Iso3 = "TUN", + Name = "Tunisia", + NumericCode = "788" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Türkiye", + Iso2 = "TR", + Iso3 = "TUR", + Name = "Türkiye", + NumericCode = "792" + }, + new + { + Id = new Guid("550ca5df-3995-617c-c39d-437beb400a42"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turkmenistan", + Iso2 = "TM", + Iso3 = "TKM", + Name = "Turkmenistan", + NumericCode = "795" + }, + new + { + Id = new Guid("0e0fefd5-9a05-fde5-bee9-ef56db7748a1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Turks and Caicos Islands", + Iso2 = "TC", + Iso3 = "TCA", + Name = "Turks and Caicos Islands", + NumericCode = "796" + }, + new + { + Id = new Guid("e0d562ca-f573-3c2f-eb83-f72d4d70d4fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Tuvalu", + Iso2 = "TV", + Iso3 = "TUV", + Name = "Tuvalu", + NumericCode = "798" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uganda", + Iso2 = "UG", + Iso3 = "UGA", + Name = "Uganda", + NumericCode = "800" + }, + new + { + Id = new Guid("e087f51c-feba-19b6-5595-fcbdce170411"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Ukraine", + Iso2 = "UA", + Iso3 = "UKR", + Name = "Ukraine", + NumericCode = "804" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Arab Emirates", + Iso2 = "AE", + Iso3 = "ARE", + Name = "United Arab Emirates", + NumericCode = "784" + }, + new + { + Id = new Guid("0b3b04b4-9782-79e3-bc55-9ab33b6ae9c7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United Kingdom of Great Britain & Northern Ireland", + Iso2 = "GB", + Iso3 = "GBR", + Name = "United Kingdom of Great Britain and Northern Ireland", + NumericCode = "826" + }, + new + { + Id = new Guid("cb2e209b-d4c6-6d5c-8901-d989a9188783"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States of America", + Iso2 = "US", + Iso3 = "USA", + Name = "United States of America", + NumericCode = "840" + }, + new + { + Id = new Guid("0868cdd3-7f50-5a25-88d6-98c45f9157e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Minor Outlying Islands", + Iso2 = "UM", + Iso3 = "UMI", + Name = "United States Minor Outlying Islands", + NumericCode = "581" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "United States Virgin Islands", + Iso2 = "VI", + Iso3 = "VIR", + Name = "United States Virgin Islands", + NumericCode = "850" + }, + new + { + Id = new Guid("8e787470-aae6-575a-fe0b-d65fc78b648a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Eastern Republic of Uruguay", + Iso2 = "UY", + Iso3 = "URY", + Name = "Uruguay", + NumericCode = "858" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Uzbekistan", + Iso2 = "UZ", + Iso3 = "UZB", + Name = "Uzbekistan", + NumericCode = "860" + }, + new + { + Id = new Guid("c98174ef-8198-54ba-2ff1-b93f3c646db8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Vanuatu", + Iso2 = "VU", + Iso3 = "VUT", + Name = "Vanuatu", + NumericCode = "548" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Bolivarian Republic of Venezuela", + Iso2 = "VE", + Iso3 = "VEN", + Name = "Venezuela", + NumericCode = "862" + }, + new + { + Id = new Guid("d7236157-d5a7-6b7a-3bc1-69802313fa30"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Socialist Republic of Vietnam", + Iso2 = "VN", + Iso3 = "VNM", + Name = "Vietnam", + NumericCode = "704" + }, + new + { + Id = new Guid("e186a953-7ab3-c009-501c-a754267b770b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Wallis and Futuna", + Iso2 = "WF", + Iso3 = "WLF", + Name = "Wallis and Futuna", + NumericCode = "876" + }, + new + { + Id = new Guid("2f4cc994-53f1-1763-8220-5d89e063804f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Western Sahara", + Iso2 = "EH", + Iso3 = "ESH", + Name = "Western Sahara", + NumericCode = "732" + }, + new + { + Id = new Guid("8c4441fd-8cd4-ff1e-928e-e46f9ca12552"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Yemen", + Iso2 = "YE", + Iso3 = "YEM", + Name = "Yemen", + NumericCode = "887" + }, + new + { + Id = new Guid("ab0b7e83-bf02-16e6-e5ae-46c4bd4c093b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zambia", + Iso2 = "ZM", + Iso3 = "ZMB", + Name = "Zambia", + NumericCode = "894" + }, + new + { + Id = new Guid("6984f722-6963-d067-d4d4-9fd3ef2edbf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + FullName = "Republic of Zimbabwe", + Iso2 = "ZW", + Iso3 = "ZWE", + Name = "Zimbabwe", + NumericCode = "716" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CitizenReportingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("EnglishTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationsVersion") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoForCitizenReportingId") + .HasColumnType("uuid"); + + b.Property("PollingStationsVersion") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("MonitoringNgoForCitizenReportingId"); + + b.ToTable("ElectionRounds"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Base64EncodedData") + .HasColumnType("text"); + + b.Property("CitizenReportsFilers") + .HasColumnType("jsonb"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("ExportStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExportedDataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormSubmissionsFilters") + .HasColumnType("jsonb"); + + b.Property("IncidentReportsFilters") + .HasColumnType("jsonb"); + + b.Property("QuickReportsFilters") + .HasColumnType("jsonb"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.ToTable("ExportedData", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property>("Metadata") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("TimeSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("UserFeedback") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("ObserverId"); + + b.ToTable("UserFeedback"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("Forms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "FormId") + .IsUnique(); + + b.ToTable("FormSubmissions", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("FormType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FormTemplates"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ImportValidationErrorsAggregate.ImportValidationErrors", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImportType") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.ToTable("ImportValidationErrors"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationDescription") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("LocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("IncidentReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportAttachments"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("IncidentReportId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("IncidentReportId"); + + b.ToTable("IncidentReportNotes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LanguageAggregate.Language", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Iso1") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NativeName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("Iso1") + .IsUnique(); + + b.ToTable("Language"); + + b.HasData( + new + { + Id = new Guid("9c11bb58-5135-453a-1d24-dc20ef0e9031"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AA", + Name = "Afar", + NativeName = "Afaraf" + }, + new + { + Id = new Guid("bd4f1638-6017-733d-f696-b8b4d72664d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AB", + Name = "Abkhaz", + NativeName = "аҧсуа бызшәа" + }, + new + { + Id = new Guid("29201cbb-ca65-1924-75a9-0c4d4db43001"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AE", + Name = "Avestan", + NativeName = "avesta" + }, + new + { + Id = new Guid("edd4319b-86f3-24cb-248c-71da624c02f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AF", + Name = "Afrikaans", + NativeName = "Afrikaans" + }, + new + { + Id = new Guid("ef584e3c-03f2-42b0-7139-69d15d21e5a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AK", + Name = "Akan", + NativeName = "Akan" + }, + new + { + Id = new Guid("688af4c8-9d64-ae1c-147f-b8afd54801e3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AM", + Name = "Amharic", + NativeName = "አማርኛ" + }, + new + { + Id = new Guid("d4d5c45a-d3c2-891e-6d7d-75569c3386ac"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AN", + Name = "Aragonese", + NativeName = "aragonés" + }, + new + { + Id = new Guid("a7afb7b1-b26d-4571-1a1f-3fff738ff21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AR", + Name = "Arabic", + NativeName = "اَلْعَرَبِيَّةُ" + }, + new + { + Id = new Guid("538114de-7db0-9242-35e6-324fa7eff44d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AS", + Name = "Assamese", + NativeName = "অসমীয়া" + }, + new + { + Id = new Guid("e43a2010-14fc-63a9-f9d3-0ab2a1d0e52f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AV", + Name = "Avaric", + NativeName = "авар мацӀ" + }, + new + { + Id = new Guid("78c6e8af-fcb4-c783-987c-7e1aca3aed64"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AY", + Name = "Aymara", + NativeName = "aymar aru" + }, + new + { + Id = new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "AZ", + Name = "Azerbaijani", + NativeName = "azərbaycan dili" + }, + new + { + Id = new Guid("a7716d29-6ef6-b775-51c5-97094536329d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BA", + Name = "Bashkir", + NativeName = "башҡорт теле" + }, + new + { + Id = new Guid("0797a7d5-bbc0-2e52-0de8-14a42fc80baa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BE", + Name = "Belarusian", + NativeName = "беларуская мова" + }, + new + { + Id = new Guid("46576b73-c05b-7498-5b07-9bbf59b7645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BG", + Name = "Bulgarian", + NativeName = "български език" + }, + new + { + Id = new Guid("75e4464b-a784-63b8-1ecc-69ee1f09f43f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BI", + Name = "Bislama", + NativeName = "Bislama" + }, + new + { + Id = new Guid("ca2a5560-d4c4-3c87-3090-6f5436310b55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BM", + Name = "Bambara", + NativeName = "bamanankan" + }, + new + { + Id = new Guid("ed6278e0-436c-9fd9-0b9e-44fd424cbd1b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BN", + Name = "Bengali", + NativeName = "বাংলা" + }, + new + { + Id = new Guid("f33ced84-eb43-fb39-ef79-b266e4d4cd94"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BO", + Name = "Tibetan", + NativeName = "བོད་ཡིག" + }, + new + { + Id = new Guid("5283afbb-2744-e930-2c16-c5ea6b0ff7cc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BR", + Name = "Breton", + NativeName = "brezhoneg" + }, + new + { + Id = new Guid("46e88019-c521-57b2-d1c0-c0e2478d3b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "BS", + Name = "Bosnian", + NativeName = "bosanski jezik" + }, + new + { + Id = new Guid("5c0e654b-8547-5d02-ee7b-d65e3c5c5273"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CA", + Name = "Catalan", + NativeName = "Català" + }, + new + { + Id = new Guid("cd5689d6-7a06-73c7-650e-f6f94387fd88"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CE", + Name = "Chechen", + NativeName = "нохчийн мотт" + }, + new + { + Id = new Guid("37c89068-a8e9-87e8-d651-f86fac63673a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CH", + Name = "Chamorro", + NativeName = "Chamoru" + }, + new + { + Id = new Guid("c64288fc-d941-0615-47f9-28e6c294ce26"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CO", + Name = "Corsican", + NativeName = "corsu" + }, + new + { + Id = new Guid("d13935c1-8956-1399-7c4e-0354795cd37b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CR", + Name = "Cree", + NativeName = "ᓀᐦᐃᔭᐍᐏᐣ" + }, + new + { + Id = new Guid("4def223a-9524-596d-cc29-ab7830c590de"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CS", + Name = "Czech", + NativeName = "čeština" + }, + new + { + Id = new Guid("57765d87-2424-2c86-ad9c-1af58ef3127a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CU", + Name = "Old Church Slavonic", + NativeName = "ѩзыкъ словѣньскъ" + }, + new + { + Id = new Guid("17ed5f0f-e091-94ff-0512-ad291bde94d7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CV", + Name = "Chuvash", + NativeName = "чӑваш чӗлхи" + }, + new + { + Id = new Guid("df20d0d7-9fbe-e725-d966-4fdf9f5c9dfb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "CY", + Name = "Welsh", + NativeName = "Cymraeg" + }, + new + { + Id = new Guid("b356a541-1383-3c0a-9afd-6aebae3753cb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DA", + Name = "Danish", + NativeName = "dansk" + }, + new + { + Id = new Guid("46ef1468-86f6-0c99-f4e9-46f966167b05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DE", + Name = "German", + NativeName = "Deutsch" + }, + new + { + Id = new Guid("d8d4f63d-fa65-63dd-a788-de2eec3d24ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DV", + Name = "Divehi", + NativeName = "ދިވެހި" + }, + new + { + Id = new Guid("fee6f04f-c4c1-e3e4-645d-bb6bb703aeb7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "DZ", + Name = "Dzongkha", + NativeName = "རྫོང་ཁ" + }, + new + { + Id = new Guid("2dc643bd-cc6c-eb0c-7314-44123576f0ee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EE", + Name = "Ewe", + NativeName = "Eʋegbe" + }, + new + { + Id = new Guid("b9da7f73-60cd-404c-18fb-1bc5bbfffb38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EL", + Name = "Greek", + NativeName = "Ελληνικά" + }, + new + { + Id = new Guid("094b3769-68b1-6211-ba2d-6bba92d6a167"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EN", + Name = "English", + NativeName = "English" + }, + new + { + Id = new Guid("1da84244-fa39-125e-06dc-3c0cb2342ce9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EO", + Name = "Esperanto", + NativeName = "Esperanto" + }, + new + { + Id = new Guid("414a34ce-2781-8f96-2bd0-7ada86c8cf38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ES", + Name = "Spanish", + NativeName = "Español" + }, + new + { + Id = new Guid("e75515a6-63cf-3612-a3a2-befa0d7048a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ET", + Name = "Estonian", + NativeName = "eesti" + }, + new + { + Id = new Guid("b2a87091-32fb-ba34-a721-bf8b3de5935d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "EU", + Name = "Basque", + NativeName = "euskara" + }, + new + { + Id = new Guid("e9da8997-dee8-0c2d-79d3-05fafc45092e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FA", + Name = "Persian", + NativeName = "فارسی" + }, + new + { + Id = new Guid("51a86a09-0d0b-31c1-90f1-f237db8e29ad"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FF", + Name = "Fula", + NativeName = "Fulfulde" + }, + new + { + Id = new Guid("5a5d9168-081b-1e02-1fbb-cdfa910e526c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FI", + Name = "Finnish", + NativeName = "suomi" + }, + new + { + Id = new Guid("0e2a1681-d852-67ae-7387-0d04be9e7fd3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FJ", + Name = "Fijian", + NativeName = "vosa Vakaviti" + }, + new + { + Id = new Guid("0d4fe6e6-ea1e-d1ce-5134-6c0c1a696a00"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FO", + Name = "Faroese", + NativeName = "føroyskt" + }, + new + { + Id = new Guid("b2261c50-1a57-7f1f-d72d-f8c21593874f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FR", + Name = "French", + NativeName = "Français" + }, + new + { + Id = new Guid("fb429393-f994-0a16-37f9-edc0510fced5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "FY", + Name = "Western Frisian", + NativeName = "Frysk" + }, + new + { + Id = new Guid("4826bc0f-235e-572f-2b1a-21f1c9e05f83"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GA", + Name = "Irish", + NativeName = "Gaeilge" + }, + new + { + Id = new Guid("ff5b4d88-c179-ff0d-6285-cf46ba475d7d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GD", + Name = "Scottish Gaelic", + NativeName = "Gàidhlig" + }, + new + { + Id = new Guid("2f00fe86-a06b-dc95-0ea7-4520d1dec784"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GL", + Name = "Galician", + NativeName = "galego" + }, + new + { + Id = new Guid("3ffe68ca-7350-175b-4e95-0c34f54dc1f4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GN", + Name = "Guaraní", + NativeName = "Avañe'ẽ" + }, + new + { + Id = new Guid("096a8586-9702-6fec-5f6a-6eb3b7b7837f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GU", + Name = "Gujarati", + NativeName = "ગુજરાતી" + }, + new + { + Id = new Guid("849b5e66-dc68-a1ed-6ed3-e315fbd0a0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "GV", + Name = "Manx", + NativeName = "Gaelg" + }, + new + { + Id = new Guid("2e9cb133-68a7-2f3b-49d1-0921cf42dfae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HA", + Name = "Hausa", + NativeName = "هَوُسَ" + }, + new + { + Id = new Guid("d685aa26-aee7-716b-9433-1b3411209f4b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HE", + Name = "Hebrew", + NativeName = "עברית" + }, + new + { + Id = new Guid("54686fcd-3f35-f468-7c9c-93217c5084bc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HI", + Name = "Hindi", + NativeName = "हिन्दी" + }, + new + { + Id = new Guid("87813ec7-4830-e4dc-5ab1-bd599057ede0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HO", + Name = "Hiri Motu", + NativeName = "Hiri Motu" + }, + new + { + Id = new Guid("1f8be615-5746-277e-d82b-47596b5bb922"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HR", + Name = "Croatian", + NativeName = "Hrvatski" + }, + new + { + Id = new Guid("2bebebe4-edaa-9160-5a0c-4d99048bd8d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HT", + Name = "Haitian", + NativeName = "Kreyòl ayisyen" + }, + new + { + Id = new Guid("dcf19e1d-74a6-7b8b-a5ed-76b94a8ac2a7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HU", + Name = "Hungarian", + NativeName = "magyar" + }, + new + { + Id = new Guid("d832c50a-112e-4591-9432-4ada24bc85b2"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HY", + Name = "Armenian", + NativeName = "Հայերեն" + }, + new + { + Id = new Guid("d5bffdfb-6a8e-6d9f-2e59-4ada912acdba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "HZ", + Name = "Herero", + NativeName = "Otjiherero" + }, + new + { + Id = new Guid("7f065da7-4ba4-81ca-5126-dbf606a73907"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IA", + Name = "Interlingua", + NativeName = "Interlingua" + }, + new + { + Id = new Guid("1d974338-decf-08e5-3e62-89e1bbdbb003"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ID", + Name = "Indonesian", + NativeName = "Bahasa Indonesia" + }, + new + { + Id = new Guid("294978f0-2702-d35d-cfc4-e676148aea2e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IE", + Name = "Interlingue", + NativeName = "Interlingue" + }, + new + { + Id = new Guid("caddae27-283a-82b2-9365-76a3d6c49eee"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IG", + Name = "Igbo", + NativeName = "Asụsụ Igbo" + }, + new + { + Id = new Guid("f21f562e-5c35-4806-4efc-416619b5b7f7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "II", + Name = "Nuosu", + NativeName = "ꆈꌠ꒿ Nuosuhxop" + }, + new + { + Id = new Guid("23785991-fef4-e625-4d3b-b6ac364d0fa0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IK", + Name = "Inupiaq", + NativeName = "Iñupiaq" + }, + new + { + Id = new Guid("b8b09512-ea4c-4a61-9331-304f55324ef7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IO", + Name = "Ido", + NativeName = "Ido" + }, + new + { + Id = new Guid("4ee6400d-5534-7c67-1521-870d6b732366"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IS", + Name = "Icelandic", + NativeName = "Íslenska" + }, + new + { + Id = new Guid("7bbf15f4-a907-c0b2-7029-144aafb3c59d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IT", + Name = "Italian", + NativeName = "Italiano" + }, + new + { + Id = new Guid("899392d7-d54f-a1c6-407a-1bada9b85fdd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "IU", + Name = "Inuktitut", + NativeName = "ᐃᓄᒃᑎᑐᑦ" + }, + new + { + Id = new Guid("6857242c-f772-38b5-b5a2-c8e8b9db551f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JA", + Name = "Japanese", + NativeName = "日本語" + }, + new + { + Id = new Guid("e7532b00-3b1b-ff2c-b7c0-26bd7e91af55"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "JV", + Name = "Javanese", + NativeName = "basa Jawa" + }, + new + { + Id = new Guid("9204928b-c569-ef6a-446e-4853aee439b0"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KA", + Name = "Georgian", + NativeName = "ქართული" + }, + new + { + Id = new Guid("0932ed88-c79f-591a-d684-9a77735f947e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KG", + Name = "Kongo", + NativeName = "Kikongo" + }, + new + { + Id = new Guid("914618fd-86f9-827a-91b8-826f0db9e02d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KI", + Name = "Kikuyu", + NativeName = "Gĩkũyũ" + }, + new + { + Id = new Guid("80ecea2c-8969-1929-0d4a-39ed2324abc6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KJ", + Name = "Kwanyama", + NativeName = "Kuanyama" + }, + new + { + Id = new Guid("b6b2351f-4f1e-c92f-0e9a-a915f4cc5fa6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KK", + Name = "Kazakh", + NativeName = "қазақ тілі" + }, + new + { + Id = new Guid("081a5fdb-445a-015a-1e36-f2e5014265ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KL", + Name = "Kalaallisut", + NativeName = "kalaallisut" + }, + new + { + Id = new Guid("5e7a08f2-7d59-bcdb-7ddd-876b87181420"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KM", + Name = "Khmer", + NativeName = "ខេមរភាសា" + }, + new + { + Id = new Guid("fa633273-9866-840d-9739-c6c957901e46"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KN", + Name = "Kannada", + NativeName = "ಕನ್ನಡ" + }, + new + { + Id = new Guid("74f19a84-b1c5-fa2d-8818-2220b80a3056"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KO", + Name = "Korean", + NativeName = "한국어" + }, + new + { + Id = new Guid("7bf934fa-bcf4-80b5-fd7d-ab4cca45c67b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KR", + Name = "Kanuri", + NativeName = "Kanuri" + }, + new + { + Id = new Guid("eace47f6-5499-f4f0-8f97-ed165b681d84"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KS", + Name = "Kashmiri", + NativeName = "कश्मीरी" + }, + new + { + Id = new Guid("7451108d-ad49-940a-d479-4d868b62a7c6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KU", + Name = "Kurdish", + NativeName = "Kurdî" + }, + new + { + Id = new Guid("78b7020d-8b82-3fae-2049-30e490ae1faf"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KV", + Name = "Komi", + NativeName = "коми кыв" + }, + new + { + Id = new Guid("b6f70436-9515-7ef8-af57-aad196503499"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KW", + Name = "Cornish", + NativeName = "Kernewek" + }, + new + { + Id = new Guid("3c5828e0-16a8-79ba-4e5c-9b45065df113"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "KY", + Name = "Kyrgyz", + NativeName = "Кыргызча" + }, + new + { + Id = new Guid("c4754c00-cfa5-aa6f-a9c8-a200457de7a8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LA", + Name = "Latin", + NativeName = "latine" + }, + new + { + Id = new Guid("1e5c0dcc-83e9-f275-c81d-3bc49f88e70c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LB", + Name = "Luxembourgish", + NativeName = "Lëtzebuergesch" + }, + new + { + Id = new Guid("80b770b8-4797-3d62-ef66-1ded7b0da0e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LG", + Name = "Ganda", + NativeName = "Luganda" + }, + new + { + Id = new Guid("9d6e6446-185e-235e-8771-9eb2d19f22e7"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LI", + Name = "Limburgish", + NativeName = "Limburgs" + }, + new + { + Id = new Guid("ca44a869-d3b6-052d-1e1a-ad4e3682a2ed"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LN", + Name = "Lingala", + NativeName = "Lingála" + }, + new + { + Id = new Guid("e9ad0bec-7dee-bd01-9528-1fc74d1d78dd"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LO", + Name = "Lao", + NativeName = "ພາສາລາວ" + }, + new + { + Id = new Guid("52538361-bbdf-fafb-e434-5655fc7451e5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LT", + Name = "Lithuanian", + NativeName = "lietuvių kalba" + }, + new + { + Id = new Guid("70673250-4cc3-3ba1-a42c-6b62ea8ab1d5"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LU", + Name = "Luba-Katanga", + NativeName = "Kiluba" + }, + new + { + Id = new Guid("9205dbfc-60cd-91d9-b0b8-8a18a3755286"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "LV", + Name = "Latvian", + NativeName = "latviešu valoda" + }, + new + { + Id = new Guid("976e496f-ca38-d113-1697-8af2d9a3b159"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MG", + Name = "Malagasy", + NativeName = "fiteny malagasy" + }, + new + { + Id = new Guid("943d2419-2ca6-95f8-9c3b-ed445aea0371"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MH", + Name = "Marshallese", + NativeName = "Kajin M̧ajeļ" + }, + new + { + Id = new Guid("54726f17-03b8-8af3-0359-c42d8fe8459d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MI", + Name = "Māori", + NativeName = "te reo Māori" + }, + new + { + Id = new Guid("aa0f69b2-93aa-ec51-b43b-60145db79e38"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MK", + Name = "Macedonian", + NativeName = "македонски јазик" + }, + new + { + Id = new Guid("c03d71a5-b215-8672-ec0c-dd8fe5c20e05"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ML", + Name = "Malayalam", + NativeName = "മലയാളം" + }, + new + { + Id = new Guid("c522b3d3-74cc-846f-0394-737dff4d2b1a"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MN", + Name = "Mongolian", + NativeName = "Монгол хэл" + }, + new + { + Id = new Guid("74da982f-cf20-e1b4-517b-a040511af23c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MR", + Name = "Marathi", + NativeName = "मराठी" + }, + new + { + Id = new Guid("50e5954d-7cb4-2201-b96c-f2a846ab3ae3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MS", + Name = "Malay", + NativeName = "Bahasa Melayu" + }, + new + { + Id = new Guid("f0219540-8b2c-bd29-4f76-b832de53a56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MT", + Name = "Maltese", + NativeName = "Malti" + }, + new + { + Id = new Guid("d292ea2d-fbb6-7c1e-cb7d-23d552673776"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "MY", + Name = "Burmese", + NativeName = "ဗမာစာ" + }, + new + { + Id = new Guid("0c0fef20-0e8d-98ea-7724-12cea9b3b926"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NA", + Name = "Nauru", + NativeName = "Dorerin Naoero" + }, + new + { + Id = new Guid("4a3aa5a4-473f-45cd-f054-fa0465c476a4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NB", + Name = "Norwegian Bokmål", + NativeName = "Norsk bokmål" + }, + new + { + Id = new Guid("b4292ad3-3ca8-eea5-f3e0-d1983db8f61e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ND", + Name = "Northern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("97cd39d5-1aca-8f10-9f5e-3f611d7606d8"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NE", + Name = "Nepali", + NativeName = "नेपाली" + }, + new + { + Id = new Guid("2e1bd9d8-df06-d773-0eb9-98e274b63b43"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NG", + Name = "Ndonga", + NativeName = "Owambo" + }, + new + { + Id = new Guid("cfff3443-1378-9c7d-9d58-66146d7f29a6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NL", + Name = "Dutch", + NativeName = "Nederlands" + }, + new + { + Id = new Guid("df41c815-40f8-197a-7a8b-e456d43283d9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NN", + Name = "Norwegian Nynorsk", + NativeName = "Norsk nynorsk" + }, + new + { + Id = new Guid("914d7923-3ac5-75e8-c8e2-47d72561e35d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NO", + Name = "Norwegian", + NativeName = "Norsk" + }, + new + { + Id = new Guid("e3bacefb-d79b-1569-a91c-43d7e4f6f230"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NR", + Name = "Southern Ndebele", + NativeName = "isiNdebele" + }, + new + { + Id = new Guid("67729f87-ef47-dd3f-65f7-b0f6df0d6384"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NV", + Name = "Navajo", + NativeName = "Diné bizaad" + }, + new + { + Id = new Guid("720b4e12-b001-8d38-7c07-f43194b9645d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "NY", + Name = "Chichewa", + NativeName = "chiCheŵa" + }, + new + { + Id = new Guid("2b6d383a-9ab6-fcdf-bcfe-a4538faca407"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OC", + Name = "Occitan", + NativeName = "occitan" + }, + new + { + Id = new Guid("9ec46cb5-6c2b-0e22-07c5-eb2fe1b8d2ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OJ", + Name = "Ojibwe", + NativeName = "ᐊᓂᔑᓈᐯᒧᐎᓐ" + }, + new + { + Id = new Guid("6c366974-3672-3a2c-2345-0fda33942304"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OM", + Name = "Oromo", + NativeName = "Afaan Oromoo" + }, + new + { + Id = new Guid("285b9e82-38af-33ab-79fd-0b4f3fd4f2f1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OR", + Name = "Oriya", + NativeName = "ଓଡ଼ିଆ" + }, + new + { + Id = new Guid("2d013d34-b258-8fe9-ef52-dd34e82a4672"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "OS", + Name = "Ossetian", + NativeName = "ирон æвзаг" + }, + new + { + Id = new Guid("7bf4a786-3733-c670-e85f-03ee3caa6ef9"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PA", + Name = "Panjabi", + NativeName = "ਪੰਜਾਬੀ" + }, + new + { + Id = new Guid("d8ef067c-1087-4ff5-8e1f-2291df7ac958"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PI", + Name = "Pāli", + NativeName = "पाऴि" + }, + new + { + Id = new Guid("de503629-2607-b948-e279-0509d8109d0f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PL", + Name = "Polish", + NativeName = "Polski" + }, + new + { + Id = new Guid("d6d31cdd-280a-56bc-24a4-a414028d2b67"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PS", + Name = "Pashto", + NativeName = "پښتو" + }, + new + { + Id = new Guid("2a039b16-2adf-0fb8-3bdf-fbdf14358d9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "PT", + Name = "Portuguese", + NativeName = "Português" + }, + new + { + Id = new Guid("93fb8ace-4156-12d5-218e-64b7d35129b1"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "QU", + Name = "Quechua", + NativeName = "Runa Simi" + }, + new + { + Id = new Guid("136610e1-8115-9cf1-d671-7950c6483495"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RM", + Name = "Romansh", + NativeName = "rumantsch grischun" + }, + new + { + Id = new Guid("7a0725cf-311a-4f59-cff8-ad8b43dd226e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RN", + Name = "Kirundi", + NativeName = "Ikirundi" + }, + new + { + Id = new Guid("51aa4900-30a6-91b7-2728-071542a064ff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RO", + Name = "Romanian", + NativeName = "Română" + }, + new + { + Id = new Guid("58337ef3-3d24-43e9-a440-832306e7fc07"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RU", + Name = "Russian", + NativeName = "Русский" + }, + new + { + Id = new Guid("f5b15ea6-133d-c2c9-7ef9-b0916ea96edb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "RW", + Name = "Kinyarwanda", + NativeName = "Ikinyarwanda" + }, + new + { + Id = new Guid("a8f30b36-4a25-3fb9-c69e-84ce6640d785"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SA", + Name = "Sanskrit", + NativeName = "संस्कृतम्" + }, + new + { + Id = new Guid("2167da32-4f80-d31d-226c-0551970304eb"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SC", + Name = "Sardinian", + NativeName = "sardu" + }, + new + { + Id = new Guid("f0965449-6b15-6c1a-f5cb-ebd2d575c02c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SD", + Name = "Sindhi", + NativeName = "सिन्धी" + }, + new + { + Id = new Guid("0ab731f0-5326-44be-af3a-20aa33ad0f35"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SE", + Name = "Northern Sami", + NativeName = "Davvisámegiella" + }, + new + { + Id = new Guid("3ce3d958-7341-bd79-f294-f2e6907c186c"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SG", + Name = "Sango", + NativeName = "yângâ tî sängö" + }, + new + { + Id = new Guid("357c121b-e28d-1765-e699-cc4ec5ff86fc"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SI", + Name = "Sinhala", + NativeName = "සිංහල" + }, + new + { + Id = new Guid("3252e51a-5bc1-f065-7101-5b34ba493dc4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SK", + Name = "Slovak", + NativeName = "slovenčina" + }, + new + { + Id = new Guid("b0f4bdfa-17dd-9714-4fe8-3c3b1f010ffa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SL", + Name = "Slovenian", + NativeName = "slovenščina" + }, + new + { + Id = new Guid("0a25f96f-5173-2fff-a2f8-c6872393edf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SM", + Name = "Samoan", + NativeName = "gagana fa'a Samoa" + }, + new + { + Id = new Guid("3175ac19-c801-0b87-8e66-7480a40dcf1e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SN", + Name = "Shona", + NativeName = "chiShona" + }, + new + { + Id = new Guid("802c05db-3866-545d-dc1a-a02c83ea6cf6"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SO", + Name = "Somali", + NativeName = "Soomaaliga" + }, + new + { + Id = new Guid("fb1cce84-4a6c-1834-0ff2-6df002e3d56f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SQ", + Name = "Albanian", + NativeName = "Shqip" + }, + new + { + Id = new Guid("61ba1844-4d33-84b4-dbac-70718aa91d59"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SR", + Name = "Serbian", + NativeName = "српски језик" + }, + new + { + Id = new Guid("6aac6f0e-d13a-a629-4c2b-9d6eaf6680e4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SS", + Name = "Swati", + NativeName = "SiSwati" + }, + new + { + Id = new Guid("766c1ebb-78c1-bada-37fb-c45d1bd4baff"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ST", + Name = "Southern Sotho", + NativeName = "Sesotho" + }, + new + { + Id = new Guid("ee1ace14-e945-4767-85ec-3d74be8b516b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SU", + Name = "Sundanese", + NativeName = "Basa Sunda" + }, + new + { + Id = new Guid("4d8bcda4-5598-16cd-b379-97eb7a5e1c29"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SV", + Name = "Swedish", + NativeName = "Svenska" + }, + new + { + Id = new Guid("5f002f07-f2c3-9fa4-2e29-225d116c10a3"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "SW", + Name = "Swahili", + NativeName = "Kiswahili" + }, + new + { + Id = new Guid("8bc44f03-84a5-2afc-8b0b-40c727e4ce36"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TA", + Name = "Tamil", + NativeName = "தமிழ்" + }, + new + { + Id = new Guid("3bf5a74a-6d12-e971-16bc-c75e487f2615"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TE", + Name = "Telugu", + NativeName = "తెలుగు" + }, + new + { + Id = new Guid("9dacf00b-7d0a-d744-cc60-e5fa66371e9d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TG", + Name = "Tajik", + NativeName = "тоҷикӣ" + }, + new + { + Id = new Guid("84d58b3d-d131-1506-0792-1b3228b6f71f"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TH", + Name = "Thai", + NativeName = "ไทย" + }, + new + { + Id = new Guid("596e8283-10ce-d81d-2e6f-400fa259d717"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TI", + Name = "Tigrinya", + NativeName = "ትግርኛ" + }, + new + { + Id = new Guid("11765ad0-30f2-bab8-b616-20f88b28b21e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TK", + Name = "Turkmen", + NativeName = "Türkmençe" + }, + new + { + Id = new Guid("fb9a713c-2de1-882a-64b7-0e8fef5d2f7e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TL", + Name = "Tagalog", + NativeName = "Wikang Tagalog" + }, + new + { + Id = new Guid("06f8ad57-7133-9a5e-5a83-53052012b014"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TN", + Name = "Tswana", + NativeName = "Setswana" + }, + new + { + Id = new Guid("9e7dbdc3-2c8b-e8ae-082b-e02695f8268e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TO", + Name = "Tonga", + NativeName = "faka Tonga" + }, + new + { + Id = new Guid("f39cca22-449e-9866-3a65-465a5510483e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TR", + Name = "Turkish", + NativeName = "Türkçe" + }, + new + { + Id = new Guid("6200b376-9eae-d01b-de52-8674aaf5b013"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TS", + Name = "Tsonga", + NativeName = "Xitsonga" + }, + new + { + Id = new Guid("95467997-f989-f456-34b7-0b578302dcba"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TT", + Name = "Tatar", + NativeName = "татар теле" + }, + new + { + Id = new Guid("875060ca-73f6-af3b-d844-1b1416ce4583"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TW", + Name = "Twi", + NativeName = "Twi" + }, + new + { + Id = new Guid("2299a74f-3ebc-f022-da1a-44ae59335b3b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "TY", + Name = "Tahitian", + NativeName = "Reo Tahiti" + }, + new + { + Id = new Guid("3e2cccbe-1615-c707-a97b-421a799b2559"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UG", + Name = "Uyghur", + NativeName = "ئۇيغۇرچە‎" + }, + new + { + Id = new Guid("de29d5e7-2ecf-a4ff-5e40-5e83edd0d9b4"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UK", + Name = "Ukrainian", + NativeName = "Українська" + }, + new + { + Id = new Guid("f1f09549-a9bb-da4a-9b98-8655a01235aa"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UR", + Name = "Urdu", + NativeName = "اردو" + }, + new + { + Id = new Guid("357369e3-85a8-86f7-91c7-349772ae7744"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "UZ", + Name = "Uzbek", + NativeName = "Ўзбек" + }, + new + { + Id = new Guid("52d9992c-19bd-82b4-9188-11dabcac6171"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VE", + Name = "Venda", + NativeName = "Tshivenḓa" + }, + new + { + Id = new Guid("e1947bdc-ff2c-d2c1-3c55-f1f9bf778578"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VI", + Name = "Vietnamese", + NativeName = "Tiếng Việt" + }, + new + { + Id = new Guid("c2254fd9-159e-4064-0fbf-a7969cba06ec"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "VO", + Name = "Volapük", + NativeName = "Volapük" + }, + new + { + Id = new Guid("629b68d8-1d71-d3ce-f13e-45048ffff017"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WA", + Name = "Walloon", + NativeName = "walon" + }, + new + { + Id = new Guid("ca6bfadf-4e87-0692-a6b3-20ea6a51555d"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "WO", + Name = "Wolof", + NativeName = "Wollof" + }, + new + { + Id = new Guid("0b9b4368-7ceb-e519-153d-2c58c983852b"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "XH", + Name = "Xhosa", + NativeName = "isiXhosa" + }, + new + { + Id = new Guid("13016d0c-fbf0-9503-12f2-e0f8d27394ae"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YI", + Name = "Yiddish", + NativeName = "ייִדיש" + }, + new + { + Id = new Guid("d55a9eb2-48fc-2719-47bf-99e902c28e80"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "YO", + Name = "Yoruba", + NativeName = "Yorùbá" + }, + new + { + Id = new Guid("ebf38b9a-6fbe-6e82-3977-2c4763bea072"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZA", + Name = "Zhuang", + NativeName = "Saɯ cueŋƅ" + }, + new + { + Id = new Guid("0ce6f5e0-0789-fa0e-b4b5-23a5b1f5e257"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZH", + Name = "Chinese", + NativeName = "中文" + }, + new + { + Id = new Guid("2c7b808d-7786-2deb-5318-56f7c238520e"), + CreatedOn = new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + Iso1 = "ZU", + Name = "Zulu", + NativeName = "isiZulu" + }); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormsVersion") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("NgoId"); + + b.ToTable("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.HasIndex("ObserverId"); + + b.HasIndex("ElectionRoundId", "Id") + .IsUnique(); + + b.ToTable("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("NgoId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.HasIndex("NgoId"); + + b.ToTable("NgoAdmins", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Ngos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuestionId") + .HasColumnType("uuid"); + + b.Property("Text") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("Notes", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.HasKey("MonitoringObserverId", "NotificationId"); + + b.HasIndex("NotificationId"); + + b.ToTable("MonitoringObserverNotification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("SenderId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("SenderId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationStubAggregate.NotificationStub", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenProcessed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SerializedData") + .IsRequired() + .HasColumnType("text"); + + b.Property("StubType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("NotificationStubs"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ObserverId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ObserverId"); + + b.ToTable("NotificationTokens"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("Observers", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("FilePath") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GuideType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UploadedFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("ObserversGuides"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(2024) + .HasColumnType("character varying(2024)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Level1") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level2") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level3") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level4") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Level5") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Tags") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("PollingStations"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Answers") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArrivalTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Breaks") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'[]'::JSONB"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DepartureTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MinutesMonitoring") + .HasColumnType("double precision"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("NumberOfFlaggedAnswers") + .HasColumnType("integer"); + + b.Property("NumberOfQuestionsAnswered") + .HasColumnType("integer"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("PollingStationInformationFormId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.HasIndex("PollingStationInformationFormId"); + + b.HasIndex("ElectionRoundId", "PollingStationId", "MonitoringObserverId", "PollingStationInformationFormId") + .IsUnique(); + + b.ToTable("PollingStationInformation", (string)null); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLanguage") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FormType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("PSI"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("LanguagesTranslationStatus") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NumberOfQuestions") + .HasColumnType("integer"); + + b.Property("Questions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Published"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId") + .IsUnique(); + + b.ToTable("PollingStationInformationForms"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FollowUpStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("NotApplicable"); + + b.Property("IncidentCategory") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Other"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("PollingStationDetails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PollingStationId") + .HasColumnType("uuid"); + + b.Property("QuickReportLocationType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("Id"); + + b.HasIndex("MonitoringObserverId"); + + b.HasIndex("PollingStationId"); + + b.ToTable("QuickReports"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("MonitoringObserverId") + .HasColumnType("uuid"); + + b.Property("QuickReportId") + .HasColumnType("uuid"); + + b.Property("UploadedFileName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringObserverId"); + + b.ToTable("QuickReportAttachments"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", b => + { + b.OwnsOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.UserPreferences", "Preferences", b1 => + { + b1.Property("ApplicationUserId") + .HasColumnType("uuid"); + + b1.Property("LanguageCode") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("ApplicationUserId"); + + b1.ToTable("AspNetUsers"); + + b1.ToJson("Preferences"); + + b1.WithOwner() + .HasForeignKey("ApplicationUserId"); + }); + + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.AttachmentAggregate.Attachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenNotificationAggregate.CitizenNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.LocationAggregate.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate.CitizenReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Attachments") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate.CitizenReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", "CitizenReport") + .WithMany("Notes") + .HasForeignKey("CitizenReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CitizenReport"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgoForCitizenReporting") + .WithMany() + .HasForeignKey("MonitoringNgoForCitizenReportingId"); + + b.Navigation("Country"); + + b.Navigation("MonitoringNgoForCitizenReporting"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ExportedDataAggregate.ExportedData", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FeedbackAggregate.Feedback", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormAggregate.Form", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.FormSubmissionAggregate.FormSubmission", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate.IncidentReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Attachments") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate.IncidentReportNote", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", "IncidentReport") + .WithMany("Notes") + .HasForeignKey("IncidentReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("IncidentReport"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.LocationAggregate.Location", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany("MonitoringNgos") + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany() + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("MonitoringObservers") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", "Observer") + .WithMany("MonitoringObservers") + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + + b.Navigation("Observer"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", "Ngo") + .WithMany("Admins") + .HasForeignKey("NgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + + b.Navigation("Ngo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NoteAggregate.Note", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Form"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.MonitoringObserverNotification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringObserver"); + + b.Navigation("Notification"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationAggregate.Notification", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.NgoAdminAggregate.NgoAdmin", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NotificationTokenAggregate.NotificationToken", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", null) + .WithMany() + .HasForeignKey("ObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ApplicationUserAggregate.ApplicationUser", "ApplicationUser") + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicationUser"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoAggregate.PollingStationInformation", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "PollingStationInformationForm") + .WithMany() + .HasForeignKey("PollingStationInformationFormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + + b.Navigation("PollingStationInformationForm"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithOne() + .HasForeignKey("Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate.PollingStationInformationForm", "ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAggregate.QuickReport", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.PollingStationAggregate.PollingStation", "PollingStation") + .WithMany() + .HasForeignKey("PollingStationId"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + + b.Navigation("PollingStation"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate.QuickReportAttachment", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringObserverAggregate.MonitoringObserver", "MonitoringObserver") + .WithMany() + .HasForeignKey("MonitoringObserverId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringObserver"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.IncidentReportAggregate.IncidentReport", b => + { + b.Navigation("Attachments"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + b.Navigation("Memberships"); + + b.Navigation("MonitoringObservers"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.NgoAggregate.Ngo", b => + { + b.Navigation("Admins"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ObserverAggregate.Observer", b => + { + b.Navigation("MonitoringObservers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs new file mode 100644 index 000000000..4cceaf01f --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20241112130734_AddDisplayNameInAspNetUsers.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddDisplayNameInAspNetUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DisplayName", + table: "AspNetUsers", + type: "text", + nullable: false, + computedColumnSql: "\"FirstName\" || ' ' || \"LastName\"", + stored: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DisplayName", + table: "AspNetUsers"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs index 6990db336..691b23682 100644 --- a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs +++ b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs @@ -189,6 +189,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsConcurrencyToken() .HasColumnType("text"); + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("\"FirstName\" || ' ' || \"LastName\"", true); + b.Property("Email") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -652,6 +658,90 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CitizenReportNotes"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("LeaderId"); + + b.ToTable("Coalitions"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("FormId") + .HasColumnType("uuid"); + + b.HasKey("CoalitionId", "MonitoringNgoId", "FormId"); + + b.HasIndex("FormId"); + + b.HasIndex("MonitoringNgoId"); + + b.ToTable("CoalitionFormAccess"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.Property("MonitoringNgoId") + .HasColumnType("uuid"); + + b.Property("CoalitionId") + .HasColumnType("uuid"); + + b.Property("ElectionRoundId") + .HasColumnType("uuid"); + + b.HasKey("MonitoringNgoId", "CoalitionId"); + + b.HasIndex("CoalitionId"); + + b.HasIndex("ElectionRoundId"); + + b.HasIndex("MonitoringNgoId", "ElectionRoundId") + .IsUnique(); + + b.HasIndex("MonitoringNgoId", "CoalitionId", "ElectionRoundId") + .IsUnique(); + + b.ToTable("CoalitionMemberships"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CountryAggregate.Country", b => { b.Property("Id") @@ -6290,6 +6380,79 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Form"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "Leader") + .WithMany() + .HasForeignKey("LeaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ElectionRound"); + + b.Navigation("Leader"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionFormAccess", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("FormAccess") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.FormAggregate.Form", "Form") + .WithMany() + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany() + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("Form"); + + b.Navigation("MonitoringNgo"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.CoalitionMembership", b => + { + b.HasOne("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", "Coalition") + .WithMany("Memberships") + .HasForeignKey("CoalitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") + .WithMany() + .HasForeignKey("ElectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", "MonitoringNgo") + .WithMany("Memberships") + .HasForeignKey("MonitoringNgoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Coalition"); + + b.Navigation("ElectionRound"); + + b.Navigation("MonitoringNgo"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => { b.HasOne("Vote.Monitor.Domain.Entities.CountryAggregate.Country", "Country") @@ -6766,6 +6929,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Notes"); }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CoalitionAggregate.Coalition", b => + { + b.Navigation("FormAccess"); + + b.Navigation("Memberships"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", b => { b.Navigation("MonitoringNgos"); @@ -6780,6 +6950,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => { + b.Navigation("Memberships"); + b.Navigation("MonitoringObservers"); }); diff --git a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs index b208ebbb8..3f450352d 100644 --- a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs +++ b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeeder.cs @@ -18,8 +18,7 @@ public async Task SeedAsync() if (await userManager.FindByEmailAsync(seedOption.Email) is not ApplicationUser adminUser) { - adminUser = ApplicationUser.CreatePlatformAdmin(seedOption.FirstName, seedOption.LastName, seedOption.Email, - seedOption.PhoneNumber, seedOption.Password); + adminUser = ApplicationUser.CreatePlatformAdmin(seedOption.FirstName, seedOption.LastName, seedOption.Email, seedOption.Password); logger.LogInformation("Seeding PlatformAdmin {user}", seedOption.Email); diff --git a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs index 96209c41d..5270bc0c0 100644 --- a/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs +++ b/api/src/Vote.Monitor.Domain/Seeders/PlatformAdminSeederSettings.cs @@ -5,7 +5,5 @@ public class PlatformAdminSeederSettings public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } - public string PhoneNumber { get; set; } public string Password { get; set; } - } diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index 2ce125b7f..fd6365324 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -7,11 +7,10 @@ using Vote.Monitor.Domain.Entities.CitizenReportAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate; using Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.ExportedDataAggregate; using Vote.Monitor.Domain.Entities.FeedbackAggregate; -using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate; using Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate; @@ -86,6 +85,9 @@ public VoteMonitorContext(DbContextOptions options, public DbSet IncidentReports { get; set; } public DbSet IncidentReportNotes { get; set; } public DbSet IncidentReportAttachments { get; set; } + public DbSet Coalitions { get; set; } + public DbSet CoalitionMemberships { get; set; } + public DbSet CoalitionFormAccess { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -165,6 +167,10 @@ protected override void OnModelCreating(ModelBuilder builder) builder.ApplyConfiguration(new IncidentReportAttachmentConfiguration()); builder.ApplyConfiguration(new LocationConfiguration()); + + builder.ApplyConfiguration(new CoalitionConfiguration()); + builder.ApplyConfiguration(new CoalitionMembershipConfiguration()); + builder.ApplyConfiguration(new CoalitionFormAccessConfiguration()); } protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) @@ -178,4 +184,4 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) optionsBuilder.AddInterceptors(new AuditTrailInterceptor(_serializerService, _currentUserProvider, _timeProvider)); } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj b/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj index eb6d086e9..7018ae2e4 100644 --- a/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj +++ b/api/src/Vote.Monitor.Module.Notifications/Vote.Monitor.Module.Notifications.csproj @@ -8,6 +8,8 @@ + + diff --git a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs index 84fbf8199..6ca09ce69 100644 --- a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs +++ b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/CreateMonitoringNgoView.cs @@ -13,7 +13,7 @@ internal MonitoringNgoView ArchivedElectionRound() NgoId = Guid.NewGuid(), NgoStatus = NgoStatus.Activated, MonitoringNgoId = Guid.NewGuid(), - MonitoringNgoStatus = MonitoringNgoStatus.Active, + MonitoringNgoStatus = MonitoringNgoStatus.Active }; } internal MonitoringNgoView DeactivatedNgo() @@ -25,7 +25,7 @@ internal MonitoringNgoView DeactivatedNgo() NgoId = Guid.NewGuid(), NgoStatus = NgoStatus.Deactivated, MonitoringNgoId = Guid.NewGuid(), - MonitoringNgoStatus = MonitoringNgoStatus.Active, + MonitoringNgoStatus = MonitoringNgoStatus.Active }; } internal MonitoringNgoView SuspendedMonitoringNgo() diff --git a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs index 53a833a00..583895e3e 100644 --- a/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs +++ b/api/tests/Authorization.Policies.UnitTests/RequirementsHandlers/NgoAdminAuthorizationHandlerTests.cs @@ -74,7 +74,7 @@ public async Task HandleRequirementAsync_NgoIsDeactivated_Failure() NgoId = _ngoId, NgoStatus = NgoStatus.Deactivated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Active, + UserStatus = UserStatus.Active }); // Act @@ -99,7 +99,7 @@ public async Task HandleRequirementAsync_UserIsDeactivated_Failure() NgoId = _ngoId, NgoStatus = NgoStatus.Activated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Deactivated, + UserStatus = UserStatus.Deactivated }); // Act @@ -124,7 +124,7 @@ public async Task HandleRequirementAsync_ValidNgoAdmin_Success() NgoId = _ngoId, NgoStatus = NgoStatus.Activated, NgoAdminId = _ngoAdminId, - UserStatus = UserStatus.Active, + UserStatus = UserStatus.Active }); // Act diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs index 8c7e7c0f4..4cbc87d3e 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/CreateEndpointTests.cs @@ -173,7 +173,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() PollingStationId = pollingStationId, FormId = formId, QuestionId = questionId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -208,7 +208,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() PollingStationId = pollingStationId, FormId = formId, QuestionId = questionId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs index b7c15ada7..d19829ec9 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -75,7 +75,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -109,7 +109,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() var request = new Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index a264c547e..6a2bc135a 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -82,7 +82,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -117,7 +117,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 63e50a8f8..69d9a0e38 100644 --- a/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -59,7 +59,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -88,7 +88,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -123,7 +123,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs index d2f1be59b..6e44fdffd 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs @@ -26,7 +26,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new Delete.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = _validator.TestValidate(model); diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs index 9cd5f773c..d82cc83a9 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs @@ -38,7 +38,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new GetById.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index 07f40015b..c7abee55e 100644 --- a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -58,7 +58,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 36db172df..a32706e1c 100644 --- a/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -36,7 +36,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = citizenReportAttachment.ElectionRoundId, CitizenReportId = citizenReportAttachment.CitizenReportId, - FormId = citizenReportAttachment.FormId, + FormId = citizenReportAttachment.FormId }; var result = await _endpoint.ExecuteAsync(request, default); @@ -64,7 +64,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs index ac0154ad3..3460fc3ac 100644 --- a/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -33,7 +33,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() var request = new List.Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 29c4c454b..54daa5710 100644 --- a/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.CitizenReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -37,7 +37,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new List.Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs index 281d0a9b9..00da9e4f0 100644 --- a/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.CitizenReports.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -35,7 +35,7 @@ public async Task ShouldReturnNotFound_WhenLocationNotFound() var request = new Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -63,7 +63,7 @@ public async Task ShouldReturnNotFound_WhenFormNotFound() var request = new Request { ElectionRoundId = Guid.NewGuid(), - CitizenReportId = Guid.NewGuid(), + CitizenReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 986927a3f..4c8833fa3 100644 --- a/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.CitizenReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -95,7 +95,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormId = Guid.NewGuid(), ElectionRoundId = Guid.NewGuid(), CitizenReportId = Guid.NewGuid(), - LocationId = Guid.NewGuid(), + LocationId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs index 68481192b..6c6d91493 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/ListMyEndpointTests.cs @@ -34,7 +34,7 @@ public async Task ShouldReturnNotFound_WhenUserIsNotAuthorized() var request = new ListMy.Request { ElectionRoundId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs index a5c1cb4dc..4085ee50c 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; using Vote.Monitor.Domain.Entities.FormAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; @@ -11,6 +12,7 @@ public class UpsertEndpointTests private readonly IReadRepository _pollingStationRepository; private readonly IReadRepository _monitoringObserverRepository; private readonly IReadRepository _formRepository; + private readonly IReadRepository _coalitionRepository; private readonly IAuthorizationService _authorizationService; private readonly Upsert.Endpoint _endpoint; @@ -20,12 +22,14 @@ public UpsertEndpointTests() _repository = Substitute.For>(); _pollingStationRepository = Substitute.For>(); _monitoringObserverRepository = Substitute.For>(); + _coalitionRepository = Substitute.For>(); _formRepository = Substitute.For>(); _authorizationService = Substitute.For(); - + _endpoint = Factory.Create(_repository, _pollingStationRepository, _monitoringObserverRepository, + _coalitionRepository, _formRepository, _authorizationService); @@ -65,7 +69,7 @@ public async Task ShouldReturnNotFound_WhenFormSubmissionFormNotFound() { // Arrange _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .ReturnsNull(); // Act @@ -91,7 +95,7 @@ public async Task ShouldUpdateFormSubmission_WhenFormSubmissionExists() // Arrange var form = new FormAggregateFaker(status: FormStatus.Published).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); var formSubmission = new FormSubmissionFaker().Generate(); @@ -202,7 +206,7 @@ public async Task ShouldThrow_WhenFormIsDrafted() // Arrange var form = new FormAggregateFaker().Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); var request = new Upsert.Request(); @@ -227,7 +231,7 @@ public async Task ShouldThrow_WhenInvalidAnswersReceived() var formSubmission = new FormAggregateFaker(electionRound: electionRound).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(formSubmission); _repository.FirstOrDefaultAsync(Arg.Any()) @@ -277,7 +281,7 @@ public async Task ShouldCreateFormSubmission_WhenNotExists() var form = new FormAggregateFaker(electionRound, status: FormStatus.Published).Generate(); _formRepository - .FirstOrDefaultAsync(Arg.Any()) + .FirstOrDefaultAsync(Arg.Any()) .Returns(form); _repository.FirstOrDefaultAsync(Arg.Any()) @@ -324,4 +328,4 @@ await _repository .Which .Result.Should().BeOfType>(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index 8d5c6dffd..fabe39607 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -66,7 +66,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs index f3ca85322..2b104e09d 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/FormSubmissionsAggregateFilterValidatorTests.cs @@ -29,7 +29,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new FormSubmissionsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs index 7185bc2bd..5ec28c461 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/GetByIdValidatorTests.cs @@ -26,7 +26,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new GetById.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs index d05ef9730..6dac4beee 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs @@ -26,7 +26,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new ListByObserver.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -43,7 +43,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new ListByObserver.Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs index c50978735..b406bb820 100644 --- a/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.Form.Submissions.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -92,7 +92,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormId = Guid.NewGuid(), ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs index c80999970..3831c5233 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/CreateValidatorTests.cs @@ -219,7 +219,7 @@ public void Validation_ShouldPass_When_ValidRequest() Code = "A code", FormType = FormType.ClosingAndCounting, Name = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First(), - Description = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First(), + Description = TranslatedStringTestData.ValidPartiallyTranslatedTestData.First() }; // Act diff --git a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs index 3e71d8f26..3aebc59d0 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/ValidatorTests/UpdateValidatorTests.cs @@ -255,7 +255,7 @@ public void Validation_ShouldPass_When_ValidRequest() Code = "c!", Questions = [], Name = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate(), - Description = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate(), + Description = new TranslatedStringFaker([LanguagesList.EN.Iso1]).Generate() }; // Act diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs index 6ce5ce628..942739719 100644 --- a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromFormRequestValidatorTests.cs @@ -143,7 +143,7 @@ public void Validation_ShouldPass_When_ValidRequest() FormElectionRoundId = Guid.NewGuid(), FormId = Guid.NewGuid(), Languages = [LanguagesList.EN.Iso1, LanguagesList.RO.Iso1], - DefaultLanguage = LanguagesList.EN.Iso1, + DefaultLanguage = LanguagesList.EN.Iso1 }; // Act diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs index 0ebce59db..350ab41f1 100644 --- a/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/FromTemplateRequestValidatorTests.cs @@ -129,7 +129,7 @@ public void Validation_ShouldPass_When_ValidRequest() NgoId = Guid.NewGuid(), TemplateId = Guid.NewGuid(), Languages = [LanguagesList.EN.Iso1, LanguagesList.RO.Iso1], - DefaultLanguage = LanguagesList.EN.Iso1, + DefaultLanguage = LanguagesList.EN.Iso1 }; // Act diff --git a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs index cafac1f3b..c9ca7e1cf 100644 --- a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/GetEndpointTests.cs @@ -53,7 +53,7 @@ public async Task ShouldReturnNotFound_WhenAttachmentDoesNotExist() { ElectionRoundId = Guid.NewGuid(), IncidentReportId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs index 79f823b8b..e3668dfa3 100644 --- a/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Attachments.UnitTests/Endpoints/ListEndpointTests.cs @@ -31,7 +31,7 @@ public async Task ShouldReturnOkWithAttachmentModel_WhenAllIdsExist() { ElectionRoundId = incidentReportAttachment.ElectionRoundId, IncidentReportId = incidentReportAttachment.IncidentReportId, - FormId = incidentReportAttachment.FormId, + FormId = incidentReportAttachment.FormId }; var result = await _endpoint.ExecuteAsync(request, default); @@ -59,7 +59,7 @@ public async Task ShouldReturnEmptyList_WhenAttachmentsDoNotExist() { ElectionRoundId = Guid.NewGuid(), IncidentReportId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs index 9cf798001..60f260782 100644 --- a/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.IncidentReports.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -30,7 +30,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() var request = new Request { ElectionRoundId = Guid.NewGuid(), - IncidentReportId = Guid.NewGuid(), + IncidentReportId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index c0540d519..0b67b0369 100644 --- a/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -39,7 +39,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new Request { ElectionRoundId = Guid.NewGuid(), - IncidentReportId = Guid.NewGuid(), + IncidentReportId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs index cc2e780fe..b240c0144 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/GetByIdValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs index e34b517a2..5133a2e3f 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/IncidentReportsAggregateFilterValidatorTests.cs @@ -31,7 +31,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new IncidentReportsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs index 3e36a68d9..05c923615 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListByObserverValidatorTests.cs @@ -29,7 +29,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new ListByObserver.Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -46,7 +46,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new ListByObserver.Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs index 8a76c0e98..9c06cfcd4 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListEntriesValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -47,7 +47,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs index 2b4a8e04b..271f5a6da 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/ListFormsOverviewValidatorTests.cs @@ -31,7 +31,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new IncidentReportsAggregateFilter { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -48,7 +48,7 @@ public void Should_Not_Have_Error_When_All_Fields_Are_Valid() var request = new IncidentReportsAggregateFilter { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs index 9209d6018..9b1dbf734 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpdateStatusValidatorTests.cs @@ -30,7 +30,7 @@ public void Should_Have_Error_When_NgoId_Is_Empty() // Arrange var request = new Request { - NgoId = Guid.Empty, + NgoId = Guid.Empty }; // Act @@ -46,7 +46,7 @@ public void Should_Have_Error_When_Id_Is_Empty() // Arrange var request = new Request { - Id = Guid.Empty, + Id = Guid.Empty }; // Act diff --git a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 68fc3f225..67bf1aebf 100644 --- a/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.IncidentReports.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -158,7 +158,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs b/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs index 2136a66f0..19335d2e4 100644 --- a/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs +++ b/api/tests/Feature.Locations.UnitTests/ValidatorTests/GetRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldFail_When_Id_Empty() // Arrange var request = new Get.Request { - Id = Guid.Empty, + Id = Guid.Empty }; // Act diff --git a/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 3d7d26356..6c5ea8706 100644 --- a/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.Locations.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -15,7 +15,7 @@ public void Validation_ShouldPass_When_PageSize_ValidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -36,7 +36,7 @@ public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -57,7 +57,7 @@ public void Validation_ShouldPass_When_PageNumber_ValidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act @@ -77,7 +77,7 @@ public void Validation_ShouldFail_When_PageNumber_InvalidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs index f5b2a8e6e..4ca1aab0f 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ActivateValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs index 895c18c68..3ad052ae2 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AddObserverRequestValidatorTests.cs @@ -49,9 +49,9 @@ public void Validation_ShouldPass_When_ValidRequest() Tags = ["tag1", "tag2"], FirstName = "firstName", LastName = "lastName", - PhoneNumber = "phoneNumber", + PhoneNumber = "phoneNumber" } - ], + ] }; // Act @@ -60,4 +60,4 @@ public void Validation_ShouldPass_When_ValidRequest() // Assert result.ShouldNotHaveAnyValidationErrors(); } -} \ No newline at end of file +} diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs index 9b339a294..e4e0aec78 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/AssignObserverRequestValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs index 19b0fa3d4..786f66011 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ClearTagsValidatorTags.cs @@ -51,7 +51,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -71,7 +71,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs index 35ac6175d..ab70433d1 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/GetValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs index 7cad66dcf..64fbf41cb 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/ListValidatorTests.cs @@ -42,7 +42,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs index c7f03498f..0535410a9 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverRequestValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs index 9851f86c2..d7ac00a21 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/RemoveObserverValidatorTests.cs @@ -54,7 +54,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs index 77daf8f8b..c95b51b96 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/SuspendValidatorTests.cs @@ -51,7 +51,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), NgoId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs index 434c2f480..87406217e 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/TagValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -86,7 +86,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; @@ -106,7 +106,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ], Tags = ["a tag"] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs index 84abbd2af..e6322cb12 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UntagValidatorTests.cs @@ -53,7 +53,7 @@ public void Validation_ShouldFail_When_MonitoringObserverIds_Contain_Empty() { MonitoringObserverIds = [ Guid.NewGuid(), - Guid.Empty, + Guid.Empty ] }; @@ -106,7 +106,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), MonitoringNgoId = Guid.NewGuid(), MonitoringObserverIds = [ - Guid.NewGuid(), + Guid.NewGuid() ], Tags = ["a Untag"] }; diff --git a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs index 13ee314dc..612a6b9ab 100644 --- a/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs +++ b/api/tests/Feature.MonitoringObservers.UnitTests/ValidatorTests/UpdateValidatorTests.cs @@ -70,7 +70,7 @@ public void Validation_ShouldFail_When_Tags_Contain_Empty(string emptyTag) { Tags = [ "a tag", - emptyTag, + emptyTag ] }; diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs index a13588a2f..085de06f8 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -46,7 +46,7 @@ public async Task ShouldReturnNoContent_WhenAllIdsExist() { ElectionRoundId = fakeNote.ElectionRoundId, ObserverId = fakeMonitoringObserver.ObserverId, - Id = fakeNote.Id, + Id = fakeNote.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -106,7 +106,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, ObserverId = fakeMonitoringObserver.Id, - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs index f3f78fa70..50631b10c 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/ListEndpointTests.cs @@ -50,7 +50,7 @@ public async Task ShouldReturnOkWithNoteModelList_WhenAllIdsExist() ElectionRoundId = fakeElectionRound.Id, PollingStationId = Guid.NewGuid(), FormId = Guid.NewGuid(), - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -78,7 +78,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = fakeElectionRound.Id, PollingStationId = Guid.NewGuid(), - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs index 170c3d0df..630bc07e3 100644 --- a/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.Notes.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -79,7 +79,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() ElectionRoundId = fakeElectionRound.Id, PollingStationId = pollingStationId, ObserverId = fakeMonitoringObserver.Id, - Text = noteText, + Text = noteText }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index e56facdf9..6306b8cd1 100644 --- a/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.Notes.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -67,7 +67,7 @@ public void Validation_ShouldPass_When_ValidRequest() ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), ObserverId = Guid.NewGuid(), - FormId = Guid.NewGuid(), + FormId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs index 9c64abd95..263562ec3 100644 --- a/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs +++ b/api/tests/Feature.ObserverGuide.UnitTests/Validators/DeleteValidatorTests.cs @@ -28,7 +28,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new Delete.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; var result = _validator.TestValidate(model); diff --git a/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs b/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs index bd0f1be6d..b04b357bd 100644 --- a/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs +++ b/api/tests/Feature.ObserverGuide.UnitTests/Validators/GetByIdValidatorTests.cs @@ -40,7 +40,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new GetById.Request { ElectionRoundId = Guid.NewGuid(), - Id = Guid.NewGuid(), + Id = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs index 4537a6eec..e1b33b128 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -35,7 +35,7 @@ public async Task Should_DeletePollingStationInformationForm_And_ReturnNoContent // Act var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -59,7 +59,7 @@ public async Task ShouldReturnNotFound_WhenPollingStationInformationFormNotFound // Act var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs index ddd79aece..1409f64b6 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/GetEndpointTests.cs @@ -34,7 +34,7 @@ public async Task Should_GetPollingStationInformationForm_And_ReturnNoContent_Wh // Act var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); @@ -57,7 +57,7 @@ public async Task ShouldReturnNotFound_WhenPollingStationInformationFormNotFound // Act var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs index 08e929317..3a3419905 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/Endpoints/UpsertEndpointTests.cs @@ -79,7 +79,7 @@ public async Task ShouldReturnNotFound_WhenElectionRoundNotExists() var request = new Upsert.Request { - ElectionRoundId = electionRoundId, + ElectionRoundId = electionRoundId }; _electionRoundRepository diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index d5925c4ae..1a54d1996 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldPass_When_ValidRequest() // Arrange var request = new Delete.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs index f94d159d3..4a54d391a 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/GetRequestValidatorTests.cs @@ -26,7 +26,7 @@ public void Validation_ShouldPass_When_ValidRequest() // Arrange var request = new Get.Request { - ElectionRoundId = Guid.NewGuid(), + ElectionRoundId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 29acc2af7..867e79862 100644 --- a/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.Form.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -130,7 +130,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), DefaultLanguage = LanguagesList.EN.Iso1, - Languages = [LanguagesList.RO.Iso1, LanguagesList.EN.Iso1], + Languages = [LanguagesList.RO.Iso1, LanguagesList.EN.Iso1] }; // Act diff --git a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs index 6fab90ca6..921e25e77 100644 --- a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/DeleteRequestValidatorTests.cs @@ -54,7 +54,7 @@ public void Validation_ShouldPass_When_ValidRequest() { ElectionRoundId = Guid.NewGuid(), PollingStationId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs index 91075584b..47d88edd5 100644 --- a/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs +++ b/api/tests/Feature.PollingStation.Information.UnitTests/ValidatorTests/UpsertValidatorTests.cs @@ -80,12 +80,12 @@ public void Validation_ShouldFail_When_ObservationBreaks_ContainsInvalid() new Request.BreakRequest { Start = DateTime.UtcNow.AddDays(-1), - End = DateTime.UtcNow, + End = DateTime.UtcNow }, new Request.BreakRequest { Start = DateTime.UtcNow, - End = DateTime.UtcNow.AddDays(-1), + End = DateTime.UtcNow.AddDays(-1) } ]}; @@ -108,7 +108,7 @@ public void Validation_ShouldPass_When_BreakEndIsNull() new Request.BreakRequest { Start = DateTime.UtcNow.AddDays(-1), - End = DateTime.UtcNow, + End = DateTime.UtcNow }, new Request.BreakRequest { diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs index 53d57a7ee..855ea9197 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/AddAttachmentEndpointTests.cs @@ -152,7 +152,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() ElectionRoundId = fakeElectionRound.Id, QuickReportId = _quickReportId, Id = _attachmentId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -183,7 +183,7 @@ public async Task ShouldReturnBadRequest_WhenMonitoringObserverDoesNotExist() ElectionRoundId = fakeElectionRound.Id, QuickReportId = _quickReportId, Id = _attachmentId, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs index 7ca4b34fd..188dd0a3f 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteAttachmentEndpointTests.cs @@ -28,7 +28,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new DeleteAttachment.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs index 460b04137..27daa7adc 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/DeleteEndpointTests.cs @@ -32,7 +32,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() { ElectionRoundId = electionRoundId, ObserverId = monitoringObserverId, - Id = attachmentId, + Id = attachmentId }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs b/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs index 5937a9d98..1d62fbe94 100644 --- a/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/Endpoints/ListMyEndpointTests.cs @@ -58,7 +58,7 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() ]; List quickReport2Attachments = [ - new QuickReportAttachmentFaker(quickReportId: quickReport2.Id).Generate(), + new QuickReportAttachmentFaker(quickReportId: quickReport2.Id).Generate() ]; List attachments = [.. quickReport1Attachments, .. quickReport2Attachments]; @@ -70,7 +70,7 @@ public async Task ShouldReturnOkWithQuickReportModels_WhenAllIdsExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -113,7 +113,7 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); @@ -155,7 +155,7 @@ public async Task ShouldGetPresignedUrlsForAttachments_WhenAllIdsExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; _ = await _endpoint.ExecuteAsync(request, default); @@ -195,7 +195,7 @@ public async Task ShouldReturnEmptyList_WhenQuickReportsDoNotExist() var request = new ListMy.Request { ElectionRoundId = fakeElectionRound.Id, - ObserverId = fakeMonitoringObserver.Id, + ObserverId = fakeMonitoringObserver.Id }; var result = await _endpoint.ExecuteAsync(request, default); diff --git a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs index 467da0651..9115dd88d 100644 --- a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListMyRequestValidatorTests.cs @@ -37,7 +37,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new ListMy.Request { ElectionRoundId = Guid.NewGuid(), - ObserverId = Guid.NewGuid(), + ObserverId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 54534d7f9..9742c4dc7 100644 --- a/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Feature.QuickReports.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -76,7 +76,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new List.Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs b/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs index f093e626f..cbf035598 100644 --- a/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs +++ b/api/tests/Feature.Statistics.UnitTests/Validators/GetNgoAdminStatisticsValidatorTests.cs @@ -37,7 +37,7 @@ public void Validation_ShouldPass_When_ValidRequest() var request = new GetNgoAdminStatistics.Request { ElectionRoundId = Guid.NewGuid(), - NgoId = Guid.NewGuid(), + NgoId = Guid.NewGuid() }; // Act diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs index 4225df53f..897c601e3 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/AnswerAggregateFactoryTests.cs @@ -29,6 +29,6 @@ public void Map_Should_Return_CorrectAggregator(BaseQuestion question, Type expe new object[] { new RatingQuestionFaker().Generate(), typeof(RatingAnswerAggregate) }, new object[] { new NumberQuestionFaker().Generate(), typeof(NumberAnswerAggregate) }, new object[] { new SingleSelectQuestionFaker().Generate(), typeof(SingleSelectAnswerAggregate) }, - new object[] { new MultiSelectQuestionFaker().Generate(), typeof(MultiSelectAnswerAggregate) }, + new object[] { new MultiSelectQuestionFaker().Generate(), typeof(MultiSelectAnswerAggregate) } }; } diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs index 5f216b630..fd5d6a86b 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/FormSubmissionsAggregateTests.cs @@ -235,7 +235,7 @@ public void AggregateAnswers_ShouldAggregateAnswersCorrectly() [ new DateAnswerFaker(_dateQuestion.Id), new TextAnswerFaker(_textQuestion.Id), - new NumberAnswerFaker(_numberQuestion.Id), + new NumberAnswerFaker(_numberQuestion.Id) ]; var formSubmission1 = FormSubmission.Create(_electionRound, pollingStation, monitoringObserver, _form, @@ -269,12 +269,12 @@ public void AggregateAnswers_ShouldIgnoreInvalidAnswersInSubmissions() List submission1Answers = [ - new SingleSelectAnswerFaker(_singleSelectQuestion.Id, _singleSelectQuestion.Options[1].Select()), + new SingleSelectAnswerFaker(_singleSelectQuestion.Id, _singleSelectQuestion.Options[1].Select()) ]; List submission2Answers = [ - new SingleSelectAnswerFaker(Guid.NewGuid(), _singleSelectQuestion.Options[2].Select()), + new SingleSelectAnswerFaker(Guid.NewGuid(), _singleSelectQuestion.Options[2].Select()) ]; List submission3Answers = []; diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs index c8deb4c01..344c0fd24 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Aggregators/PSIFormSubmissionsAggregateTests.cs @@ -257,7 +257,7 @@ public void AggregateAnswers_ShouldAggregateAnswersCorrectly() [ new DateAnswerFaker(_dateQuestion.Id), new TextAnswerFaker(_textQuestion.Id), - new NumberAnswerFaker(_numberQuestion.Id), + new NumberAnswerFaker(_numberQuestion.Id) ]; var formSubmission1 = PollingStationInformation.Create(_userId, _electionRound, pollingStation, diff --git a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs index 716b27854..0de2af4a3 100644 --- a/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs +++ b/api/tests/Vote.Monitor.Answer.Module.UnitTests/Validators/MultiSelectAnswerRequestValidatorTests.cs @@ -42,7 +42,7 @@ public void Validation_ShouldFail_When_InvalidSelection() { Text = "", OptionId = Guid.Empty - }, + } ] }; diff --git a/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs b/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs index bf55de186..c8964c217 100644 --- a/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.Observer.UnitTests/Services/ObserverCsvParserTests.cs @@ -107,7 +107,7 @@ public void Parsing_ShouldSucceed_When_ValidCSVs(string fileContent, List{ - new() {FirstName = "Obs1",LastName="test",Password="pa$$word", Email = "obs1@mail.com", PhoneNumber = "2000000000", }, + new() {FirstName = "Obs1",LastName="test",Password="pa$$word", Email = "obs1@mail.com", PhoneNumber = "2000000000" }, new() {FirstName = "Obs2",LastName="test",Password="pa$$word", Email = "obs2@mail.com", PhoneNumber = "3000000000" } } }, diff --git a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs index 735a40ab4..9c9be647d 100644 --- a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/Specifications/GetPollingStationSpecificationTests.cs @@ -17,8 +17,14 @@ public void GetPollingStationSpecification_AppliesCorrectFilters() .Union(new PollingStationAggregateFaker(electionRound: electionRound).Generate(500)) .ToList(); - var spec = new GetPollingStationSpecification(electionRound.Id, pollingStation.Address, new Dictionary()); - + var spec = new GetPollingStationSpecification(electionRound.Id, + pollingStation.Level1, + pollingStation.Level2, + pollingStation.Level3, + pollingStation.Level4, + pollingStation.Level5, + pollingStation.Number, + pollingStation.Address); // Act var result = spec.Evaluate(testCollection).ToList(); @@ -40,7 +46,14 @@ public void GetPollingStationSpecification_AppliesCorrectFilters_WhenPartialAddr .Union(new PollingStationAggregateFaker(electionRound).Generate(500)) .ToList(); - var spec = new GetPollingStationSpecification(electionRound.Id, pollingStation.Address[..(pollingStation.Address.Length / 2)], new Dictionary()); + var spec = new GetPollingStationSpecification(electionRound.Id, + pollingStation.Level1, + pollingStation.Level2, + pollingStation.Level3, + pollingStation.Level4, + pollingStation.Level5, + pollingStation.Number, + pollingStation.Address); // Act var result = spec.Evaluate(testCollection).ToList(); diff --git a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs index 9a3db366d..b3d458875 100644 --- a/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs +++ b/api/tests/Vote.Monitor.Api.Feature.PollingStation.UnitTests/ValidatorTests/ListRequestValidatorTests.cs @@ -15,7 +15,7 @@ public void Validation_ShouldPass_When_PageSize_ValidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -36,7 +36,7 @@ public void Validation_ShouldFail_When_PageSize_InvalidValues(int pageSize) { ElectionRoundId = Guid.NewGuid(), PageSize = pageSize, - PageNumber = 1, + PageNumber = 1 }; // Act @@ -57,7 +57,7 @@ public void Validation_ShouldPass_When_PageNumber_ValidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act @@ -77,7 +77,7 @@ public void Validation_ShouldFail_When_PageNumber_InvalidValues(int pageNumber) { ElectionRoundId = Guid.NewGuid(), PageSize = 10, - PageNumber = pageNumber, + PageNumber = pageNumber }; // Act diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs new file mode 100644 index 000000000..8e5b268da --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs @@ -0,0 +1,41 @@ +using Vote.Monitor.Api.IntegrationTests.Db; + +namespace Vote.Monitor.Api.IntegrationTests; + +[SetUpFixture] +public class ApiTesting +{ + private static ITestDatabase _database = null!; + private static CustomWebApplicationFactory _factory = null!; + + [OneTimeSetUp] + public async Task RunBeforeAnyTests() + { + _database = await TestDatabaseFactory.CreateAsync(); + await _database.InitialiseAsync(); + _factory = new CustomWebApplicationFactory(_database.GetConnectionString(), _database.GetConnection()); + } + + public static async Task ResetState() + { + try + { + await _database.ResetAsync(); + } + catch (Exception) + { + } + } + + public static HttpClient CreateClient() + { + return _factory.CreateClient(); + } + + [OneTimeTearDown] + public async Task RunAfterAnyTests() + { + await _database.DisposeAsync(); + await _factory.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs new file mode 100644 index 000000000..a459603b5 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseApiTestFixture.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests; + +using static ApiTesting; + +[TestFixture] +public abstract class BaseApiTestFixture +{ + [SetUp] + public async Task BaseTestSetUp() + { + await ResetState(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs new file mode 100644 index 000000000..efb214879 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs @@ -0,0 +1,11 @@ +namespace Vote.Monitor.Api.IntegrationTests; + +[TestFixture] +public abstract class BaseDbTestFixture +{ + [SetUp] + public async Task BaseTestSetUp() + { + await DbTesting.ResetState(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs new file mode 100644 index 000000000..f438f3c05 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs @@ -0,0 +1,7 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public class Coalitions +{ + public const string Youth = "Mega coalitia tineretului"; + public const string Old = "Brigata dementa"; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs new file mode 100644 index 000000000..7ccf325f5 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public class ElectionRounds +{ + public const string A = "ElectionRound-A"; + public const string B = "ElectionRound-B"; + public const string C = "ElectionRound-C"; + public const string D = "ElectionRound-D"; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs new file mode 100644 index 000000000..6737f63ea --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs @@ -0,0 +1,33 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public class Ngos +{ + public static AlfaDetails Alfa => new(); + public static BetaDetails Beta => new BetaDetails(); + public static DeltaDetails Delta => new DeltaDetails(); + + + public class AlfaDetails + { + public static implicit operator string(AlfaDetails _) => "Alfa NGO"; + public readonly string Anya = "anya@alfa.com"; + public readonly string Ben = "ben@alfa.com"; + public readonly string Cody = "cody@alfa.com"; + } + + public class BetaDetails + { + public static implicit operator string(BetaDetails _) => "Beta NGO"; + public readonly string Dana = "dana@beta.com"; + public readonly string Ivy = "ivy@beta.com"; + public readonly string Finn = "finn@beta.com"; + } + + public class DeltaDetails + { + public static implicit operator string(DeltaDetails _) => "Delta NGO"; + public readonly string Gia = "gia@delta.com"; + public readonly string Mason = "mason@delta.com"; + public readonly string Omar = "omar@delta.com"; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs new file mode 100644 index 000000000..0f1233118 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public class Observers +{ + public const string Alice = "alice@observer.com"; + public const string Bob = "bod@observer.com"; + public const string Charlie = "charlie@observer.com"; + public const string Dave = "dave@observer.com"; + public const string Eve = "eve@obsever.com"; + public const string Frank = "frank@obsever.com"; + public const string Grace = "grace@obsever.com"; + public const string Mallory = "mallory@obsever.com"; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs new file mode 100644 index 000000000..6180bdd93 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public class PollingStations +{ + public const string Iasi = "A"; + public const string Bacau = "B"; + public const string Cluj = "C"; + public const string D = "D"; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs new file mode 100644 index 000000000..1a40edcc8 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs @@ -0,0 +1,81 @@ +using System.Data.Common; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Npgsql; +using Serilog; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests; + +public class CustomWebApplicationFactory : WebApplicationFactory +{ + private readonly DbConnection _connection; + private readonly NpgsqlConnectionStringBuilder _connectionDetails; + + public const string AdminEmail = "admin@example.com"; + public const string AdminPassword = "toTallyNotTestPassw0rd"; + + public CustomWebApplicationFactory(string connectionString, DbConnection connection) + { + _connection = connection; + _connectionDetails = new NpgsqlConnectionStringBuilder() { ConnectionString = connectionString }; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseSetting("AuthFeatureConfig:JWTConfig:TokenSigningKey", "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes" ); + builder.UseSetting("AuthFeatureConfig:JWTConfig:TokenExpirationInMinutes", "10080" ); + builder.UseSetting("AuthFeatureConfig:JWTConfig:RefreshTokenExpirationInDays", "30"); + builder.UseSetting("Domain:DbConnectionConfig:Server", _connectionDetails.Host ); + builder.UseSetting("Domain:DbConnectionConfig:Port", _connectionDetails.Port.ToString() ); + builder.UseSetting("Domain:DbConnectionConfig:Database", _connectionDetails.Database ); + builder.UseSetting("Domain:DbConnectionConfig:UserId", _connectionDetails.Username ); + builder.UseSetting("Domain:DbConnectionConfig:Password", _connectionDetails.Password); + builder.UseSetting("Core:EnableHangfire", "false" ); + builder.UseSetting("Core:HangfireConnectionConfig:Server", _connectionDetails.Host ); + builder.UseSetting("Core:HangfireConnectionConfig:Port", _connectionDetails.Port.ToString() ); + builder.UseSetting("Core:HangfireConnectionConfig:Database", _connectionDetails.Database); + builder.UseSetting("Core:HangfireConnectionConfig:UserId", _connectionDetails.Username); + builder.UseSetting("Core:HangfireConnectionConfig:Password", _connectionDetails.Password); + builder.UseSetting("Sentry:Enabled", "false" ); + builder.UseSetting("Sentry:Dsn", "" ); + builder.UseSetting("Sentry:TracesSampleRate", "0.2" ); + builder.UseSetting("Seeders:PlatformAdminSeeder:FirstName", "John" ); + builder.UseSetting("Seeders:PlatformAdminSeeder:LastName", "Doe" ); + builder.UseSetting("Seeders:PlatformAdminSeeder:Email", AdminEmail); + builder.UseSetting("Seeders:PlatformAdminSeeder:PhoneNumber", "1234567890" ); + builder.UseSetting("Seeders:PlatformAdminSeeder:Password", AdminPassword); + + builder.ConfigureTestServices((services) => + { + services + .RemoveAll>() + .AddDbContext((sp, options) => + { + options.AddInterceptors(sp.GetServices()); + options.UseNpgsql(_connection); + }); + + services.AddLogging(logging => + { + Serilog.Debugging.SelfLog.Enable(Console.WriteLine); + + var loggerConfiguration = new LoggerConfiguration() + .WriteTo.NUnitOutput() + .Enrich.FromLogContext() + .Enrich.WithMachineName() + .Enrich.WithEnvironmentUserName() + .Destructure.ToMaximumDepth(3); + + var logger = Log.Logger = loggerConfiguration.CreateLogger(); + + logging.AddSerilog(logger); + }); + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs new file mode 100644 index 000000000..1f6f86b63 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/ITestDatabase.cs @@ -0,0 +1,14 @@ +using System.Data.Common; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public interface ITestDatabase +{ + Task InitialiseAsync(); + + DbConnection GetConnection(); + string GetConnectionString(); + Task ResetAsync(); + + Task DisposeAsync(); +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs new file mode 100644 index 000000000..0565658f8 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs @@ -0,0 +1,74 @@ +using System.Data.Common; +using Authorization.Policies; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Npgsql; +using Respawn; +using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public class PostgresTestDatabase : ITestDatabase +{ + private readonly string _connectionString = null!; + private NpgsqlConnection _connection = null!; + private Respawner _respawner = null!; + + public PostgresTestDatabase() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + var connectionString = configuration.GetConnectionString("DefaultConnection"); + + _connectionString = connectionString; + } + + public async Task InitialiseAsync() + { + _connection = new NpgsqlConnection(_connectionString); + _connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(_connectionString) + .Options; + + var context = new VoteMonitorContext(options, new SerializerService(NullLogger.Instance), + new CurrentUtcTimeProvider(), new CurrentUserProvider()); + + context.Database.Migrate(); + + _respawner = await Respawner.CreateAsync(_connection, + new RespawnerOptions + { + TablesToIgnore = + ["__EFMigrationsHistory", "AspNetRoles", "Language", "Countries"], + DbAdapter = DbAdapter.Postgres + }); + } + + public DbConnection GetConnection() + { + return _connection; + } + + public string GetConnectionString() + { + return _connectionString; + } + + public async Task ResetAsync() + { + await _respawner.ResetAsync(_connection); + } + + public async Task DisposeAsync() + { + await _connection.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs new file mode 100644 index 000000000..296aafa9e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestDatabaseFactory.cs @@ -0,0 +1,14 @@ +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public static class TestDatabaseFactory +{ + public static async Task CreateAsync() + { + var database = new TestcontainersTestDatabase(); + // var database = new PostgresTestDatabase(); + + await database.InitialiseAsync(); + + return database; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs new file mode 100644 index 000000000..7d92eb7cc --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs @@ -0,0 +1,75 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging.Abstractions; +using Npgsql; +using Respawn; +using Testcontainers.PostgreSql; +using Vote.Monitor.Api.IntegrationTests.Services; +using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; + +namespace Vote.Monitor.Api.IntegrationTests.Db; + +public class TestcontainersTestDatabase : ITestDatabase +{ + private readonly PostgreSqlContainer _container; + private DbConnection _connection = null!; + private string _connectionString = null!; + private Respawner _respawner = null!; + + public TestcontainersTestDatabase() + { + _container = new PostgreSqlBuilder() + .WithAutoRemove(true) + .Build(); + } + + public async Task InitialiseAsync() + { + await _container.StartAsync(); + + _connectionString = _container.GetConnectionString(); + _connection = new NpgsqlConnection(_connectionString); + _connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(_connectionString) + .Options; + + var context = new VoteMonitorContext(options, new SerializerService(NullLogger.Instance), + new CurrentUtcTimeProvider(), new NoopCurrentUserProvider()); + + context.Database.Migrate(); + + _respawner = await Respawner.CreateAsync(_connection, + new RespawnerOptions + { + TablesToIgnore = + ["__EFMigrationsHistory", "AspNetRoles", "Language", "Countries"], + DbAdapter = DbAdapter.Postgres + }); + } + + public DbConnection GetConnection() + { + return _connection; + } + + public string GetConnectionString() + { + return _connectionString; + } + + + public async Task ResetAsync() + { + await _respawner.ResetAsync(_connectionString); + } + + public async Task DisposeAsync() + { + await _connection.DisposeAsync(); + await _container.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs new file mode 100644 index 000000000..a2e9ed037 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs @@ -0,0 +1,35 @@ +using Vote.Monitor.Api.IntegrationTests.Db; + +namespace Vote.Monitor.Api.IntegrationTests; + +[SetUpFixture] +public class DbTesting +{ + private static ITestDatabase _database = null!; + + [OneTimeSetUp] + public async Task RunBeforeAnyTests() + { + _database = await TestDatabaseFactory.CreateAsync(); + await _database.InitialiseAsync(); + } + + public static async Task ResetState() + { + try + { + await _database.ResetAsync(); + } + catch (Exception) + { + } + } + + public static string DbConnectionString => _database.GetConnectionString(); + + [OneTimeTearDown] + public async Task RunAfterAnyTests() + { + await _database.DisposeAsync(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs deleted file mode 100644 index 51c239d88..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/FEProblemDetails.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Vote.Monitor.Api.IntegrationTests; - -/// -/// A helper class to deserialize FastEndpoints.ProblemDetails -/// -public sealed class FEProblemDetails -{ - public string Type { get; set; } = string.Empty; - public string Title { get; set; } = string.Empty; - public int Status { get; set; } - public string Instance { get; set; } = string.Empty; - public string TraceId { get; set; } = string.Empty; - public IEnumerable Errors { get; set; } = Array.Empty(); - - public sealed class Error - { - /// - /// the name of the error or property of the dto that caused the error - /// - public string Name { get; init; } = string.Empty; - - /// - /// the reason for the error - /// - public string Reason { get; init; } = string.Empty; - - /// - /// the code of the error - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Code { get; init; } - - /// - /// the severity of the error - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Severity { get; init; } - - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs new file mode 100644 index 000000000..852627b73 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/Answers.cs @@ -0,0 +1,33 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public static class Answers +{ + public static BaseAnswerRequest GetFakeAnswer(BaseQuestionRequest question) + { + switch (question) + { + case TextQuestionRequest _: + return new TextAnswerFaker(question.Id).Generate(); + + case NumberQuestionRequest _: + return new NumberAnswerFaker(question.Id).Generate(); + + case DateQuestionRequest _: + return new DateAnswerFaker(question.Id).Generate(); + + case SingleSelectQuestionRequest singleSelectQuestionRequest: + return new SingleSelectAnswerFaker(question.Id, singleSelectQuestionRequest.Options).Generate(); + + case MultiSelectQuestionRequest multiSelectQuestionRequest: + return new MultiSelectAnswerFaker(question.Id, multiSelectQuestionRequest.Options).Generate(); + + case RatingQuestionRequest _: + return new RatingAnswerFaker(question.Id).Generate(); + + default: throw new ApplicationException("Unknown question type received"); + } + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs new file mode 100644 index 000000000..3814bb8c7 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/DateAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class DateAnswerFaker : Faker +{ + public DateAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Date, f => f.Date.Recent()); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs new file mode 100644 index 000000000..7aa377484 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/ElectionRoundFaker.cs @@ -0,0 +1,15 @@ +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class ElectionRoundFaker : Faker +{ + public ElectionRoundFaker() + { + // Republic of Azerbaijan + RuleFor(x => x.CountryId, new Guid("008c3138-73d8-dbbc-f1dd-521e4c68bcf1")); + RuleFor(x => x.Title, f => f.Lorem.Sentence()); + RuleFor(x => x.EnglishTitle, f => f.Lorem.Sentence()); + RuleFor(x => x.StartDate, f => f.Date.FutureDateOnly()); + } +} \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/FormSubmissionRequestFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/FormSubmissionRequestFaker.cs new file mode 100644 index 000000000..da6889c3f --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/FormSubmissionRequestFaker.cs @@ -0,0 +1,18 @@ +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class FormSubmissionRequestFaker : Faker +{ + public FormSubmissionRequestFaker(Guid formId, Guid pollingStationId, List questions) + { + Rules((f, x) => + { + x.PollingStationId = pollingStationId; + x.FormId = formId; + x.Answers = f.PickRandom(questions, f.Random.Int(0, questions.Count)) + .Select(Answers.GetFakeAnswer).ToList(); + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs new file mode 100644 index 000000000..1edc4311e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/MultiSelectAnswerFaker.cs @@ -0,0 +1,32 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class MultiSelectAnswerFaker : Faker +{ + public MultiSelectAnswerFaker(Guid questionId, List options) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Selection, f => + { + var optionsToPick = f.Random.Number(1, options.Count); + + var selectedOptions = f.PickRandom(options, optionsToPick).Distinct().ToList(); + var selection = new List(); + + foreach (var selectedOption in selectedOptions) + { + string? text = null; + if (selectedOption.IsFreeText) + { + text = FakerHub.Lorem.Sentence(100); + } + + selection.Add(new SelectedOptionRequest { OptionId = selectedOption.Id, Text = text }); + } + + return selection; + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs new file mode 100644 index 000000000..1bc0cf7f1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/NumberAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class NumberAnswerFaker : Faker +{ + public NumberAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Value, f => f.Random.Number(0, 1_000_000)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs new file mode 100644 index 000000000..5049366b3 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/RatingAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class RatingAnswerFaker : Faker +{ + public RatingAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Value, f => f.Random.Number(1, 10)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs new file mode 100644 index 000000000..8180d6941 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/SingleSelectAnswerFaker.cs @@ -0,0 +1,25 @@ +using Vote.Monitor.Answer.Module.Requests; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class SingleSelectAnswerFaker : Faker +{ + public SingleSelectAnswerFaker(Guid questionId, List options) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Selection, f => + { + var selectedOption = f.PickRandom(options); + + string? text = null; + if (selectedOption.IsFreeText) + { + text = f.Lorem.Sentence(100); + } + + var selection = new SelectedOptionRequest { OptionId = selectedOption.Id, Text = text }; + return selection; + }); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs new file mode 100644 index 000000000..bf16d68d1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Fakers/TextAnswerFaker.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Fakers; + +public sealed class TextAnswerFaker : Faker +{ + public TextAnswerFaker(Guid questionId) + { + RuleFor(x => x.QuestionId, questionId); + RuleFor(x => x.Text, f => f.Lorem.Sentence(100)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs new file mode 100644 index 000000000..a1e6d0a3b --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs @@ -0,0 +1,210 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Forms; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; +using ListMonitoringNgosResponse = Feature.Monitoring.List.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class CreateTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A) + .Please(); + + var coalitionName = Guid.NewGuid().ToString(); + var electionRoundId = scenarioData.ElectionRoundId; + + var coalition = await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = coalitionName, + LeaderId = scenarioData.NgoIdByName(Ngos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + }); + + coalition.Should().NotBeNull(); + coalition.Name.Should().Be(coalitionName); + coalition.Members.Should().HaveCount(2); + + coalition.LeaderId.Should().Be(scenarioData.NgoByName(Ngos.Alfa).NgoId); + coalition.Members.Select(x => x.Id).Should() + .Contain([scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId]); + } + + [Test] + public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithNgo(Ngos.Delta) + .WithElectionRound(ElectionRounds.A, + electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Delta).NgoId } + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId, + scenarioData.NgoByName(Ngos.Delta).NgoId + ]); + } + + [Test] + public async Task ShouldAddLeaderAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithNgo(Ngos.Delta) + .WithElectionRound(ElectionRounds.A, + electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Delta).NgoId, + NgoMembersIds = Array.Empty() + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId, + scenarioData.NgoByName(Ngos.Delta).NgoId + ]); + } + + [Test] + public async Task ShouldNotGiveAccessToNgoFormsForCoalitionMembers() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, + electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A"))) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + await scenarioData.PlatformAdmin.PostWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Beta).NgoId, + NgoMembersIds = Array.Empty() + }); + + var formResult = await scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().BeEmpty(); + } + + [Test] + public async Task NgoAdmin_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithElectionRound(ElectionRounds.A) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await scenarioData.NgoByName(Ngos.Alfa).Admin.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await scenarioData.Observer.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotCreateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + + var coalitionResponseMessage = await CreateClient().PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + }); + + coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs new file mode 100644 index 000000000..e20d8d07c --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs @@ -0,0 +1,238 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Form.Submissions.ListEntries; +using Feature.Forms; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class DeleteTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldDeleteCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalition = scenarioData.ElectionRound.Coalition; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalition.CoalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + + var getCoalitionByIdResponse = await scenarioData.PlatformAdmin.GetAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalition.CoalitionId}"); + + getCoalitionByIdResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + + [Test] + public async Task ShouldRemoveDataForMonitoringNgos() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) + .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithElectionRound(ElectionRounds.A, er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithPollingStation(PollingStations.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + cfg => cfg + .WithForm("Common", [Ngos.Beta], + form => form + .WithSubmission(Observers.Alice, PollingStations.Iasi) + .WithSubmission(Observers.Alice, PollingStations.Bacau) + .WithSubmission(Observers.Bob, PollingStations.Iasi) + .WithSubmission(Observers.Bob, PollingStations.Bacau)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var alfaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + var submission1 = + scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Iasi); + var submission2 = + scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Bacau); + + alfaNgoSubmissions.Items.Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And + .BeEquivalentTo([submission1, submission2]); + + betaNgoSubmissions.Items.Should().HaveCount(0); + } + + [Test] + public async Task ShouldKeepFormForLeader() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) + .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithElectionRound(ElectionRounds.A, er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithPollingStation(PollingStations.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + cfg => cfg.WithForm("Common", [Ngos.Beta]) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var formResult = await scenarioData + .NgoByName(Ngos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().HaveCount(1); + formResult.Items.First().Id.Should().Be(scenarioData.ElectionRound.Coalition.Form.FormId); + } + + [Test] + public async Task ShouldRemoveFormAccessFromExCoalitionMembers() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) + .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithElectionRound(ElectionRounds.A, er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithPollingStation(PollingStations.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + cfg => cfg + .WithForm("Common", [Ngos.Beta]) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var deleteResponse = await scenarioData.PlatformAdmin.DeleteAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}"); + + deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + var formResult = await scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/forms"); + + formResult.Items.Should().HaveCount(0); + } + + [Test] + public async Task NgoAdmin_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin()) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.NgoByName(Ngos.Alfa).Admin.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Observer.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = Array.Empty() + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await CreateClient().PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + NgoMembersIds = Array.Empty() + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs new file mode 100644 index 000000000..963fccec7 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -0,0 +1,206 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Forms; +using Feature.Forms.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class FormAccessTests : BaseApiTestFixture +{ + [Test] + public void ShouldNotGrantFormAccessForMonitoringObservers_WhenCreatingNewForm() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithElectionRound(ElectionRounds.A, + er => er + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + // Act + var formRequest = Dummy.Form(); + var electionRoundId = scenarioData.ElectionRoundId; + var ngoForm = + scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/forms", + formRequest); + + scenarioData.NgoByName(Ngos.Alfa).Admin + .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + // Assert + var aliceForms = scenarioData + .ObserverByName(Observers.Alice) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + var bobForms = scenarioData + .ObserverByName(Observers.Bob) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + aliceForms.Forms.Should().BeEmpty(); + bobForms.Forms.Should().BeEmpty(); + } + + [Test] + public void ShouldNotGrantFormAccessForMonitoringNgos_WhenCreatingNewForm() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithElectionRound(ElectionRounds.A, + er => er + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + // Act + var formRequest = Dummy.Form(); + var electionRoundId = scenarioData.ElectionRoundId; + var ngoForm = + scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithResponse( + $"/api/election-rounds/{electionRoundId}/forms", + formRequest); + + scenarioData.NgoByName(Ngos.Alfa).Admin + .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + // Assert + var betaForms = scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + betaForms.Items.Should().BeEmpty(); + } + + [Test] + public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithElectionRound(ElectionRounds.A, + er => er + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice).WithForm()) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + + scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + // Assert + var aliceForms = scenarioData + .ObserverByName(Observers.Alice) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + var bobForms = scenarioData + .ObserverByName(Observers.Bob) + .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); + + var betaForms = scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + aliceForms.Forms.Should().BeEmpty(); + bobForms.Forms.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); + betaForms.Items.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg.WithForm("A", []))) + .Please(); + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await scenarioData.NgoByName(Ngos.Beta).Admin.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.NotFound); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithElectionRound(ElectionRounds.A, er => er + .WithMonitoringNgo(Ngos.Alfa) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg + .WithForm("A", []) + .WithMonitoringObserver(Ngos.Alfa, Observers.Alice) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await scenarioData.ObserverByName(Observers.Alice).PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, + er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], c => c.WithForm())) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + var response = await CreateClient().PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs new file mode 100644 index 000000000..2e0cffcdc --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs @@ -0,0 +1,309 @@ +using System.Net; +using System.Net.Http.Json; +using Feature.Form.Submissions.ListEntries; +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Core.Models; +using ListMonitoringNgosResponse = Feature.Monitoring.List.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.Coalition; + +using static ApiTesting; + +public class UpdateTests : BaseApiTestFixture +{ + [Test] + public async Task PlatformAdmin_ShouldUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var newCoalitionName = Guid.NewGuid().ToString(); + var updatedCoalition = await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new { CoalitionName = newCoalitionName, NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + updatedCoalition.Should().NotBeNull(); + + updatedCoalition.Name.Should().Be(newCoalitionName); + updatedCoalition.Members.Should().HaveCount(2); + + updatedCoalition.LeaderId.Should().Be(scenarioData.NgoIdByName(Ngos.Alfa)); + updatedCoalition.Members.Select(x => x.Id).Should() + .Contain([scenarioData.NgoIdByName(Ngos.Alfa), scenarioData.NgoIdByName(Ngos.Beta)]); + } + + [Test] + public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithNgo(Ngos.Delta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta), scenarioData.NgoIdByName(Ngos.Delta) } + }); + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoIdByName(Ngos.Alfa), + scenarioData.NgoIdByName(Ngos.Beta), + scenarioData.NgoIdByName(Ngos.Delta) + ]); + } + + [Test] + public async Task NgosStayAsMonitoringNgos_WhenTheyAreKicked() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithNgo(Ngos.Delta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta), scenarioData.NgoIdByName(Ngos.Delta) } + }); + + + var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( + $"/api/election-rounds/{electionRoundId}/monitoring-ngos"); + monitoringNgos.MonitoringNgos.Should().HaveCount(3); + monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() + .Contain([ + scenarioData.NgoIdByName(Ngos.Alfa), + scenarioData.NgoIdByName(Ngos.Beta), + scenarioData.NgoIdByName(Ngos.Delta) + ]); + } + + + [Test] + public async Task ShouldKeepObserverDatatForNgoForms_WhenNgoIsKicked() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithObserver(Observers.Charlie) + .WithObserver(Observers.Dave) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithPollingStation(PollingStations.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo + .WithMonitoringObserver(Observers.Alice) + .WithMonitoringObserver(Observers.Bob) + .WithForm("A", form => form + .Publish() + .WithSubmission(Observers.Alice, PollingStations.Iasi) + .WithSubmission(Observers.Alice, PollingStations.Bacau) + .WithSubmission(Observers.Bob, PollingStations.Bacau) + .WithSubmission(Observers.Bob, PollingStations.Cluj)) + ) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo + .WithMonitoringObserver(Observers.Charlie) + .WithMonitoringObserver(Observers.Dave) + .WithForm("A", form => form + .Publish() + .WithSubmission(Observers.Charlie, PollingStations.Iasi) + .WithSubmission(Observers.Charlie, PollingStations.Bacau) + .WithSubmission(Observers.Dave, PollingStations.Bacau) + .WithSubmission(Observers.Dave, PollingStations.Cluj)) + ) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + }); + + var alfaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + alfaNgoSubmissions.Items.Should().HaveCount(4); + betaNgoSubmissions.Items.Should().HaveCount(4); + } + + [Test] + public async Task ShouldRemoveDataForSharedFormsOfKickedNgos() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) + .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithElectionRound(ElectionRounds.A, er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithPollingStation(PollingStations.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + cfg => cfg + .WithForm("Common", [Ngos.Beta], + form => form + .WithSubmission(Observers.Alice, PollingStations.Iasi) + .WithSubmission(Observers.Alice, PollingStations.Bacau) + .WithSubmission(Observers.Bob, PollingStations.Iasi) + .WithSubmission(Observers.Bob, PollingStations.Bacau)) + ) + ) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + + await scenarioData.PlatformAdmin.PutWithResponseAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Alfa) } + }); + + var alfaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Alfa).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + var betaNgoSubmissions = await scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponseAsync>( + $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); + + var submission1 = + scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Iasi); + var submission2 = + scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Bacau); + + alfaNgoSubmissions.Items + .Should() + .HaveCount(2) + .And.Subject + .Select(x => x.SubmissionId) + .Should() + .BeEquivalentTo([submission1, submission2]); + + betaNgoSubmissions.Items.Should().HaveCount(0); + } + + + [Test] + public async Task NgoAdmin_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Ngo.Admin.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task Observer_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await scenarioData.Observer.PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); + } + + [Test] + public async Task UnauthorizedClients_CannotUpdateCoalition() + { + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .Please(); + + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + + var coalitionResponseMessage = await CreateClient().PutAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = scenarioData.NgoByName(Ngos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + }); + + coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Unauthorized); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs index 3329ca202..03c5616fb 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/GlobalUsings.cs @@ -1,13 +1,3 @@ -// Global using directives - -global using Bogus; -global using Microsoft.AspNetCore.Hosting; -global using Microsoft.AspNetCore.TestHost; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.Extensions.DependencyInjection; -global using Testcontainers.PostgreSql; -global using Vote.Monitor.Domain; -global using Xunit.Abstractions; -global using FastEndpoints; +global using Bogus; global using FluentAssertions; -global using Xunit; +global using NUnit.Framework; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs new file mode 100644 index 000000000..133d1fabb --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs @@ -0,0 +1,146 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Json; +using System.Text.Json; +using Vote.Monitor.Api.Feature.Auth.Services; + +namespace Vote.Monitor.Api.IntegrationTests; + +public static class HttpClientExtensions +{ + public static HttpClient NewForAuthenticatedUser(this Func clientFactory, string email, + string password) + { + var authenticatedUser = clientFactory(); + + var loginResponse = authenticatedUser + .PostWithResponse("/api/auth/login", new { email, password }); + + authenticatedUser.DefaultRequestHeaders.Authorization = new("Bearer", loginResponse.Token); + + return authenticatedUser; + } + + public static void PostWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + public static TResponse PostWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PostAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync().GetAwaiter().GetResult(); + return result!; + } + + public static TResponse PutWithResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync().GetAwaiter().GetResult(); + return result!; + } + + public static TResponse GetResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri) + { + var response = client.GetAsync(requestUri).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadFromJsonAsync().GetAwaiter().GetResult(); + return result!; + } + + + public static async Task PostWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PostAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + return result!; + } + + public static async Task PutWithResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value, + CancellationToken cancellationToken = default) + { + var response = await client.PutAsJsonAsync(requestUri, value, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + return result!; + } + + public static async Task GetResponseAsync( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + CancellationToken cancellationToken = default) + { + var response = await client.GetAsync(requestUri, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(await response.Content.ReadAsStringAsync(cancellationToken)); + } + + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(cancellationToken); + return result!; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs deleted file mode 100644 index 39b2db720..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpServerFixture.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Security.Claims; -using MartinCostello.Logging.XUnit; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.Logging; -using Vote.Monitor.Api.Feature.Auth.Login; -using Vote.Monitor.Api.Feature.Auth.Services; -using Vote.Monitor.Api.Feature.ElectionRound; -using Vote.Monitor.Core.Security; -using Vote.Monitor.Core.Services.Security; -using Vote.Monitor.Domain.Constants; - -using ElectionRoundCreateEndpoint = Vote.Monitor.Api.Feature.ElectionRound.Create.Endpoint; -using ElectionRoundCreateRequest = Vote.Monitor.Api.Feature.ElectionRound.Create.Request; - -namespace Vote.Monitor.Api.IntegrationTests; - -public class HttpServerFixture : WebApplicationFactory, IAsyncLifetime, ITestOutputHelperAccessor where TDataSeeder : class, IDataSeeder -{ - private static readonly Faker _faker = new(); - private PostgreSqlContainer _postgresContainer = default!; - - public ITestOutputHelper? OutputHelper { get; set; } - - /// - /// bogus data generator - /// - public Faker Fake => _faker; - - /// - /// the default http client - /// - public HttpClient Client { get; private set; } - - /// - /// The platform admin http client - /// - public HttpClient PlatformAdmin { get; private set; } - public ElectionRoundModel ElectionRound { get; private set; } - - private readonly ClaimsPrincipal _integrationTestingUser = new([new ClaimsIdentity([new Claim(ApplicationClaimTypes.UserId, "007e57ed-7e57-7e57-7e57-007e57ed0000")], "fake")]); - private const string AdminEmail = "admin@example.com"; - private const string AdminPassword = "toTallyNotTestPassw0rd"; - - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.UseEnvironment("Testing"); - builder.ConfigureTestServices(services => - { - var descriptorType = typeof(DbContextOptions); - RemoveService(services, descriptorType); - - services.AddDbContext(options => options.UseNpgsql(_postgresContainer.GetConnectionString())); - services.AddScoped(); - }); - - builder.ConfigureLogging(l => l.ClearProviders().AddXUnit(this).SetMinimumLevel(LogLevel.Debug)); - } - - public async Task InitializeAsync() - { - _postgresContainer = new PostgreSqlBuilder() - .WithDatabase(Guid.NewGuid().ToString()) - .Build(); - - await _postgresContainer.StartAsync(); - - var currentUserInitializer = Services.GetRequiredService(); - currentUserInitializer.SetCurrentUser(_integrationTestingUser); - - Client = CreateClient(); - - var (tokenResult, tokenResponse) = await Client.POSTAsync(new() - { - Email = AdminEmail, - Password = AdminPassword - }); - - tokenResult.IsSuccessStatusCode.Should().BeTrue(); - - PlatformAdmin = CreateClient(); - PlatformAdmin.DefaultRequestHeaders.Authorization = new("Bearer", tokenResponse.Token); - - (var electionRoundResult, ElectionRound) = await PlatformAdmin.POSTAsync(new() - { - CountryId = CountriesList.RO.Id, - Title = Guid.NewGuid().ToString(), - EnglishTitle = Guid.NewGuid().ToString(), - StartDate = Fake.Date.FutureDateOnly() - }); - - electionRoundResult.IsSuccessStatusCode.Should().BeTrue(); - - var seeder = Services.GetRequiredService(); - await seeder.SeedDataAsync(); - } - - public new async Task DisposeAsync() => await _postgresContainer.DisposeAsync().AsTask(); - - private static void RemoveService(IServiceCollection services, Type descriptorType) - { - var descriptor = services.SingleOrDefault(s => s.ServiceType == descriptorType); - if (descriptor is not null) - { - services.Remove(descriptor); - } - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs deleted file mode 100644 index 62639c23d..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/IDataSeeder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests; - -/// -/// Data seeder contract used in -/// -public interface IDataSeeder -{ - Task SeedDataAsync(); -} diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs similarity index 91% rename from api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs rename to api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs index 01c52fe8f..efd27647d 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditTrailInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs @@ -1,8 +1,9 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Vote.Monitor.Core.Services.Security; using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; using Vote.Monitor.Domain.Constants; using Vote.Monitor.Domain.Entities.ApplicationUserAggregate; using Vote.Monitor.Domain.Entities.Auditing; @@ -11,29 +12,41 @@ using Vote.Monitor.Domain.Entities.ObserverAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -namespace Vote.Monitor.Domain.UnitTests.Interceptors; - -public class AuditTrailInterceptorTests +namespace Vote.Monitor.Api.IntegrationTests.Interceptors; +using static DbTesting; +public class AuditTrailInterceptorTests: BaseDbTestFixture { - private readonly ITimeProvider _fakeTimeProvider; - private readonly ICurrentUserProvider _fakeCurrentUserProvider; - private readonly TestContext _context; + private ITimeProvider _fakeTimeProvider; + private ICurrentUserProvider _fakeCurrentUserProvider; + private VoteMonitorContext _context; - public AuditTrailInterceptorTests() + [SetUp] + public void Init() { _fakeTimeProvider = Substitute.For(); + var fakeSerializationService = Substitute.For(); _fakeCurrentUserProvider = Substitute.For(); - var dbContextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()); + var options = new DbContextOptionsBuilder() + .UseNpgsql(DbConnectionString) + .Options; - _context = new TestContext(dbContextOptions.Options, - new SerializerService(NullLogger.Instance), + _context = new VoteMonitorContext(options, + fakeSerializationService, _fakeTimeProvider, _fakeCurrentUserProvider); } - [Fact] + [TearDown] + public void Cleanup() + { + _fakeTimeProvider = null!; + _fakeCurrentUserProvider = null!; + _context.Dispose(); + } + + + [Test] public async Task Interceptor_OnEntityAdd_SaveChangesAsync_AddsCreateAuditTrail() { //Arrange @@ -62,7 +75,7 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_AddsCreateAuditTrail( auditTrail.NewValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityAdd_SaveChanges_AddsCreateAuditTrail() { //Arrange @@ -91,7 +104,7 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_AddsCreateAuditTrail() auditTrail.NewValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_AddsUpdateAudit() { //Arrange @@ -127,7 +140,7 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_AddsUpdateAudit() auditTrail.NewValues.Should().Contain(newNgoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChanges_AddsUpdateAudit() { //Arrange @@ -163,7 +176,7 @@ public async Task Interceptor_OnEntityUpdate_SaveChanges_AddsUpdateAudit() auditTrail.NewValues.Should().Contain(newNgoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityDelete_SaveChangesAsync_AddsDeleteAudit() { //Arrange @@ -197,7 +210,7 @@ public async Task Interceptor_OnEntityDelete_SaveChangesAsync_AddsDeleteAudit() auditTrail.OldValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnEntityDelete_SaveChanges_AddsDeleteAudit() { //Arrange @@ -231,7 +244,7 @@ public async Task Interceptor_OnEntityDelete_SaveChanges_AddsDeleteAudit() auditTrail.OldValues.Should().Contain(ngoName); } - [Fact] + [Test] public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForForm() { //Arrange @@ -285,4 +298,4 @@ public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForF newTrails.First().Type.Should().Be(TrailType.Create); newTrails.First().TableName.Should().Be("FormSubmission"); } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs similarity index 82% rename from api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs rename to api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs index 55f0a81b2..4de8a2954 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Interceptors/AuditingInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs @@ -2,32 +2,44 @@ using NSubstitute; using Vote.Monitor.Core.Services.Security; using Vote.Monitor.Core.Services.Serialization; +using Vote.Monitor.Core.Services.Time; +using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.NgoAggregate; -namespace Vote.Monitor.Domain.UnitTests.Interceptors; - -public class AuditingInterceptorTests +namespace Vote.Monitor.Api.IntegrationTests.Interceptors; +using static DbTesting; +public class AuditingInterceptorTests : BaseDbTestFixture { - private readonly ITimeProvider _fakeTimeProvider; - private readonly ICurrentUserProvider _fakeCurrentUserProvider; - private readonly TestContext _context; + private ITimeProvider _fakeTimeProvider; + private ICurrentUserProvider _fakeCurrentUserProvider; + private VoteMonitorContext _context; - public AuditingInterceptorTests() + [SetUp] + public void Init() { _fakeTimeProvider = Substitute.For(); var fakeSerializationService = Substitute.For(); _fakeCurrentUserProvider = Substitute.For(); - var dbContextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()); + var options = new DbContextOptionsBuilder() + .UseNpgsql(DbConnectionString) + .Options; - _context = new TestContext(dbContextOptions.Options, + _context = new VoteMonitorContext(options, fakeSerializationService, _fakeTimeProvider, _fakeCurrentUserProvider); } - [Fact] + [TearDown] + public void Cleanup() + { + _fakeTimeProvider = null!; + _fakeCurrentUserProvider = null!; + _context.Dispose(); + } + + [Test] public async Task Interceptor_OnEntityAdd_SaveChangesAsync_SetsCreatedFields() { //Arrange @@ -51,7 +63,7 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_SetsCreatedFields() testEntity.LastModifiedBy.Should().Be(userId); } - [Fact] + [Test] public async Task Interceptor_OnEntityAdd_SaveChanges_SetsCreatedFields() { //Arrange @@ -76,7 +88,7 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_SetsCreatedFields() testEntity.LastModifiedBy.Should().Be(userId); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFields() { //Arrange @@ -87,7 +99,7 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFi _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); - + var testEntity = new Ngo(string.Empty); await _context.Ngos.AddAsync(testEntity); @@ -103,7 +115,7 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFi testEntity.LastModifiedBy.Should().Be(anotherUserId); } - [Fact] + [Test] public async Task Interceptor_OnEntityUpdate_SaveChanges_SetsLastModifiedFields() { //Arrange @@ -114,7 +126,7 @@ public async Task Interceptor_OnEntityUpdate_SaveChanges_SetsLastModifiedFields( _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); - + var testEntity = new Ngo(string.Empty); await _context.Ngos.AddAsync(testEntity); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs new file mode 100644 index 000000000..45be27e4e --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/CreateFormRequest.cs @@ -0,0 +1,20 @@ +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Form.Module.Requests; + + +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class CreateFormRequest +{ + public Guid Id { get; set; } + public string Code { get; set; } + public TranslatedString Name { get; set; } = new(); + public TranslatedString Description { get; set; } = new(); + public FormType FormType { get; set; } + public List Languages { get; set; } = []; + public string DefaultLanguage { get; set; } + public string Icon { get; set; } + + public List Questions { get; set; } = new(); +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs new file mode 100644 index 000000000..2254fa956 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/FormSubmissionRequest.cs @@ -0,0 +1,12 @@ +using Vote.Monitor.Answer.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class FormSubmissionRequest +{ + public Guid PollingStationId { get; set; } + public Guid FormId { get; set; } + + public List? Answers { get; set; } + public bool? IsCompleted { get; set; } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs new file mode 100644 index 000000000..358b438cc --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Models/ResponseWithId.cs @@ -0,0 +1,6 @@ +namespace Vote.Monitor.Api.IntegrationTests.Models; + +public class ResponseWithId +{ + public Guid Id { get; set; } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs deleted file mode 100644 index 0a4368d9b..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/NoopDataSeeder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests; -/// -/// No operation data seeder. -/// -public class NoopDataSeeder : IDataSeeder -{ - public Task SeedDataAsync() - { - return Task.CompletedTask; - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs new file mode 100644 index 000000000..ac9983ef1 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs @@ -0,0 +1,40 @@ +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class CoalitionFormScenarioBuilder +{ + private readonly CoalitionScenarioBuilder _parentBuilder; + private readonly CreateFormRequest _form; + + private readonly Dictionary _submissions = new Dictionary(); + + internal CoalitionFormScenarioBuilder( + CoalitionScenarioBuilder parentBuilder, + CreateFormRequest form) + { + _parentBuilder = parentBuilder; + _form = form; + } + + public CoalitionFormScenarioBuilder WithSubmission(string observerEmail, string pollingStationName) + { + var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStationName); + var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observer = _parentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observerEmail); + + var submissionId = observer.PostWithResponse( + $"/api/election-rounds/{_parentBuilder.ParentBuilder.ElectionRoundId}/form-submissions", + submission).Id; + + _submissions.Add($"{observerEmail}_{pollingStationName}", submissionId); + return this; + } + + public Guid GetSubmissionId(string observerEmail, string pollingStationName) => + _submissions[$"{observerEmail}_{pollingStationName}"]; + + public Guid FormId => _form.Id; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs new file mode 100644 index 000000000..a7c11cdf3 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs @@ -0,0 +1,77 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class CoalitionScenarioBuilder +{ + private readonly HttpClient _platformAdmin; + private readonly HttpClient _coalitionLeaderAdminAdmin; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly CoalitionModel _coalition; + private readonly Dictionary _forms = new(); + + public CoalitionScenarioBuilder(HttpClient platformAdmin, HttpClient coalitionLeaderAdmin, + ElectionRoundScenarioBuilder parentBuilder, + CoalitionModel coalition) + { + _platformAdmin = platformAdmin; + _coalitionLeaderAdminAdmin = coalitionLeaderAdmin; + ParentBuilder = parentBuilder; + _coalition = coalition; + } + + public Guid CoalitionId => _coalition.Id; + + public CoalitionScenarioBuilder WithForm(string? formCode = null, string[]? sharedWithMembers = null, + Action? cfg = null) + { + sharedWithMembers ??= Array.Empty(); + formCode ??= Guid.NewGuid().ToString(); + + var formRequest = Dummy.Form(); + var ngoForm = + _coalitionLeaderAdminAdmin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + _coalitionLeaderAdminAdmin + .PostAsync($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + + var members = sharedWithMembers.Select(member => ParentBuilder.ParentBuilder.NgoIdByName(member)) + .ToList(); + _coalitionLeaderAdminAdmin.PostWithoutResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/coalitions/{CoalitionId}/forms/{ngoForm.Id}:access", + new + { + NgoMembersIds = members + }); + + var coalitionFormScenarioBuilder = new CoalitionFormScenarioBuilder(this, ngoForm); + cfg?.Invoke(coalitionFormScenarioBuilder); + + _forms.Add(formCode, coalitionFormScenarioBuilder); + + return this; + } + + public CoalitionScenarioBuilder WithMonitoringObserver(string ngo, string observerEmail) + { + var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observerEmail); + + _platformAdmin + .PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{ParentBuilder.MonitoringNgoIdByName(ngo)}/monitoring-observers", + new { observerId = observerId }); + + return this; + } + + public CoalitionFormScenarioBuilder Form => _forms.First().Value; + public Guid FormId => _forms.First().Value.FormId; + public CoalitionFormScenarioBuilder FormByCode(string formCode) => _forms[formCode]; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs new file mode 100644 index 000000000..1cea44e95 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/Dummy.cs @@ -0,0 +1,185 @@ +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.FormBase.Questions; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class Dummy +{ + public static CreateFormRequest Form() => new() + { + Id = Guid.Empty, + Code = "A2", + DefaultLanguage = "RO", + Languages = new List { "RO", "EN" }, + Name = new TranslatedString { { "EN", "test form" }, { "RO", "formular de test" } }, + Description = new TranslatedString { { "EN", "test form" }, { "RO", "formular de test" } }, + FormType = FormType.Opening, + Questions = + [ + new NumberQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A1", + Text = new TranslatedString + { + { "EN", "How many PEC members have been appointed" }, + { "RO", "Câți membri PEC au fost numiți" } + }, + Helptext = new TranslatedString + { + { "EN", "Please enter a number" }, { "RO", "Vă rugăm să introduceți numărul dvs" } + }, + InputPlaceholder = new TranslatedString { { "EN", "number" }, { "RO", "numar" } } + }, + new TextQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A2", + Text = new TranslatedString { { "EN", "How are you today" }, { "RO", "Cum te simți azi" } }, + Helptext = new TranslatedString + { + { "EN", "Please enter how are you" }, { "RO", "Vă rugăm să introduceți cum sunteți" } + }, + InputPlaceholder = new TranslatedString { { "EN", "mood" }, { "RO", "dispozitie" } } + }, + new DateQuestionRequest + { + Id = Guid.NewGuid(), + Code = "A3", + Text = new TranslatedString { { "EN", "Time of arrival" }, { "RO", "Timpul sosirii" } }, + Helptext = new TranslatedString + { + { "EN", "Please enter exact hour when did you arrive" }, + { "RO", "Vă rugăm să introduceți ora exactă când ați sosit" } + } + }, + new RatingQuestionRequest + { + Id = Guid.NewGuid(), + Code = "C1", + Text = new TranslatedString + { + { "EN", "Please rate this form" }, { "RO", "Vă rugăm să evaluați acest formular" } + }, + Helptext = new TranslatedString + { + { "EN", "Please give us a rating" }, { "RO", "Vă rugăm să ne dați o evaluare" } + }, + Scale = RatingScale.OneTo10 + }, + new SingleSelectQuestionRequest + { + Id = Guid.NewGuid(), + Code = "B1", + Text = + new TranslatedString + { + { "EN", "The overall conduct of the opening of this PS was:" }, + { "RO", "Conducerea generală a deschiderii acestui PS a fost:" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select a single option" }, + { "RO", "Vă rugăm să selectați o singură opțiune" } + }, + Options = new List + { + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Very good" }, { "RO", "Foarte bun" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Good" }, { "RO", "bun" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bad" }, { "RO", "Rea" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Very bad" }, { "RO", "Foarte rea" } }, + IsFreeText = false, + IsFlagged = true + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Alta" } }, + IsFreeText = true, + IsFlagged = true + } + } + }, + new MultiSelectQuestionRequest + { + Id = Guid.NewGuid(), + Code = "B2", + Text = + new TranslatedString + { + { "EN", "What party/bloc proxies were present at the opening of this PS" }, + { "RO", "Ce împuterniciri de partid/bloc au fost prezenți la deschiderea acestui PS" } + }, + Helptext = + new TranslatedString + { + { "EN", "Please select as many you want" }, + { "RO", "Vă rugăm să selectați câte doriți" } + }, + Options = new List + { + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 1" }, { "RO", "Bloc 1" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 2" }, { "RO", "Bloc 2" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Bloc 3" }, { "RO", "Bloc 3" } }, + IsFreeText = false, + IsFlagged = false + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Party 1" }, { "RO", "Party 1" } }, + IsFreeText = false, + IsFlagged = true + }, + new() + { + Id = Guid.NewGuid(), + Text = new TranslatedString { { "EN", "Other" }, { "RO", "Other" } }, + IsFreeText = true, + IsFlagged = true + } + } + } + ] + }; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs new file mode 100644 index 000000000..3b2c53970 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs @@ -0,0 +1,90 @@ +using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ElectionRoundScenarioBuilder +{ + public Guid ElectionRoundId { get; } + + private readonly Dictionary _pollingStations = new(); + private readonly Dictionary _monitoringNgos = new(); + private readonly Dictionary _coalitions = new(); + + private readonly HttpClient _platformAdmin; + public readonly ScenarioBuilder ParentBuilder; + + public ElectionRoundScenarioBuilder WithPollingStation(string name) + { + var pollingStation = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/polling-stations", + new + { + Level1 = Guid.NewGuid().ToString(), + Number = "1", + DisplayOrder = 1, + Address = "Address", + Tags = new { } + }); + + _pollingStations[name] = pollingStation.Id; + return this; + } + + public ElectionRoundScenarioBuilder(ScenarioBuilder parentBuilder, + Guid electionRoundId, + HttpClient platformAdmin) + { + ParentBuilder = parentBuilder; + ElectionRoundId = electionRoundId; + _platformAdmin = platformAdmin; + } + + public ElectionRoundScenarioBuilder WithMonitoringNgo(string ngo, + Action? cfg = null) + { + var monitoringNgo = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/monitoring-ngos", + new { ngoId = ParentBuilder.NgoIdByName(ngo) }); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ParentBuilder.NgoByName(ngo)); + + cfg?.Invoke(monitoringNgoScenarioBuilder); + + _monitoringNgos.Add(ngo, monitoringNgoScenarioBuilder); + + return this; + } + + public ElectionRoundScenarioBuilder WithCoalition(string name, string leader, string[] members, + Action? cfg = null) + { + var coalition = _platformAdmin.PostWithResponse( + $"/api/election-rounds/{ElectionRoundId}/coalitions", + new + { + CoalitionName = Guid.NewGuid().ToString(), + LeaderId = ParentBuilder.NgoIdByName(leader), + NgoMembersIds = members.Select(member => ParentBuilder.NgoIdByName(member)).ToArray(), + }); + + var coalitionScenarioBuilder = new CoalitionScenarioBuilder(_platformAdmin, ParentBuilder.NgoByName(leader).Admin, this, coalition); + cfg?.Invoke(coalitionScenarioBuilder); + _coalitions.Add(name, coalitionScenarioBuilder); + return this; + } + + public MonitoringNgoScenarioBuilder MonitoringNgo => _monitoringNgos.First().Value; + public MonitoringNgoScenarioBuilder MonitoringNgoByName(string name) => _monitoringNgos[name]; + public Guid MonitoringNgoIdByName(string name) => _monitoringNgos[name].MonitoringNgoId; + public CoalitionScenarioBuilder Coalition => _coalitions.First().Value; + public Guid CoalitionId => _coalitions.First().Value.CoalitionId; + public CoalitionScenarioBuilder CoalitionByName(string name) => _coalitions[name]; + public Guid CoalitionIdByName(string name) => _coalitions[name].CoalitionId; + + public Guid PollingStation => _pollingStations.First().Value; + + public Guid PollingStationByName(string pollingStationName) => _pollingStations[pollingStationName]; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs new file mode 100644 index 000000000..e33310b03 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -0,0 +1,58 @@ +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class MonitoringNgoFormScenarioBuilder +{ + public readonly MonitoringNgoScenarioBuilder ParentBuilder; + + private readonly CreateFormRequest _form; + public Guid Id => _form.Id; + + private bool _formIsPublished = false; + + public MonitoringNgoFormScenarioBuilder( + MonitoringNgoScenarioBuilder parentBuilder, + CreateFormRequest form) + { + ParentBuilder = parentBuilder; + _form = form; + } + + + public MonitoringNgoFormScenarioBuilder Publish(string? adminEmail = null) + { + var admin = adminEmail is not null + ? ParentBuilder.NgoScenario.AdminByName(adminEmail) + : ParentBuilder.NgoScenario.Admin; + + _formIsPublished = true; + + admin + .PostAsync($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{_form.Id}:publish", null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + return this; + } + + public MonitoringNgoFormScenarioBuilder WithSubmission(string observerEmail, + string pollingStationName) + { + if (!_formIsPublished) + { + throw new ArgumentException("Form is not published"); + } + + var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStationName); + var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); + + var observer = ParentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observerEmail); + + observer.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/form-submissions", + submission); + return this; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs new file mode 100644 index 000000000..ada5a9cdd --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -0,0 +1,67 @@ +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class MonitoringNgoScenarioBuilder +{ + public Guid ElectionRoundId { get; } + private readonly Dictionary _forms = new(); + private readonly Dictionary _monitoringObservers = new(); + public readonly Guid MonitoringNgoId; + public readonly ElectionRoundScenarioBuilder ParentBuilder; + private readonly HttpClient _platformAdmin; + public NgoScenarioBuilder NgoScenario { get; } + public Guid FormId => _forms.First().Value.Id; + + public MonitoringNgoScenarioBuilder(Guid electionRoundId, + Guid monitoringNgoId, + ElectionRoundScenarioBuilder parentBuilder, + HttpClient platformAdmin, + NgoScenarioBuilder ngoScenario) + { + ElectionRoundId = electionRoundId; + MonitoringNgoId = monitoringNgoId; + ParentBuilder = parentBuilder; + _platformAdmin = platformAdmin; + NgoScenario = ngoScenario; + } + + public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, + Action? cfAction = null) + { + formCode ??= Guid.NewGuid().ToString(); + var formRequest = Dummy.Form(); + var admin = NgoScenario.Admin; + + var ngoForm = + admin.PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", + formRequest); + + admin + .PostAsync($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms/{ngoForm.Id}:publish", + null) + .GetAwaiter().GetResult() + .EnsureSuccessStatusCode(); + + var monitoringNgoFormScenarioBuilder = new MonitoringNgoFormScenarioBuilder(this, ngoForm); + cfAction?.Invoke(monitoringNgoFormScenarioBuilder); + + _forms.Add(formCode, monitoringNgoFormScenarioBuilder); + return this; + } + + public MonitoringNgoScenarioBuilder WithMonitoringObserver(string observerEmail) + { + var observer = ParentBuilder.ParentBuilder.ObserverByName(observerEmail); + var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observerEmail); + + _platformAdmin + .PostWithResponse( + $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{MonitoringNgoId}/monitoring-observers" + , new { observerId = observerId }); + + _monitoringObservers.Add(observerEmail, observer); + return this; + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs new file mode 100644 index 000000000..35dbbe8d6 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/NgoScenarioBuilder.cs @@ -0,0 +1,37 @@ +using Vote.Monitor.Api.IntegrationTests.Models; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class NgoScenarioBuilder +{ + private readonly Dictionary _admins = new(); + private readonly HttpClient _platformAdmin; + private readonly Func _clientFactory; + public Guid NgoId { get; } + + public NgoScenarioBuilder(HttpClient platformAdmin, + Func clientFactory, + Guid ngoId) + { + _platformAdmin = platformAdmin; + _clientFactory = clientFactory; + NgoId = ngoId; + } + + public NgoScenarioBuilder WithAdmin(string? adminEmail = null) + { + adminEmail ??= Guid.NewGuid().ToString("N"); + var realEmail = $"{Guid.NewGuid()}@example.org"; + _platformAdmin.PostWithResponse($"/api/ngos/{NgoId}/admins", + new { FirstName = "Observer", LastName = adminEmail, Email = realEmail, Password = "string" }); + + var adminClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + _admins.Add(adminEmail, adminClient); + return this; + } + + public HttpClient Admin => _admins.First().Value; + + public HttpClient AdminByName(string name) => _admins[name]; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs new file mode 100644 index 000000000..f49c9e920 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs @@ -0,0 +1,114 @@ +using Vote.Monitor.Api.Feature.Ngo; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Domain.Constants; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ScenarioBuilder +{ + public HttpClient PlatformAdmin { get; } + private readonly Func _clientFactory; + private readonly Dictionary _electionRounds = new(); + private readonly Dictionary _ngos = new(); + private readonly Dictionary _observers = new(); + + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(string name) => _electionRounds[name]; + public Guid ElectionRoundIdByName(string name) => _electionRounds[name].ElectionRoundId; + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public HttpClient ObserverByName(string name) => _observers[name].Client; + public Guid ObserverIdByName(string name) => _observers[name].Id; + + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(string name) => _ngos[name]; + public Guid NgoIdByName(string name) => _ngos[name].NgoId; + + public static ScenarioBuilder New(Func clientFactory) + { + return new ScenarioBuilder(clientFactory); + } + + private ScenarioBuilder(Func clientFactory) + { + PlatformAdmin = clientFactory.NewForAuthenticatedUser(CustomWebApplicationFactory.AdminEmail, + CustomWebApplicationFactory.AdminPassword); + _clientFactory = clientFactory; + } + + public ScenarioBuilder WithNgo(string ngoName, Action? cfg = null) + { + var ngo = PlatformAdmin.PostWithResponse("/api/ngos", new { name = Guid.NewGuid().ToString() }); + + var ngoScenarioBuilder = new NgoScenarioBuilder(PlatformAdmin, _clientFactory, ngo.Id); + ngoScenarioBuilder.WithAdmin(); + + cfg?.Invoke(ngoScenarioBuilder); + _ngos.Add(ngoName, ngoScenarioBuilder); + return this; + } + + public ScenarioBuilder WithElectionRound(string name) + { + var electionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + new + { + CountryId = CountriesList.RO.Id, + Title = name + Guid.NewGuid(), + EnglishTitle = name, + StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(69) + }); + + + var electionRoundScenarioBuilder = + new ElectionRoundScenarioBuilder(this, electionRound.Id, PlatformAdmin); + + _electionRounds.Add(name, electionRoundScenarioBuilder); + + return this; + } + + public ScenarioBuilder WithElectionRound(string name, Action cfg) + { + var electionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + new + { + countryId = CountriesList.RO.Id, + title = name + Guid.NewGuid(), + englishTitle = name, + StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(69) + }); + + + var electionRoundScenarioBuilder = + new ElectionRoundScenarioBuilder(this, electionRound.Id, PlatformAdmin); + + cfg(electionRoundScenarioBuilder); + + _electionRounds.Add(name, electionRoundScenarioBuilder); + + return this; + } + + public ScenarioBuilder WithObserver(string observerEmail) + { + var realEmail = $"{Guid.NewGuid()}@example.org"; + var observer = PlatformAdmin.PostWithResponse("/api/observers", + new { FirstName = "Observer", LastName = observerEmail, Email = realEmail, Password = "string" }); + + var observerClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); + + _observers.Add(observerEmail, (observer.Id, observerClient)); + return this; + } + + public ScenarioData Please() + { + return new(PlatformAdmin, _electionRounds, _ngos, _observers); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs new file mode 100644 index 000000000..79cb9d57a --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs @@ -0,0 +1,36 @@ +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; + +public class ScenarioData +{ + public HttpClient PlatformAdmin { get; } + private IReadOnlyDictionary _electionRounds; + private IReadOnlyDictionary _ngos; + private IReadOnlyDictionary _observers; + + public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary electionRounds, + IReadOnlyDictionary ngos, + IReadOnlyDictionary observers) + { + PlatformAdmin = platformAdmin; + _electionRounds = electionRounds; + _ngos = ngos; + _observers = observers; + } + + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; + public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(string name) => _electionRounds[name]; + public Guid ElectionRoundIdByName(string name) => _electionRounds[name].ElectionRoundId; + public HttpClient Observer => _observers.First().Value.Client; + public Guid ObserverId => _observers.First().Value.Id; + + public HttpClient ObserverByName(string name) => _observers[name].Client; + public Guid ObserverIdByName(string name) => _observers[name].Id; + + public NgoScenarioBuilder Ngo => _ngos.First().Value; + public Guid NgoId => _ngos.First().Value.NgoId; + public NgoScenarioBuilder NgoByName(string name) => _ngos[name]; + public HttpClient AdminOfNgo(string name) => _ngos[name].Admin; + public Guid NgoIdByName(string name) => _ngos[name].NgoId; +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Services/NoopCurrentUserProvider.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Services/NoopCurrentUserProvider.cs new file mode 100644 index 000000000..e7ee4e05d --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Services/NoopCurrentUserProvider.cs @@ -0,0 +1,23 @@ +using System.Security.Claims; +using Vote.Monitor.Core.Services.Security; + +namespace Vote.Monitor.Api.IntegrationTests.Services; + +public class NoopCurrentUserProvider : ICurrentUserProvider +{ + public ClaimsPrincipal? User { get; } + public Guid? GetUserId() + { + throw new NotImplementedException(); + } + + public Guid? GetNgoId() + { + throw new NotImplementedException(); + } + + public string? GetClaimValue(string type) + { + throw new NotImplementedException(); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj index 100f4a6dc..b6c9eca76 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj @@ -9,16 +9,48 @@ - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + PreserveNewest + + + + + + ..\..\..\..\..\.nuget\packages\nsubstitute\5.1.0\lib\net6.0\NSubstitute.dll + diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json b/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json new file mode 100644 index 000000000..654a3c10f --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=vote-monitor;Username=postgres;Password=docker;Include Error Detail=True" + } +} diff --git a/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs b/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs index 679369ed2..7986b1cf5 100644 --- a/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs +++ b/api/tests/Vote.Monitor.Core.UnitTests/LanguagesTranslationStatusTests.cs @@ -7,13 +7,13 @@ public class LanguagesTranslationStatusTests private readonly LanguagesTranslationStatus _first = new LanguagesTranslationStatus { ["Ro"] = TranslationStatus.Translated, - ["En"] = TranslationStatus.MissingTranslations, + ["En"] = TranslationStatus.MissingTranslations }; private readonly LanguagesTranslationStatus _second = new LanguagesTranslationStatus { ["Ro"] = TranslationStatus.Translated, - ["En"] = TranslationStatus.MissingTranslations, + ["En"] = TranslationStatus.MissingTranslations }; diff --git a/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs b/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs index 5fa9dfe60..612a7cc05 100644 --- a/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs +++ b/api/tests/Vote.Monitor.Core.UnitTests/TranslatedStringTests.cs @@ -40,7 +40,7 @@ public void ComparingToATranslatedString_WithDifferentProperties_ReturnsFalse(Tr new object[] { new TranslatedString { { "EN", "Some text" }, { "RO", "Other text" } }, new TranslatedString { { "EN", "Some text" }, { "RO", "Other different text" } } }, new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "EN", "Some text" }, { "RO", "Other different text" } } }, new object[] { new TranslatedString { { "EN", "Some text" }, { "RO", "Other text" } }, new TranslatedString { { "EN", "Some text" } } }, - new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "RO", "Other different text" } } }, + new object[] { new TranslatedString { { "EN", "Some text" } }, new TranslatedString { { "RO", "Other different text" } } } }; } diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs index d0b7998df..0a74cae0a 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/BaseFormTests.cs @@ -99,6 +99,6 @@ public void CreatePollingStationInformation_ShouldUpdateTimeOfStays_Correctly( { ValueOrUndefined.Some(DateTime.Now), ValueOrUndefined.Some(DateTime.Now.AddDays(3)), Answers, new List() - }, + } }; } \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs index c0cc52792..233d7ea93 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.AddTranslations.cs @@ -80,7 +80,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForEachQuestion() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, @@ -133,7 +133,7 @@ public void WhenAddingTranslations_ThenRecomputesLanguagesTranslationsStatus() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs index fc069a002..7de59d886 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Duplicate.cs @@ -13,7 +13,7 @@ public void WhenDuplicate_ThenCreatesNewDraftedFormTemplate() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs index 71065f187..f55435635 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.RemoveTranslation.cs @@ -63,7 +63,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForEachQuestion() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, @@ -112,7 +112,7 @@ public void WhenRemovingTranslation_ThenRecomputesLanguagesTranslationsStatus() new DateQuestionFaker(_languages).Generate(), new RatingQuestionFaker(languageList: _languages).Generate(), new SingleSelectQuestionFaker(languageList: _languages).Generate(), - new MultiSelectQuestionFaker(languageList: _languages).Generate(), + new MultiSelectQuestionFaker(languageList: _languages).Generate() ]; var form = Form.Create(Guid.NewGuid(), Guid.NewGuid(), FormType.Voting, "code", _name, _description, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs index 411185ff1..c9e7439aa 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTestsTestData.cs @@ -54,7 +54,7 @@ static FormTestsTestData() private static readonly SelectOption[] Options = [ new(Guid.NewGuid(), Option1Text, false, false), - new(Guid.NewGuid(), Option2Text, false, true), + new(Guid.NewGuid(), Option2Text, false, true) ]; private static readonly TextQuestion PartiallyTranslatedTextQuestion = @@ -92,6 +92,6 @@ static FormTestsTestData() PartiallyTranslatedSingleSelectQuestion, PartiallyTranslatedMultiSelectQuestion } - }, + } }; } \ No newline at end of file diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs index 1c922c429..674b26dfb 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregateTests.cs @@ -26,7 +26,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), + SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()) ]; List multiSelectOptions = @@ -34,7 +34,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(regularOptionId, new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true), + SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true) ]; var singleSelectQuestion = @@ -44,7 +44,7 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() var questions = new BaseQuestion[] { singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -59,8 +59,8 @@ public void WhenCreatingSubmission_ShouldCountFlaggedAnswers() new MultiSelectAnswer(multiSelectQuestion.Id, [ SelectedOption.Create(flaggedOptionId1, ""), SelectedOption.Create(flaggedOptionId2, ""), - SelectedOption.Create(regularOptionId, ""), - ]), + SelectedOption.Create(regularOptionId, "") + ]) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, answers, false); @@ -88,7 +88,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), + SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()) ]; List multiSelectOptions = @@ -96,7 +96,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() SelectOption.Create(flaggedOptionId1, new TranslatedStringFaker(languages).Generate(), false, true), SelectOption.Create(Guid.NewGuid(), new TranslatedStringFaker(languages).Generate()), SelectOption.Create(regularOptionId, new TranslatedStringFaker(languages).Generate()), - SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true), + SelectOption.Create(flaggedOptionId2, new TranslatedStringFaker(languages).Generate(), false, true) ]; var singleSelectQuestion = @@ -106,7 +106,7 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() var questions = new BaseQuestion[] { singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -127,8 +127,8 @@ public void WhenFillingInSubmission_ShouldUpdateFlaggedAnswers() new MultiSelectAnswer(multiSelectQuestion.Id, [ SelectedOption.Create(flaggedOptionId1, ""), SelectedOption.Create(flaggedOptionId2, ""), - SelectedOption.Create(regularOptionId, ""), - ]), + SelectedOption.Create(regularOptionId, "") + ]) ]; form.FillIn(submission, updatedAnswers, false); @@ -161,7 +161,7 @@ public void WhenCreatingSubmission_ShouldCountQuestionsAnswered() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -175,7 +175,7 @@ public void WhenCreatingSubmission_ShouldCountQuestionsAnswered() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; // Act @@ -209,7 +209,7 @@ public void WhenFillingInSubmission_ShouldUpdateQuestionsAnswered() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -231,7 +231,7 @@ public void WhenFillingInSubmission_ShouldUpdateQuestionsAnswered() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; form.FillIn(submission, updatedAnswers, false); @@ -264,7 +264,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldClearAnswers() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -278,7 +278,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldClearAnswers() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, initialAnswers, false); @@ -316,7 +316,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldStayTheSame() ratingQuestion, numberQuestion, singleSelectQuestion, - multiSelectQuestion, + multiSelectQuestion }; var form = Form.Create(electionRound, monitoringNgo, FormType.ClosingAndCounting, "", @@ -329,7 +329,7 @@ public void WhenFillingInSubmission_EmptyAnswers_ShouldStayTheSame() new TextAnswerFaker(textQuestion.Id), new NumberAnswerFaker(numberQuestion.Id), new RatingAnswerFaker(ratingQuestion.Id), - new DateAnswerFaker(dateQuestion.Id), + new DateAnswerFaker(dateQuestion.Id) ]; var submission = form.CreateFormSubmission(pollingStation, monitoringObserver, initialAnswers, false); diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs index 3625aa9f1..eb39627e7 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs @@ -1,5 +1,4 @@ using Vote.Monitor.Core.Helpers; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; @@ -103,7 +102,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForEachQuestion() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs index 87e302e9c..e3f5ddecb 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs @@ -1,5 +1,4 @@ using FluentValidation; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs index 5d115286c..7fac90802 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs @@ -19,7 +19,7 @@ public void WhenDuplicate_ThenCreatesNewDraftedFormTemplate() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs index fbcbffe53..5b0da10e3 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs @@ -1,5 +1,4 @@ using Vote.Monitor.Core.Helpers; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; @@ -81,7 +80,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForEachQuestion() new DateQuestionFaker(languages).Generate(), new RatingQuestionFaker(languageList: languages).Generate(), new SingleSelectQuestionFaker(languageList: languages).Generate(), - new MultiSelectQuestionFaker(languageList: languages).Generate(), + new MultiSelectQuestionFaker(languageList: languages).Generate() ]; var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs index 4f1cbdece..6374683ca 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTests.cs @@ -1,8 +1,4 @@ -using Vote.Monitor.Core.Helpers; -using Vote.Monitor.Domain.Entities.FormTemplateAggregate; -using Vote.Monitor.TestUtils.Fakes.Aggregates; - -namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; +namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests { diff --git a/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs b/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs index 2cea57b81..4589667ad 100644 --- a/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs +++ b/api/tests/Vote.Monitor.Form.Module.UnitTests/QuestionsMapperTests.cs @@ -79,7 +79,7 @@ public QuestionsMapperTests() new(Guid.NewGuid(), _option1Text, false, false), new(Guid.NewGuid(), _option2Text, false, true), new(Guid.NewGuid(), _option3Text, true, false), - new(Guid.NewGuid(), _option4Text, true, true), + new(Guid.NewGuid(), _option4Text, true, true) ]; _requestOptions = @@ -111,7 +111,7 @@ public QuestionsMapperTests() Text = _option4Text, IsFlagged = true, IsFreeText = true - }, + } ]; } @@ -294,7 +294,7 @@ public void ToEntity_ShouldReturnNumberQuestion_WhenGivenDateQuestionModel() Id = Guid.NewGuid(), Code = _code, Text = _text, - Helptext = _helptext, + Helptext = _helptext }; // Act var result = QuestionsMapper.ToEntity(dateQuestionRequest); diff --git a/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs b/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs index 4a54412e2..23d175026 100644 --- a/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs +++ b/api/tests/Vote.Monitor.Form.Module.UnitTests/Validators/ValidatorsTestData.cs @@ -64,7 +64,7 @@ public class ValidatorsTestData new List { new object?[] { new DisplayLogicRequest{ParentQuestionId= Guid.NewGuid(), Condition = DisplayLogicCondition.GreaterEqual, Value = "1"} }, - new object?[] { null }, + new object?[] { null } }; diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs index 33aa865ed..ed3f7d1d7 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormAggregateFaker.cs @@ -32,7 +32,7 @@ public FormAggregateFaker(ElectionRoundAggregate? electionRound = null, new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; SelectOption[] multiSelectOptions = @@ -40,7 +40,7 @@ public FormAggregateFaker(ElectionRoundAggregate? electionRound = null, new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, false), new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + new SelectOption(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; questions ??= diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs index 0fc66d2c7..702367132 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/PollingStationInformationFormFaker.cs @@ -27,7 +27,7 @@ public PollingStationInformationFormFaker(ElectionRoundAggregate? electionRound SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate()), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; SelectOption[] multiSelectOptions = @@ -35,7 +35,7 @@ public PollingStationInformationFormFaker(ElectionRoundAggregate? electionRound SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate()), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true), SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), false, true), - SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true), + SelectOption.Create(Guid.NewGuid(), translatedStringFaker.Generate(), true, true) ]; questions ??= diff --git a/api/tests/Vote.Monitor.TestUtils/TestData.cs b/api/tests/Vote.Monitor.TestUtils/TestData.cs index 45bff9980..fdc917f74 100644 --- a/api/tests/Vote.Monitor.TestUtils/TestData.cs +++ b/api/tests/Vote.Monitor.TestUtils/TestData.cs @@ -24,6 +24,6 @@ public class TestData new List { new object[] { Guid.Empty }, - new object[] { null as Guid? }, + new object[] { null as Guid? } }; } diff --git a/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs b/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs index 68ea7293d..e79afc7b6 100644 --- a/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs +++ b/utils/SubmissionsFaker/Clients/Citizen/ICitizenApi.cs @@ -21,8 +21,9 @@ Task SubmitForm( // [AliasAs("QuestionId")] string questionId, // [AliasAs("Attachment")] StreamPart attachment); - [Post("/api/election-rounds/{electionRoundId}/citizen-report-notes")] + [Post("/api/election-rounds/{electionRoundId}/citizen-reports/{citizenReportId}/notes")] Task SubmitNote( [AliasAs("electionRoundId")] string electionRoundId, + [AliasAs("citizenReportId")] string citizenReportId, [Body] CitizenReportNoteRequest note); } \ No newline at end of file diff --git a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs index a91670941..f34885eed 100644 --- a/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs +++ b/utils/SubmissionsFaker/Clients/MonitoringObserver/Models/IncidentReportRequest.cs @@ -3,7 +3,7 @@ namespace SubmissionsFaker.Clients.MonitoringObserver.Models; public class IncidentReportRequest { public string ObserverToken { get; set; } - public Guid IncidentReportId { get; set; } + public Guid Id { get; set; } public string FormId { get; set; } public string LocationType { get; set; } diff --git a/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs b/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs index 882fb3ba0..be1fb9a5a 100644 --- a/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs +++ b/utils/SubmissionsFaker/Fakers/IncidentReportFaker.cs @@ -13,7 +13,7 @@ public sealed class IncidentReportFaker : Faker public IncidentReportFaker(List forms,List pollingStations, List observers) { - RuleFor(x => x.IncidentReportId, f => f.Random.Guid()); + RuleFor(x => x.Id, f => f.Random.Guid()); RuleFor(x => x.ObserverToken, f => f.PickRandom(observers).Token); Rules((f, x) => diff --git a/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs b/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs index 568b84eed..9d7eb0eb1 100644 --- a/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs +++ b/utils/SubmissionsFaker/Forms/CitizenReportingFormData.cs @@ -20,7 +20,7 @@ public class CitizenReportingFormData { "EN", "test form" }, { "RO", "formular de test" } }, - FormType = "Opening", + FormType = "CitizenReporting", Questions = [ new NumberQuestionRequest diff --git a/utils/SubmissionsFaker/Program.cs b/utils/SubmissionsFaker/Program.cs index 0f71035dc..9bf941103 100644 --- a/utils/SubmissionsFaker/Program.cs +++ b/utils/SubmissionsFaker/Program.cs @@ -401,7 +401,7 @@ await AnsiConsole.Progress() foreach (var notesChunk in citizenReportNoteRequests.Chunk(Consts.CHUNK_SIZE)) { - var tasks = notesChunk.Select(n => citizenReportApi.SubmitNote(electionRound.Id, n)); + var tasks = notesChunk.Select(n => citizenReportApi.SubmitNote(electionRound.Id, n.CitizenReportId, n)); await Task.WhenAll(tasks); progressTask.Increment(Consts.CHUNK_SIZE); } diff --git a/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs b/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs index 0c9a9a413..d4457f618 100644 --- a/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/CitizenReportingFormSeeder.cs @@ -16,11 +16,11 @@ public static async Task> Seed(INgoAdminApi ngoAdminApi progressTask.MaxValue(1); progressTask.StartTask(); - var openingForm = await ngoAdminApi.CreateForm(electionRoundId, + var citizenReporting = await ngoAdminApi.CreateForm(electionRoundId, CitizenReportingFormData.CitizenReporting with { FormType = "CitizenReporting" }, ngoAdminToken.Token); - await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, CitizenReportingFormData.CitizenReporting, + await ngoAdminApi.UpdateForm(electionRoundId, citizenReporting.Id, CitizenReportingFormData.CitizenReporting, ngoAdminToken.Token); - await ngoAdminApi.PublishForm(electionRoundId, openingForm.Id, ngoAdminToken.Token); + await ngoAdminApi.PublishForm(electionRoundId, citizenReporting.Id, ngoAdminToken.Token); progressTask.Increment(1); progressTask.Increment(progressTask.MaxValue); @@ -30,7 +30,7 @@ await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, CitizenReportingFo [ new UpdateFormResponse { - Id = openingForm.Id, + Id = citizenReporting.Id, Questions = CitizenReportingFormData.CitizenReporting.Questions, }, // ... diff --git a/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs b/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs index 36ecf6e74..9246e9c29 100644 --- a/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs +++ b/utils/SubmissionsFaker/Seeders/IncidentReportingFormSeeder.cs @@ -16,11 +16,11 @@ public static async Task> Seed(INgoAdminApi ngoAdminApi progressTask.MaxValue(1); progressTask.StartTask(); - var openingForm = await ngoAdminApi.CreateForm(electionRoundId, + var incidentReportingForm = await ngoAdminApi.CreateForm(electionRoundId, IncidentReportingFormData.IncidentReporting with { FormType = "IncidentReporting" }, ngoAdminToken.Token); - await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, IncidentReportingFormData.IncidentReporting, + await ngoAdminApi.UpdateForm(electionRoundId, incidentReportingForm.Id, IncidentReportingFormData.IncidentReporting, ngoAdminToken.Token); - await ngoAdminApi.PublishForm(electionRoundId, openingForm.Id, ngoAdminToken.Token); + await ngoAdminApi.PublishForm(electionRoundId, incidentReportingForm.Id, ngoAdminToken.Token); progressTask.Increment(1); progressTask.Increment(progressTask.MaxValue); @@ -30,7 +30,7 @@ await ngoAdminApi.UpdateForm(electionRoundId, openingForm.Id, IncidentReportingF [ new UpdateFormResponse { - Id = openingForm.Id, + Id = incidentReportingForm.Id, Questions = IncidentReportingFormData.IncidentReporting.Questions, }, // ... diff --git a/utils/SubmissionsFaker/SubmissionsFaker.csproj b/utils/SubmissionsFaker/SubmissionsFaker.csproj index 08c997908..6c11aab9d 100644 --- a/utils/SubmissionsFaker/SubmissionsFaker.csproj +++ b/utils/SubmissionsFaker/SubmissionsFaker.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 833ea06b0..a5ffe9139 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -2302,8 +2302,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -2490,8 +2490,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001599: - resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} + caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} @@ -4616,8 +4616,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -7092,7 +7092,7 @@ snapshots: acorn@8.11.3: {} - acorn@8.12.1: + acorn@8.14.0: optional: true agent-base@6.0.2: @@ -7220,7 +7220,7 @@ snapshots: autoprefixer@10.4.14(postcss@8.4.27): dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001599 + caniuse-lite: 1.0.30001677 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -7264,7 +7264,7 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001599 + caniuse-lite: 1.0.30001677 electron-to-chromium: 1.4.713 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -7298,7 +7298,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001599: {} + caniuse-lite@1.0.30001677: {} chai@4.4.1: dependencies: @@ -8514,7 +8514,7 @@ snapshots: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.7.0 + tslib: 2.8.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -9593,7 +9593,7 @@ snapshots: terser@5.29.2: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 optional: true @@ -9685,7 +9685,7 @@ snapshots: tslib@2.6.2: {} - tslib@2.7.0: + tslib@2.8.1: optional: true type-check@0.4.0: diff --git a/web/src/common/prev-data-source-store.ts b/web/src/common/prev-data-source-store.ts new file mode 100644 index 000000000..3f530d394 --- /dev/null +++ b/web/src/common/prev-data-source-store.ts @@ -0,0 +1,23 @@ +import { DataSources } from './types'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +type PrevDataSourceStore = { + dataSource: DataSources; + setDataSource: (dataSource: DataSources) => void; +}; + +const usePrevDataSourceStore = create( + persist( + (set) => ({ + dataSource: DataSources.MyNgo, + setDataSource: (dataSource: DataSources) => { + set({ dataSource }); + }, + }), + { name: 'data-source' } + ) +); + +export const usePrevDataSource = () => usePrevDataSourceStore((state) => state.dataSource); +export const useSetPrevDataSource = () => usePrevDataSourceStore((state) => state.setDataSource); diff --git a/web/src/common/types.ts b/web/src/common/types.ts index b62d16787..05c532036 100644 --- a/web/src/common/types.ts +++ b/web/src/common/types.ts @@ -189,6 +189,7 @@ export type ElectionRoundMonitoring = { country: string; countryId: string; isMonitoringNgoForCitizenReporting: boolean; + isCoalitionLeader: boolean; }; export type LevelNode = { @@ -235,7 +236,15 @@ export type HistogramData = { [bucket: string]: number; }; -export const ZFormType = z.enum(['PSI', 'Opening', 'Voting', 'ClosingAndCounting', 'CitizenReporting', 'IncidentReporting','Other']); +export const ZFormType = z.enum([ + 'PSI', + 'Opening', + 'Voting', + 'ClosingAndCounting', + 'CitizenReporting', + 'IncidentReporting', + 'Other', +]); export type FormType = z.infer; @@ -245,7 +254,6 @@ export type TranslationStatus = z.infer; const ZLanguagesTranslationStatus = z.record(z.string(), ZTranslationStatus); export type LanguagesTranslationStatus = z.infer; - export interface Country { id: string; iso2: string; @@ -260,4 +268,22 @@ export interface Language { code: string; name: string; nativeName: string; -} \ No newline at end of file +} + +export interface CoalitionMember { + id: string; + name: string; +} +export interface Coalition { + id: string; + name: string; + leaderId: string; + leaderName: string; + numberOfMembers: number; + members: CoalitionMember[]; +} + +export enum DataSources { + MyNgo = 'myNgo', + Coalition = 'coalition', +} diff --git a/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx new file mode 100644 index 000000000..04c7b975e --- /dev/null +++ b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx @@ -0,0 +1,62 @@ +import { usePrevDataSource, useSetPrevDataSource } from '@/common/prev-data-source-store'; +import { DataSources, type FunctionComponent } from '@/common/types'; +import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { useNavigate, useSearch } from '@tanstack/react-router'; +import { useCallback, useEffect, useState } from 'react'; +import { Label } from '../ui/label'; +import { Switch } from '../ui/switch'; + +export function DataSourceSwitcher(): FunctionComponent { + const isCoalitionLeader = useCurrentElectionRoundStore((s) => s.isCoalitionLeader); + + const navigate = useNavigate(); + + const search: any = useSearch({ + strict: false, + }); + + const prevDataSource = usePrevDataSource(); + const setPrevDataSource = useSetPrevDataSource(); + + const navigateHandler = useCallback( + (dataSource: DataSources) => { + void navigate({ + search: (prev) => { + const newSearch: Record = { + ...prev, + dataSource, + }; + setPrevDataSource(dataSource); + return newSearch; + }, + }); + }, + [navigate, setPrevDataSource] + ); + const [isCoalition, setIsCoalition] = useState(false); + + useEffect(() => { + if (search.dataSource === undefined) { + navigateHandler(prevDataSource ?? DataSources.MyNgo); + return; + } + + setIsCoalition((search.dataSource ?? prevDataSource) === DataSources.Coalition); + }, [search.dataSource]); + + return isCoalitionLeader ? ( +
+ navigateHandler(checked ? DataSources.Coalition : DataSources.MyNgo)} + className='data-[state=checked]:bg-primary' + /> + +
+ ) : ( + <> + ); +} diff --git a/web/src/components/layout/Header/Header.tsx b/web/src/components/layout/Header/Header.tsx index 22d9992a1..14352d295 100644 --- a/web/src/components/layout/Header/Header.tsx +++ b/web/src/components/layout/Header/Header.tsx @@ -43,7 +43,7 @@ const Header = (): FunctionComponent => { const navigate = useNavigate(); const [selectedElectionRound, setSelectedElection] = useState(); const router = useRouter(); - const { setCurrentElectionRoundId, setIsMonitoringNgoForCitizenReporting, currentElectionRoundId } = + const { setCurrentElectionRoundId, setIsMonitoringNgoForCitizenReporting, currentElectionRoundId, isCoalitionLeader, setIsCoalitionLeader } = useCurrentElectionRoundStore((s) => s); const handleSelectElectionRound = async (electionRound?: ElectionRoundMonitoring): Promise => { @@ -51,6 +51,8 @@ const Header = (): FunctionComponent => { setSelectedElection(electionRound); setCurrentElectionRoundId(electionRound.electionRoundId); setIsMonitoringNgoForCitizenReporting(electionRound.isMonitoringNgoForCitizenReporting); + setIsCoalitionLeader(electionRound.isCoalitionLeader); + sleep(1); await queryClient.invalidateQueries({ diff --git a/web/src/context/election-round.store.tsx b/web/src/context/election-round.store.tsx index 16ecfc722..f4dfc7cdf 100644 --- a/web/src/context/election-round.store.tsx +++ b/web/src/context/election-round.store.tsx @@ -26,6 +26,9 @@ export type CurrentElectionRoundState = { isMonitoringNgoForCitizenReporting: boolean; setIsMonitoringNgoForCitizenReporting(isMonitoringNgoForCitizenReporting: boolean): void; + + isCoalitionLeader: boolean; + setIsCoalitionLeader(isCoalitionLeader: boolean): void; } export type CurrentElectionRoundStoreType = ZustandStore; @@ -43,7 +46,10 @@ export const CurrentElectionRoundStoreProvider = ({ children }: PropsWithChildre setCurrentElectionRoundId: (electionRoundId: string) => set({ currentElectionRoundId: electionRoundId }), isMonitoringNgoForCitizenReporting: false, - setIsMonitoringNgoForCitizenReporting: (isMonitoringNgoForCitizenReporting: boolean) => set({ isMonitoringNgoForCitizenReporting }) + setIsMonitoringNgoForCitizenReporting: (isMonitoringNgoForCitizenReporting: boolean) => set({ isMonitoringNgoForCitizenReporting }), + + isCoalitionLeader: false, + setIsCoalitionLeader: (isCoalitionLeader: boolean) => set({ isCoalitionLeader }), }), { name: 'current-election-round', // name of the item in the storage (must be unique), diff --git a/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx b/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx index 2933cb1ab..13a438b46 100644 --- a/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx +++ b/web/src/features/election-event/components/ElectionEventDetails/ElectionEventDetails.tsx @@ -6,52 +6,84 @@ import { cn } from '@/lib/utils'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { useTranslation } from 'react-i18next'; import { useElectionRoundDetails } from '../../hooks/election-event-hooks'; +import { useCoalitionDetails } from '../../hooks/coalition-hooks'; export default function ElectionEventDetails() { const { t } = useTranslation(); const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); const { data: electionEvent } = useElectionRoundDetails(currentElectionRoundId); + const { data: coalitionDetails } = useCoalitionDetails(currentElectionRoundId); return ( - - -
- - {t('electionEvent.eventDetails.cardTitle')} - -
- -
- -
-

{t('electionEvent.eventDetails.title')}

-

{electionEvent?.title}

-
-
-

{t('electionEvent.eventDetails.englishTitle')}

-

{electionEvent?.englishTitle}

-
-
-

{t('electionEvent.eventDetails.country')}

-

{electionEvent?.countryFullName}

-
-
-

{t('electionEvent.eventDetails.startDate')}

-

{electionEvent?.startDate}

-
+
+ + +
+ + {t('electionEvent.eventDetails.cardTitle')} + +
+ +
+ +
+

{t('electionEvent.eventDetails.title')}

+

{electionEvent?.title}

+
+
+

{t('electionEvent.eventDetails.englishTitle')}

+

{electionEvent?.englishTitle}

+
+
+

{t('electionEvent.eventDetails.country')}

+

{electionEvent?.countryFullName}

+
+
+

{t('electionEvent.eventDetails.startDate')}

+

{electionEvent?.startDate}

+
-
-

{t('electionEvent.eventDetails.status')}

- - {electionEvent?.status} - -
-
-
+
+

{t('electionEvent.eventDetails.status')}

+ + {electionEvent?.status} + +
+ + + {coalitionDetails && ( + + +
+ + {t('electionEvent.eventDetails.coalition.cardTitle')} + +
+ +
+ +
+

{t('electionEvent.eventDetails.coalition.coalitionName')}

+

{coalitionDetails.name}

+
+ +
+

{t('electionEvent.eventDetails.coalition.leaderName')}

+

{coalitionDetails.leaderName}

+
+ +
+

{t('electionEvent.eventDetails.coalition.members')}

+

{coalitionDetails?.members.map((m) => m.name).join(', ')}

+
+
+
+ )} +
); } diff --git a/web/src/features/election-event/hooks/coalition-hooks.ts b/web/src/features/election-event/hooks/coalition-hooks.ts new file mode 100644 index 000000000..97d498947 --- /dev/null +++ b/web/src/features/election-event/hooks/coalition-hooks.ts @@ -0,0 +1,26 @@ +import { authApi } from '@/common/auth-api'; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import { Coalition } from './../../../common/types'; + +const STALE_TIME = 1000 * 60 * 5; // five minutes + +export const coalitionKeys = { + all: (electionRoundId: string) => ['coalitions', electionRoundId] as const, +}; + +type CoalitionDetailResult = UseQueryResult; + +export function useCoalitionDetails(electionRoundId: string): CoalitionDetailResult { + return useQuery({ + queryKey: coalitionKeys.all(electionRoundId!), + queryFn: async () => { + const response = await authApi.get(`/election-rounds/${electionRoundId}/coalitions:my`); + + return { + ...response.data, + }; + }, + staleTime: STALE_TIME, + enabled: !!electionRoundId, + }); +} diff --git a/web/src/features/filtering/components/ActiveFilters.tsx b/web/src/features/filtering/components/ActiveFilters.tsx index 13fe5ffb1..e1cfacc3b 100644 --- a/web/src/features/filtering/components/ActiveFilters.tsx +++ b/web/src/features/filtering/components/ActiveFilters.tsx @@ -2,18 +2,16 @@ import { DateTimeFormat } from '@/common/formats'; import { FilterBadge } from '@/components/ui/badge'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { useFormSubmissionsFilters } from '@/features/responses/hooks/form-submissions-queries'; -import { useNavigate } from '@tanstack/react-router'; -import { format } from 'date-fns/format'; -import { FC, useCallback } from 'react'; -import { FILTER_KEY, FILTER_LABEL } from '../filtering-enums'; -import { isNotNilOrWhitespace, toBoolean } from '@/lib/utils'; import { mapFormSubmissionFollowUpStatus, mapIncidentCategory, - mapQuickReportFollowUpStatus, - mapQuickReportLocationType, + mapQuickReportFollowUpStatus } from '@/features/responses/utils/helpers'; -import { QuickReportFollowUpStatus } from '@/common/types'; +import { isNotNilOrWhitespace, toBoolean } from '@/lib/utils'; +import { useNavigate } from '@tanstack/react-router'; +import { format } from 'date-fns/format'; +import { FC, useCallback } from 'react'; +import { FILTER_KEY, FILTER_LABEL } from '../filtering-enums'; interface ActiveFilterProps { filterId: string; @@ -32,6 +30,7 @@ export const HIDDEN_FILTERS = [ FILTER_KEY.Tab, FILTER_KEY.SortOrder, FILTER_KEY.SortColumnName, + FILTER_KEY.DataSource, ]; const FILTER_LABELS = new Map([ diff --git a/web/src/features/filtering/filtering-enums.ts b/web/src/features/filtering/filtering-enums.ts index 13703a5ad..100928f63 100644 --- a/web/src/features/filtering/filtering-enums.ts +++ b/web/src/features/filtering/filtering-enums.ts @@ -29,6 +29,7 @@ export const enum FILTER_KEY { QuickReportIncidentCategory ='incidentCategory', QuickReportFollowUpStatus ='quickReportFollowUpStatus', HasQuickReports ='hasQuickReports', + DataSource ='dataSource', } export const enum FILTER_LABEL { diff --git a/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx b/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx index 48d9a6d4c..b82127db3 100644 --- a/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx +++ b/web/src/features/ngo-admin-dashboard/components/Dashboard/Dashboard.tsx @@ -28,6 +28,7 @@ import { useElectionRoundStatistics } from '../../hooks/statistics-queries'; import { HistogramEntry } from '../../models/ngo-admin-statistics-models'; import LevelStatistics from '../LevelStatisticsCard/LevelStatisticsCard'; import useDashboardExpandedChartsStore from './dashboard-config.store'; +import { DataSourceSwitcher } from '@/components/DataSourceSwitcher/DataSourceSwitcher'; export default function NgoAdminDashboard(): FunctionComponent { const { t } = useTranslation('translation', { keyPrefix: 'ngoAdminDashboard' }); @@ -63,7 +64,7 @@ export default function NgoAdminDashboard(): FunctionComponent { return (formsHistogram ?? []).reduce((acc, { value }) => acc + value, 0); }, []); - const saveChartCallback = useCallback((chartRef:any, fileName:string) => { + const saveChartCallback = useCallback((chartRef: any, fileName: string) => { if (chartRef?.current) { saveChart(chartRef, fileName); } @@ -76,267 +77,143 @@ export default function NgoAdminDashboard(): FunctionComponent { [] ); + const observersOnTheFieldData = useCallback( + (totalNumberOfObservers: number | undefined, numberOfObserversOnTheField?: number) => + observersOnTheFieldDataConfig(totalNumberOfObservers, numberOfObserversOnTheField ?? 0), + [] + ); return ( - -
-
-
- - - {t('observersAccounts.cardTitle')} - - - - - - - - - {t('observersOnFieldCardTitle')} - - - - - - - - - {t('pollingStationCardTitle')} - - - - - - - - - {t('timeSpentObserving.cardTitle')} - - - - - - + <> +
+
+

+ {t('title')} +

+
+

{t('subtitle')}

+
-
0 ? 'row dense' : 'unset', - }}> - - - {t('startedForms.cardTitle')} -
+
+
+
+
+
+
+ + + {t('observersAccounts.cardTitle')} - -
- - - - - - - - {t('questionsAnswered.cardTitle')} -
+ + + + + + + + {t('observersOnFieldCardTitle')} -
-
- - - -
- - - {t('flaggedAnswers.cardTitle')} -
+ value={statistics?.totalStats?.activeObservers ?? 0} + total={statistics?.observersStats?.totalNumberOfObservers ?? 0} + ref={observersOnFieldChartRef} + /> + + + + + {t('pollingStationCardTitle')} - -
-
- - - -
- - - {t('quickReports.cardTitle')} -
+ + + + + + + + {t('timeSpentObserving.cardTitle')} - -
-
- - - -
- {isMonitoringNgoForCitizenReporting && ( + + + + + +
+
0 ? 'row dense' : 'unset', + }}> - {t('citizenReports.cardTitle')} + {t('startedForms.cardTitle')}
- - -
-
- - - -
-
-
- - - Level 1 - {statistics?.level2Stats?.length ? Level 2 : null} - {statistics?.level3Stats?.length ? Level 3 : null} - {statistics?.level4Stats?.length ? Level 4 : null} - {statistics?.level5Stats?.length ? Level 5 : null} - - - - - + + {t('questionsAnswered.cardTitle')} +
+ + +
+
+ + + + + + + {t('flaggedAnswers.cardTitle')} +
+ + +
+
+ + + +
+ + + {t('quickReports.cardTitle')} +
+ + +
+
+ + + +
+ {isMonitoringNgoForCitizenReporting && ( + + + {t('citizenReports.cardTitle')} +
+ + +
+
+ + + +
+ )} + + + {t('incidentReports.cardTitle')} +
+ + +
+
+ + + +
+
+
+ + + Level 1 + {statistics?.level2Stats?.length ? Level 2 : null} + {statistics?.level3Stats?.length ? Level 3 : null} + {statistics?.level4Stats?.length ? Level 4 : null} + {statistics?.level5Stats?.length ? Level 5 : null} + - {statistics?.level2Stats?.length ? ( - - + + - ) : null} - {statistics?.level3Stats?.length ? ( - - - - ) : null} + {statistics?.level2Stats?.length ? ( + + + + ) : null} - {statistics?.level4Stats?.length ? ( - - - - ) : null} + {statistics?.level3Stats?.length ? ( + + + + ) : null} - {statistics?.level5Stats?.length ? ( - - - - ) : null} - + {statistics?.level4Stats?.length ? ( + + + + ) : null} + + {statistics?.level5Stats?.length ? ( + + + + ) : null} + +
-
- + + ); } diff --git a/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts b/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts index 45d86eef7..9c058ab32 100644 --- a/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts +++ b/web/src/features/ngo-admin-dashboard/hooks/statistics-queries.ts @@ -21,6 +21,7 @@ export function useElectionRoundStatistics( return response.data; }, + refetchOnMount: false, staleTime: STALE_TIME, }); } \ No newline at end of file diff --git a/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx b/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx index 40f5f372b..006dd1e22 100644 --- a/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx +++ b/web/src/features/responses/components/CitizenReportDetails/CitizenReportDetails.tsx @@ -14,6 +14,10 @@ import { citizenReportKeys } from '../../hooks/citizen-reports'; import PreviewAnswer from '../PreviewAnswer/PreviewAnswer'; import { SubmissionType } from '../../models/common'; import { mapCitizenReportFollowUpStatus } from '../../utils/helpers'; +import { usePrevSearch } from '@/common/prev-search-store'; +import { NavigateBack } from '@/components/NavigateBack/NavigateBack'; +import { DateTimeFormat } from '@/common/formats'; +import { format } from 'date-fns'; export default function CitizenReportDetails(): FunctionComponent { const { citizenReportId } = Route.useParams(); @@ -23,6 +27,7 @@ export default function CitizenReportDetails(): FunctionComponent { ); const router = useRouter(); + const prevSearch = usePrevSearch(); const updateSubmissionFollowUpStatusMutation = useMutation({ mutationKey: citizenReportKeys.detail(currentElectionRoundId, citizenReportId), @@ -62,8 +67,52 @@ export default function CitizenReportDetails(): FunctionComponent { } return ( - + }>
+ + +
+

Time submitted

+ {citizenReport.timeSubmitted &&

{format(citizenReport.timeSubmitted, DateTimeFormat)}

} +
+ + {citizenReport.level1 && ( +
+
+

Location - L1

+ {citizenReport.level1} +
+ {citizenReport.level2 && ( +
+

Location - L2

+ {citizenReport.level2} +
+ )} + {citizenReport.level3 && ( +
+

Location - L3

+ {citizenReport.level3} +
+ )} + {citizenReport.level4 && ( +
+

Location - L4

+ {citizenReport.level4} +
+ )} + {citizenReport.level5 && ( +
+

Location - L5

+ {citizenReport.level5} +
+ )} +
+ )} +
+
+ diff --git a/web/src/features/responses/components/CitizenReportsTab/CitizenReportsTab.tsx b/web/src/features/responses/components/CitizenReportsTab/CitizenReportsTab.tsx index 62a14162e..26549857d 100644 --- a/web/src/features/responses/components/CitizenReportsTab/CitizenReportsTab.tsx +++ b/web/src/features/responses/components/CitizenReportsTab/CitizenReportsTab.tsx @@ -39,7 +39,7 @@ export function CitizenReportsTab(): FunctionComponent { const setPrevSearch = useSetPrevSearch(); useEffect(() => { - if (byFilter === 'byObserver') { + if (byFilter === 'byEntry') { setPrevSearch({ [FILTER_KEY.ViewBy]: 'byEntry' }); void navigate({ search: { [FILTER_KEY.ViewBy]: 'byEntry' } }); } diff --git a/web/src/features/responses/components/Dashboard/Dashboard.tsx b/web/src/features/responses/components/Dashboard/Dashboard.tsx index 1397d3022..ae8db193c 100644 --- a/web/src/features/responses/components/Dashboard/Dashboard.tsx +++ b/web/src/features/responses/components/Dashboard/Dashboard.tsx @@ -1,68 +1,80 @@ import { useSetPrevSearch } from '@/common/prev-search-store'; -import Layout from '@/components/layout/Layout'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; -import { getRouteApi } from '@tanstack/react-router'; +import { cn } from '@/lib/utils'; +import { Route } from '@/routes/responses'; import { type ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; import { CitizenReportsTab } from '../CitizenReportsTab/CitizenReportsTab'; import FormSubmissionsTab from '../FormSubmissionsTab/FormSubmissionsTab'; -import { QuickReportsTab } from '../QuickReportsTab/QuickReportsTab'; -import { cn } from '@/lib/utils'; import IncidentReportsTab from '../IncidentReportsTab/IncidentReportsTab'; -import { useTranslation } from 'react-i18next'; - -const routeApi = getRouteApi('/responses/'); +import { QuickReportsTab } from '../QuickReportsTab/QuickReportsTab'; +import { useNavigate } from '@tanstack/react-router'; +import { DataSourceSwitcher } from '@/components/DataSourceSwitcher/DataSourceSwitcher'; export default function ResponsesDashboard(): ReactElement { const isMonitoringNgoForCitizenReporting = useCurrentElectionRoundStore((s) => s.isMonitoringNgoForCitizenReporting); - const navigate = routeApi.useNavigate(); - const search = routeApi.useSearch(); + const navigate = useNavigate(); + const search = Route.useSearch(); const { tab } = search; - const { t } = useTranslation(); + const { t } = useTranslation('translation', { keyPrefix: 'responses' }); const setPrevSearch = useSetPrevSearch(); return ( - - { - void navigate({ - search() { - const newSearch = { tab }; - setPrevSearch(newSearch); - return newSearch; - }, - }); - }}> - - Form answers - Quick reports - {isMonitoringNgoForCitizenReporting && Citizen reports} - Incident reports - - - - - + <> +
+
+

+ {t('title')} +

+
+

{t('subtitle')}

+ +
+
+
+
+ { + void navigate({ + search() { + const newSearch = { tab }; + setPrevSearch(newSearch); + return newSearch; + }, + }); + }}> + + Form answers + Quick reports + {isMonitoringNgoForCitizenReporting && Citizen reports} + Incident reports + - - - + + + - - - + + + - {isMonitoringNgoForCitizenReporting && ( - - + + - )} - - + + {isMonitoringNgoForCitizenReporting && ( + + + + )} + +
+ ); } diff --git a/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx b/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx index 984a55bdf..e2692524d 100644 --- a/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx +++ b/web/src/features/responses/components/QuickReportDetails/QuickReportDetails.tsx @@ -5,7 +5,6 @@ import { QuickReportFollowUpStatus, type FunctionComponent } from '@/common/type import { NavigateBack } from '@/components/NavigateBack/NavigateBack'; import Layout from '@/components/layout/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { toast } from '@/components/ui/use-toast'; @@ -17,10 +16,9 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query'; import { Link, useRouter } from '@tanstack/react-router'; import { format } from 'date-fns'; import { quickReportKeys } from '../../hooks/quick-reports'; +import { SubmissionType } from '../../models/common'; import { mapIncidentCategory, mapQuickReportFollowUpStatus, mapQuickReportLocationType } from '../../utils/helpers'; -import { MediaFilesCell } from '../MediaFilesCell/MediaFilesCell'; import { ResponseExtraDataSection } from '../ReponseExtraDataSection/ResponseExtraDataSection'; -import { SubmissionType } from '../../models/common'; export default function QuickReportDetails(): FunctionComponent { const { quickReportId } = Route.useParams(); diff --git a/web/src/features/responses/models/citizen-report.ts b/web/src/features/responses/models/citizen-report.ts index bd1c9313b..aadc540d6 100644 --- a/web/src/features/responses/models/citizen-report.ts +++ b/web/src/features/responses/models/citizen-report.ts @@ -19,6 +19,11 @@ export interface CitizenReportByEntry { timeSubmitted: Date; numberOfQuestionsAnswered: number; numberOfFlaggedAnswers: number; + level1: string; + level2: string; + level3: string; + level4: string; + level5: string; notesCount: number; mediaFilesCount: number; followUpStatus: CitizenReportFollowUpStatus; diff --git a/web/src/features/responses/models/search-params.ts b/web/src/features/responses/models/search-params.ts index 5e904fc80..2e92a84fc 100644 --- a/web/src/features/responses/models/search-params.ts +++ b/web/src/features/responses/models/search-params.ts @@ -9,10 +9,14 @@ import { import { z } from 'zod'; import { IncidentCategory, QuickReportLocationType } from './quick-report'; import { IncidentReportLocationType } from './incident-report'; +import { ZDataSourceSearchSchema } from '@/routes'; export const ResponsesPageSearchParamsSchema = z.object({ viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), - tab: z.enum(['form-answers', 'quick-reports','citizen-reports','incident-reports']).catch('form-answers').optional(), + tab: z + .enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']) + .catch('form-answers') + .optional(), }); export const FormSubmissionsSearchParamsSchema = ResponsesPageSearchParamsSchema.merge( @@ -45,50 +49,58 @@ export const FormSubmissionsSearchParamsSchema = ResponsesPageSearchParamsSchema submissionsFromDate: z.coerce.date().optional(), submissionsToDate: z.coerce.date().optional(), - })); + }) +).merge(ZDataSourceSearchSchema); export type FormSubmissionsSearchParams = z.infer; -export const QuickReportsSearchParamsSchema = z.object({ - level1Filter: z.string().catch('').optional(), - level2Filter: z.string().catch('').optional(), - level3Filter: z.string().catch('').optional(), - level4Filter: z.string().catch('').optional(), - level5Filter: z.string().catch('').optional(), - pollingStationNumberFilter: z.string().catch('').optional(), - quickReportFollowUpStatus: z.nativeEnum(QuickReportFollowUpStatus).optional(), - quickReportLocationType: z.nativeEnum(QuickReportLocationType).optional(), - incidentCategory: z.nativeEnum(IncidentCategory).optional(), -}); +export const QuickReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + level1Filter: z.string().catch('').optional(), + level2Filter: z.string().catch('').optional(), + level3Filter: z.string().catch('').optional(), + level4Filter: z.string().catch('').optional(), + level5Filter: z.string().catch('').optional(), + pollingStationNumberFilter: z.string().catch('').optional(), + quickReportFollowUpStatus: z.nativeEnum(QuickReportFollowUpStatus).optional(), + quickReportLocationType: z.nativeEnum(QuickReportLocationType).optional(), + incidentCategory: z.nativeEnum(IncidentCategory).optional(), + }) +); export type QuickReportsSearchParams = z.infer; -export const CitizenReportsSearchParamsSchema = z.object({ - citizenReportFollowUpStatus: z - .nativeEnum(CitizenReportFollowUpStatus) - .optional(), -}); +export const CitizenReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + citizenReportFollowUpStatus: z.nativeEnum(CitizenReportFollowUpStatus).optional(), + }) +); export type CitizenReportsSearchParams = z.infer; -export const IncidentReportsSearchParamsSchema = z.object({ - viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), - tab: z.enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']).catch('form-answers').optional(), - searchText: z.string().catch('').optional(), - level1Filter: z.string().catch('').optional(), - level2Filter: z.string().catch('').optional(), - level3Filter: z.string().catch('').optional(), - level4Filter: z.string().catch('').optional(), - level5Filter: z.string().catch('').optional(), - pollingStationNumberFilter: z.string().catch('').optional(), - hasFlaggedAnswers: z.string().catch('').optional(), - monitoringObserverId: z.string().catch('').optional(), - tagsFilter: z.array(z.string()).optional().catch([]).optional(), - incidentReportFollowUpStatus: z.nativeEnum(IncidentReportLocationType).optional(), - incidentReportLocationType: z.nativeEnum(IncidentReportLocationType).optional(), - questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), - hasNotes: z.string().catch('').optional(), - hasAttachments: z.string().catch('').optional(), -}); +export const IncidentReportsSearchParamsSchema = ZDataSourceSearchSchema.merge( + z.object({ + viewBy: z.enum(['byEntry', 'byObserver', 'byForm']).catch('byEntry').default('byEntry'), + tab: z + .enum(['form-answers', 'quick-reports', 'citizen-reports', 'incident-reports']) + .catch('form-answers') + .optional(), + searchText: z.string().catch('').optional(), + level1Filter: z.string().catch('').optional(), + level2Filter: z.string().catch('').optional(), + level3Filter: z.string().catch('').optional(), + level4Filter: z.string().catch('').optional(), + level5Filter: z.string().catch('').optional(), + pollingStationNumberFilter: z.string().catch('').optional(), + hasFlaggedAnswers: z.string().catch('').optional(), + monitoringObserverId: z.string().catch('').optional(), + tagsFilter: z.array(z.string()).optional().catch([]).optional(), + incidentReportFollowUpStatus: z.nativeEnum(IncidentReportLocationType).optional(), + incidentReportLocationType: z.nativeEnum(IncidentReportLocationType).optional(), + questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), + hasNotes: z.string().catch('').optional(), + hasAttachments: z.string().catch('').optional(), + }) +); export type IncidentReportsSearchParams = z.infer; diff --git a/web/src/features/responses/utils/column-defs.tsx b/web/src/features/responses/utils/column-defs.tsx index 7888ce454..57fd69554 100644 --- a/web/src/features/responses/utils/column-defs.tsx +++ b/web/src/features/responses/utils/column-defs.tsx @@ -946,6 +946,41 @@ export const citizenReportsByEntryColumnDefs: ColumnDef , + accessorFn:(row)=> row.level1, + id: 'level1', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level2, + id: 'level2', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level3, + id: 'level3', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level4, + id: 'level4', + enableSorting: true, + enableGlobalFilter: true, + }, + { + header: ({ column }) => , + accessorFn:(row)=> row.level5, + id: 'level5', + enableSorting: true, + enableGlobalFilter: true, + }, { header: ({ column }) => , accessorFn:(row)=> row.notesCount, diff --git a/web/src/locales/en.json b/web/src/locales/en.json index eeac91c49..c3c1bc821 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -209,7 +209,13 @@ "englishTitle": "Election event English title", "country": "Country", "startDate": "Start date", - "status": "Status" + "status": "Status", + "coalition": { + "cardTitle": "Details on assigned organization(s)", + "coalitionName": "Coalition", + "leaderName": "Coalition coordinator", + "members": "Coalition members (NGOs)" + } }, "pollingStations": { "tabTitle": "Polling Stations", @@ -352,5 +358,10 @@ "Some": "Some", "All": "All" } + }, + "responses": { + "title": "Responses", + "subtitle":"View all form answers and other reports submitted by your observers.", + "subtitleCoalitionLeader": "View all form answers and other reports submitted by coalition observers." } } \ No newline at end of file diff --git a/web/src/locales/ro.json b/web/src/locales/ro.json index 9f5e7b973..8438e0152 100644 --- a/web/src/locales/ro.json +++ b/web/src/locales/ro.json @@ -202,7 +202,10 @@ "englishTitle": "Election event English title", "country": "Country", "startDate": "Start date", - "status": "Status" + "status": "Status", + "coalition": { + "cardTitle": "Details on assigned organization(s)" + } }, "pollingStations": { "tabTitle": "Polling Stations", @@ -277,5 +280,9 @@ "goToNextPage": "Go to next page", "goToLastPage": "Go to last page" } + }, + "responses": { + "title": "", + "subtitle":"" } } diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 7b9d105c5..5e70f9e63 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -5,16 +5,22 @@ import { redirectIfNotAuth } from '@/lib/utils'; import { createFileRoute } from '@tanstack/react-router'; import { useContext } from 'react'; -import type { FunctionComponent } from '../common/types'; -const Index = (): FunctionComponent => { +import { DataSources, type FunctionComponent } from '../common/types'; +import { z } from 'zod'; +const StatisticsDetails = (): FunctionComponent => { const { userRole } = useContext(AuthContext); - return userRole === 'PlatformAdmin' ? : + return userRole === 'PlatformAdmin' ? : ; }; +export const ZDataSourceSearchSchema = z.object({ + dataSource: z.nativeEnum(DataSources).optional(), +}); + export const Route = createFileRoute('/')({ beforeLoad: () => { redirectIfNotAuth(); }, - component: Index + component: StatisticsDetails, + validateSearch: ZDataSourceSearchSchema, }); From 4b66e5190e5176f5440487a08e8f0bdc8578936a Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Tue, 12 Nov 2024 18:23:10 +0200 Subject: [PATCH 02/22] Fix form template name, fix get form by id and form submissions --- .../GetCoalitionFormSpecification.cs | 2 +- .../AddTranslations/Endpoint.cs | 2 +- .../Feature.FormTemplates/Create/Endpoint.cs | 4 +- .../Feature.FormTemplates/Delete/Endpoint.cs | 2 +- .../DeleteTranslation/Endpoint.cs | 2 +- .../Feature.FormTemplates/Draft/Endpoint.cs | 2 +- api/src/Feature.FormTemplates/Get/Endpoint.cs | 2 +- api/src/Feature.FormTemplates/GlobalUsings.cs | 2 +- .../Feature.FormTemplates/Publish/Endpoint.cs | 2 +- .../SetDefaultLanguage/Endpoint.cs | 2 +- .../Feature.FormTemplates/Update/Endpoint.cs | 2 +- api/src/Feature.Forms/FormSlimModel.cs | 3 +- .../Feature.Forms/FromTemplate/Endpoint.cs | 2 +- api/src/Feature.Forms/Get/Endpoint.cs | 16 +- api/src/Feature.Forms/List/Endpoint.cs | 410 ++++++++++++++---- .../GetCoalitionFormSpecification.cs | 17 + .../appsettings.Development.json | 4 +- .../{Form.cs => FormTemplate.cs} | 12 +- .../FormTemplateValidator.cs | 2 +- .../FormTemplateConfiguration.cs | 4 +- .../Vote.Monitor.Domain/VoteMonitorContext.cs | 4 +- .../GlobalUsings.cs | 2 +- .../ApiTesting.cs | 5 +- .../BaseDbTestFixture.cs | 4 +- .../CustomWebApplicationFactory.cs | 4 +- .../Db/PostgresTestDatabase.cs | 8 +- .../Db/TestcontainersTestDatabase.cs | 11 +- .../DbTesting.cs | 35 -- .../Features/Coalition/FormAccessTests.cs | 48 ++ .../AuditTrailInterceptorTests.cs | 52 +-- .../Interceptors/AuditingInterceptorTests.cs | 14 +- .../MonitoringNgoFormScenarioBuilder.cs | 3 +- .../Scenarios/MonitoringNgoScenarioBuilder.cs | 6 +- .../Entities/FormAggregate/FormTests.Clone.cs | 10 +- .../FormTemplateTests.AddTranslations.cs | 12 +- .../FormTemplateTests.Clone.cs | 10 +- .../FormTemplateTests.Duplicate.cs | 3 +- .../FormTemplateTests.RemoveTranslation.cs | 10 +- .../Aggregates/FormTemplateAggregateFaker.cs | 2 +- .../Aggregates/MonitoringObserverFaker.cs | 6 +- 40 files changed, 524 insertions(+), 219 deletions(-) create mode 100644 api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs rename api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/{Form.cs => FormTemplate.cs} (95%) delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs diff --git a/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs index a236a6aa2..1bc9e99f5 100644 --- a/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs +++ b/api/src/Feature.Form.Submissions/Specifications/GetCoalitionFormSpecification.cs @@ -11,7 +11,7 @@ public GetCoalitionFormSpecification(Guid electionRondId, Guid observerId, Guid .Where(x => x.Memberships.Any(cm => cm.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId))) .Where(x => x.FormAccess.Any(fa => - fa.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId && fa.FormId == formId))); + fa.MonitoringNgo.MonitoringObservers.Any(o => o.ObserverId == observerId) && fa.FormId == formId)); Query.Include(x => x.FormAccess).ThenInclude(x => x.Form).ThenInclude(x => x.ElectionRound); diff --git a/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs b/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs index 413be0a42..e9ee87828 100644 --- a/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs +++ b/api/src/Feature.FormTemplates/AddTranslations/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.AddTranslations; -public class Endpoint(IRepository
repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Create/Endpoint.cs b/api/src/Feature.FormTemplates/Create/Endpoint.cs index a6dd83b39..f692eca6a 100644 --- a/api/src/Feature.FormTemplates/Create/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Create/Endpoint.cs @@ -3,7 +3,7 @@ namespace Feature.FormTemplates.Create; -public class Endpoint(IRepository repository) : +public class Endpoint(IRepository repository) : Endpoint, Conflict>> { public override void Configure() @@ -27,7 +27,7 @@ public override async Task, Conflict repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs b/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs index 081eefda9..8b226c4c2 100644 --- a/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs +++ b/api/src/Feature.FormTemplates/DeleteTranslation/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.DeleteTranslation; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Draft/Endpoint.cs b/api/src/Feature.FormTemplates/Draft/Endpoint.cs index 93dc27255..f48bd1b27 100644 --- a/api/src/Feature.FormTemplates/Draft/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Draft/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.Draft; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Get/Endpoint.cs b/api/src/Feature.FormTemplates/Get/Endpoint.cs index cb2fce8b0..e50b23ffe 100644 --- a/api/src/Feature.FormTemplates/Get/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Get/Endpoint.cs @@ -7,7 +7,7 @@ namespace Feature.FormTemplates.Get; public class Endpoint( - IReadRepository repository, + IReadRepository repository, ICurrentUserRoleProvider userRoleProvider, IAuthorizationService authorizationService) : Endpoint, NotFound>> { diff --git a/api/src/Feature.FormTemplates/GlobalUsings.cs b/api/src/Feature.FormTemplates/GlobalUsings.cs index 59120cf13..c8b58fa05 100644 --- a/api/src/Feature.FormTemplates/GlobalUsings.cs +++ b/api/src/Feature.FormTemplates/GlobalUsings.cs @@ -8,4 +8,4 @@ global using Microsoft.AspNetCore.Http.HttpResults; global using Vote.Monitor.Domain.Repository; global using Vote.Monitor.Form.Module.Mappers; -global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; +global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate; diff --git a/api/src/Feature.FormTemplates/Publish/Endpoint.cs b/api/src/Feature.FormTemplates/Publish/Endpoint.cs index 71b7bce23..53ab779d6 100644 --- a/api/src/Feature.FormTemplates/Publish/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Publish/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.Publish; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs b/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs index bf246c1d4..a180dc1e6 100644 --- a/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs +++ b/api/src/Feature.FormTemplates/SetDefaultLanguage/Endpoint.cs @@ -2,7 +2,7 @@ namespace Feature.FormTemplates.SetDefaultLanguage; -public class Endpoint(IRepository repository) : Endpoint> +public class Endpoint(IRepository repository) : Endpoint> { public override void Configure() { diff --git a/api/src/Feature.FormTemplates/Update/Endpoint.cs b/api/src/Feature.FormTemplates/Update/Endpoint.cs index 32dc5475d..a181c2ac4 100644 --- a/api/src/Feature.FormTemplates/Update/Endpoint.cs +++ b/api/src/Feature.FormTemplates/Update/Endpoint.cs @@ -3,7 +3,7 @@ namespace Feature.FormTemplates.Update; -public class Endpoint(IRepository repository) : Endpoint>> +public class Endpoint(IRepository repository) : Endpoint>> { public override void Configure() { diff --git a/api/src/Feature.Forms/FormSlimModel.cs b/api/src/Feature.Forms/FormSlimModel.cs index 9e8be7846..fe13dc99e 100644 --- a/api/src/Feature.Forms/FormSlimModel.cs +++ b/api/src/Feature.Forms/FormSlimModel.cs @@ -27,6 +27,7 @@ public class FormSlimModel public required DateTime LastModifiedOn { get; init; } public string LastModifiedBy { get; init; } + public bool IsFormOwner { get; init; } public LanguagesTranslationStatus LanguagesTranslationStatus { get; init; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/FromTemplate/Endpoint.cs b/api/src/Feature.Forms/FromTemplate/Endpoint.cs index 9da8ca2d6..1ea295329 100644 --- a/api/src/Feature.Forms/FromTemplate/Endpoint.cs +++ b/api/src/Feature.Forms/FromTemplate/Endpoint.cs @@ -9,7 +9,7 @@ namespace Feature.Forms.FromTemplate; public class Endpoint( IAuthorizationService authorizationService, - IReadRepository formTemplateRepository, + IReadRepository formTemplateRepository, IRepository monitoringNgoRepository, IRepository formsRepository) : Endpoint, NotFound>> { diff --git a/api/src/Feature.Forms/Get/Endpoint.cs b/api/src/Feature.Forms/Get/Endpoint.cs index 50585c038..e24a91cae 100644 --- a/api/src/Feature.Forms/Get/Endpoint.cs +++ b/api/src/Feature.Forms/Get/Endpoint.cs @@ -1,12 +1,15 @@ using Authorization.Policies.Requirements; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CoalitionAggregate; +using GetCoalitionFormSpecification = Feature.Forms.Specifications.GetCoalitionFormSpecification; namespace Feature.Forms.Get; public class Endpoint( IAuthorizationService authorizationService, - IReadRepository repository) : Endpoint, NotFound>> + IReadRepository formRepository, + IReadRepositorycoalitionRepository) : Endpoint, NotFound>> { public override void Configure() { @@ -24,10 +27,13 @@ public override async Task, NotFound>> ExecuteAsync(Re return TypedResults.NotFound(); } - FormAggregate? form = null; - - var specification = new GetFormByIdSpecification(req.ElectionRoundId, req.NgoId, req.Id); - form = await repository.FirstOrDefaultAsync(specification, ct); + var coalitionFormSpecification = + new GetCoalitionFormSpecification(req.ElectionRoundId, req.NgoId, req.Id); + var ngoFormSpecification = + new GetFormByIdSpecification(req.ElectionRoundId, req.NgoId, req.Id); + + var form = (await coalitionRepository.FirstOrDefaultAsync(coalitionFormSpecification, ct)) ?? + (await formRepository.FirstOrDefaultAsync(ngoFormSpecification, ct)); if (form is null) { diff --git a/api/src/Feature.Forms/List/Endpoint.cs b/api/src/Feature.Forms/List/Endpoint.cs index ff48079b4..a28734a72 100644 --- a/api/src/Feature.Forms/List/Endpoint.cs +++ b/api/src/Feature.Forms/List/Endpoint.cs @@ -32,90 +32,338 @@ public override async Task>, NotFound>> } var sql = """ - SELECT COUNT(*) COUNT - FROM "Forms" F - INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" - WHERE ( - @searchText IS NULL - OR @searchText = '' - OR F."Code" ILIKE @searchText - OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText - OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + WITH + "MonitoringNgoData" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + MN."FormsVersion", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader", + -- Check if MonitoringNgo is in a coalition + ( + SELECT + COUNT(1) + FROM + "CoalitionMemberships" CM + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + ) > 0 AS "IsInACoalition" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 ) - AND (@type IS NULL OR F."FormType" = @type) - AND (@status IS NULL OR F."Status" = @status) - AND F."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId; + SELECT + COUNT(*) COUNT + FROM + ( + SELECT + 1 + FROM + "CoalitionFormAccess" CFA + JOIN "Forms" F ON CFA."FormId" = F."Id" + JOIN "Coalitions" C ON CFA."CoalitionId" = C."Id" + WHERE + CFA."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND C."ElectionRoundId" = @electionRoundId + AND ( + ( + SELECT + "IsInACoalition" + FROM + "MonitoringNgoData" + ) + OR ( + SELECT + "IsCoalitionLeader" + FROM + "MonitoringNgoData" + ) + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + UNION ALL + -- If not a coalition leader, get published forms specific to the MonitoringNgo + SELECT + 1 + FROM + "Forms" F + WHERE + F."ElectionRoundId" = @electionRoundId + AND F."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND NOT ( + SELECT + "IsCoalitionLeader" + FROM + "MonitoringNgoData" + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + ) F; - SELECT F."Id", - F."Code", - F."Name", - F."Description", - F."DefaultLanguage", - F."Languages", - F."Status", - F."FormType", - F."NumberOfQuestions", - F."LanguagesTranslationStatus", - F."Icon", - F."LastModifiedOn", - F."LastModifiedBy" - FROM (SELECT F."Id", - F."Code", - F."Name", - F."Description", - F."DefaultLanguage", - F."Languages", - F."Status", - F."FormType", - F."NumberOfQuestions", - F."LanguagesTranslationStatus", - F."Icon", - COALESCE(F."LastModifiedOn", F."CreatedOn") as "LastModifiedOn", - COALESCE(UPDATER."DisplayName", - CREATOR."DisplayName") AS "LastModifiedBy" - FROM "Forms" F - INNER JOIN "MonitoringNgos" MN ON MN."Id" = F."MonitoringNgoId" - INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" - LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" - Where F."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId) F - WHERE ( - @searchText IS NULL - OR @searchText = '' - OR F."Code" ILIKE @searchText - OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText - OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + WITH + "MonitoringNgoData" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + MN."FormsVersion", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader", + -- Check if MonitoringNgo is in a coalition + ( + SELECT + COUNT(1) + FROM + "CoalitionMemberships" CM + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + ) > 0 AS "IsInACoalition" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 ) - AND (@type IS NULL OR F."FormType" = @type) - AND (@status IS NULL OR F."Status" = @status) - - ORDER BY CASE - WHEN @sortExpression = 'Code ASC' THEN "Code" - END ASC, - CASE - WHEN @sortExpression = 'Code DESC' THEN "Code" - END DESC, - CASE - WHEN @sortExpression = 'LastModifiedOn ASC' THEN "LastModifiedOn" - END ASC, - CASE - WHEN @sortExpression = 'LastModifiedOn DESC' THEN "LastModifiedOn" - END DESC, - CASE - WHEN @sortExpression = 'FormType ASC' THEN "FormType" - END ASC, - CASE - WHEN @sortExpression = 'FormType DESC' THEN "FormType" - END DESC, - CASE - WHEN @sortExpression = 'Status ASC' THEN "Status" - END ASC, - CASE - WHEN @sortExpression = 'Status DESC' THEN "Status" - END DESC - OFFSET @offset ROWS - FETCH NEXT @pageSize ROWS ONLY; + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + F."LastModifiedOn", + F."LastModifiedBy", + F."IsFormOwner" + FROM + ( + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + COALESCE(F."LastModifiedOn", F."CreatedOn") AS "LastModifiedOn", + COALESCE(UPDATER."DisplayName", CREATOR."DisplayName") AS "LastModifiedBy", + EXISTS ( + SELECT 1 + FROM "MonitoringNgoData" + WHERE "MonitoringNgoId" = f."MonitoringNgoId" + ) AS "IsFormOwner" + FROM + "CoalitionFormAccess" CFA + INNER JOIN "Coalitions" C ON CFA."CoalitionId" = C."Id" + INNER JOIN "Forms" F ON CFA."FormId" = F."Id" + INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" + LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" + WHERE + CFA."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND C."ElectionRoundId" = @electionRoundId + AND ( + ( + SELECT + "IsInACoalition" + FROM + "MonitoringNgoData" + ) + OR ( + SELECT + "IsCoalitionLeader" + FROM + "MonitoringNgoData" + ) + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + UNION ALL + -- If not a coalition leader, get published forms specific to the MonitoringNgo + SELECT + F."Id", + F."Code", + F."Name", + F."Description", + F."DefaultLanguage", + F."Languages", + F."Status", + F."FormType", + F."NumberOfQuestions", + F."LanguagesTranslationStatus", + F."Icon", + COALESCE(F."LastModifiedOn", F."CreatedOn") AS "LastModifiedOn", + COALESCE(UPDATER."DisplayName", CREATOR."DisplayName") AS "LastModifiedBy", + false as "IsFormOwner" + FROM + "Forms" F + INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" + LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" + WHERE + F."ElectionRoundId" = @electionRoundId + AND F."MonitoringNgoId" = ( + SELECT + "MonitoringNgoId" + FROM + "MonitoringNgoData" + ) + AND NOT ( + SELECT + "IsCoalitionLeader" + FROM + "MonitoringNgoData" + ) + AND ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + ) F + WHERE + ( + @searchText IS NULL + OR @searchText = '' + OR F."Code" ILIKE @searchText + OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + ) + AND ( + @type IS NULL + OR F."FormType" = @type + ) + AND ( + @status IS NULL + OR F."Status" = @status + ) + ORDER BY + CASE + WHEN @sortExpression = 'Code ASC' THEN "Code" + END ASC, + CASE + WHEN @sortExpression = 'Code DESC' THEN "Code" + END DESC, + CASE + WHEN @sortExpression = 'LastModifiedOn ASC' THEN "LastModifiedOn" + END ASC, + CASE + WHEN @sortExpression = 'LastModifiedOn DESC' THEN "LastModifiedOn" + END DESC, + CASE + WHEN @sortExpression = 'FormType ASC' THEN "FormType" + END ASC, + CASE + WHEN @sortExpression = 'FormType DESC' THEN "FormType" + END DESC, + CASE + WHEN @sortExpression = 'Status ASC' THEN "Status" + END ASC, + CASE + WHEN @sortExpression = 'Status DESC' THEN "Status" + END DESC + OFFSET + @offset + ROWS + FETCH NEXT + @pageSize ROWS ONLY; """; var queryArgs = new diff --git a/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs b/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs new file mode 100644 index 000000000..1722a89ca --- /dev/null +++ b/api/src/Feature.Forms/Specifications/GetCoalitionFormSpecification.cs @@ -0,0 +1,17 @@ +using Vote.Monitor.Domain.Entities.CoalitionAggregate; + +namespace Feature.Forms.Specifications; + +public sealed class GetCoalitionFormSpecification : SingleResultSpecification +{ + public GetCoalitionFormSpecification(Guid electionRondId, Guid ngoId, Guid formId) + { + Query.Where(x => x.ElectionRoundId == electionRondId) + .Where(x => x.Memberships.Any(cm => cm.MonitoringNgo.NgoId == ngoId)) + .Where(x => x.FormAccess.Any(fa => fa.MonitoringNgo.NgoId == ngoId && fa.FormId == formId)); + + Query.Include(x => x.FormAccess).ThenInclude(x => x.Form).ThenInclude(x => x.ElectionRound); + + Query.Select(x => x.FormAccess.First(f => f.FormId == formId).Form); + } +} diff --git a/api/src/Vote.Monitor.Api/appsettings.Development.json b/api/src/Vote.Monitor.Api/appsettings.Development.json index bff1a1627..e3af8b6cd 100644 --- a/api/src/Vote.Monitor.Api/appsettings.Development.json +++ b/api/src/Vote.Monitor.Api/appsettings.Development.json @@ -38,8 +38,8 @@ "FirstName": "John", "LastName": "Doe", "Email": "john.doe@example.com", - "PhoneNumber": "1234567890", - "Password": "password123" + "PhoneNumber": "string", + "Password": "string" } }, "Statistics": { diff --git a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs similarity index 95% rename from api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs rename to api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs index 029637f05..54549f8d1 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/Form.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplate.cs @@ -7,7 +7,7 @@ namespace Vote.Monitor.Domain.Entities.FormTemplateAggregate; -public class Form : AuditableBaseEntity, IAggregateRoot +public class FormTemplate : AuditableBaseEntity, IAggregateRoot { public Guid Id { get; private set; } public FormType FormType { get; set; } @@ -21,7 +21,7 @@ public class Form : AuditableBaseEntity, IAggregateRoot public IReadOnlyList Questions { get; private set; } = new List().AsReadOnly(); [JsonConstructor] - internal Form(Guid id, + internal FormTemplate(Guid id, FormType formType, string code, string defaultLanguage, @@ -40,7 +40,7 @@ internal Form(Guid id, Languages = languages; } - private Form(FormType formType, + private FormTemplate(FormType formType, string code, string defaultLanguage, TranslatedString name, @@ -59,7 +59,7 @@ private Form(FormType formType, Questions = questions.ToList(); } - public static Form Create(FormType formType, + public static FormTemplate Create(FormType formType, string code, string defaultLanguage, TranslatedString name, @@ -162,7 +162,7 @@ public void SetDefaultLanguage(string languageCode) DefaultLanguage = languageCode; } - public Form Duplicate() => + public FormTemplate Duplicate() => new(FormType, Code, DefaultLanguage, Name, Description, Languages, Questions); public FormAggregate.Form Clone(Guid electionRoundId, Guid monitoringNgoId, string defaultLanguage, string[] languages) @@ -206,7 +206,7 @@ public FormAggregate.Form Clone(Guid electionRoundId, Guid monitoringNgoId, stri #pragma warning disable CS8618 // Required by Entity Framework - private Form() + private FormTemplate() { } #pragma warning restore CS8618 diff --git a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs index 623a76164..fd34f5ec0 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormTemplateAggregate/FormTemplateValidator.cs @@ -5,7 +5,7 @@ namespace Vote.Monitor.Domain.Entities.FormTemplateAggregate; -public class FormTemplateValidator : Validator +public class FormTemplateValidator : Validator { public FormTemplateValidator() { diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs index ee248ae15..2bcd08f80 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/FormTemplateConfiguration.cs @@ -5,9 +5,9 @@ namespace Vote.Monitor.Domain.EntitiesConfiguration; -public class FormTemplateConfiguration : IEntityTypeConfiguration +public class FormTemplateConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { builder.HasKey(e => e.Id); builder.Property(x => x.FormType).IsRequired(); diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index fd6365324..338906b22 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -11,6 +11,7 @@ using Vote.Monitor.Domain.Entities.ExportedDataAggregate; using Vote.Monitor.Domain.Entities.FeedbackAggregate; using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAggregate; using Vote.Monitor.Domain.Entities.IncidentReportAttachmentAggregate; using Vote.Monitor.Domain.Entities.IncidentReportNoteAggregate; @@ -29,7 +30,6 @@ using Vote.Monitor.Domain.Entities.PollingStationInfoFormAggregate; using Vote.Monitor.Domain.Entities.QuickReportAggregate; using Vote.Monitor.Domain.Entities.QuickReportAttachmentAggregate; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain; @@ -57,7 +57,7 @@ public VoteMonitorContext(DbContextOptions options, public DbSet ElectionRounds { get; set; } public DbSet ImportValidationErrors { set; get; } public DbSet AuditTrails => Set(); - public DbSet FormTemplates { set; get; } + public DbSet FormTemplates { set; get; } public DbSet Forms { set; get; } public DbSet FormSubmissions { set; get; } public DbSet PollingStationInformationForms { set; get; } diff --git a/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs b/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs index 3b9647457..a0f7c9375 100644 --- a/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs +++ b/api/tests/Feature.FormTemplates.UnitTests/GlobalUsings.cs @@ -5,7 +5,7 @@ global using NSubstitute; global using Xunit; -global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; +global using FormTemplateAggregate = Vote.Monitor.Domain.Entities.FormTemplateAggregate.FormTemplate; global using Vote.Monitor.Domain.Entities.FormTemplateAggregate; global using Vote.Monitor.Domain.Repository; global using Vote.Monitor.TestUtils; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs index 8e5b268da..2df15bd06 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs @@ -15,6 +15,8 @@ public async Task RunBeforeAnyTests() await _database.InitialiseAsync(); _factory = new CustomWebApplicationFactory(_database.GetConnectionString(), _database.GetConnection()); } + + public static string DbConnectionString => _database.GetConnectionString(); public static async Task ResetState() { @@ -22,8 +24,9 @@ public static async Task ResetState() { await _database.ResetAsync(); } - catch (Exception) + catch (Exception e) { + TestContext.Out.WriteLine(e.Message); } } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs index efb214879..8e542be31 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/BaseDbTestFixture.cs @@ -1,11 +1,13 @@ namespace Vote.Monitor.Api.IntegrationTests; +using static ApiTesting; + [TestFixture] public abstract class BaseDbTestFixture { [SetUp] public async Task BaseTestSetUp() { - await DbTesting.ResetState(); + await ResetState(); } } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs index 1a40edcc8..f531db23d 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs @@ -17,13 +17,13 @@ public class CustomWebApplicationFactory : WebApplicationFactory private readonly DbConnection _connection; private readonly NpgsqlConnectionStringBuilder _connectionDetails; - public const string AdminEmail = "admin@example.com"; + public const string AdminEmail = "integration@testing.com"; public const string AdminPassword = "toTallyNotTestPassw0rd"; public CustomWebApplicationFactory(string connectionString, DbConnection connection) { _connection = connection; - _connectionDetails = new NpgsqlConnectionStringBuilder() { ConnectionString = connectionString }; + _connectionDetails = new NpgsqlConnectionStringBuilder { ConnectionString = connectionString }; } protected override void ConfigureWebHost(IWebHostBuilder builder) diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs index 0565658f8..c9686b35d 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs @@ -47,7 +47,13 @@ public async Task InitialiseAsync() new RespawnerOptions { TablesToIgnore = - ["__EFMigrationsHistory", "AspNetRoles", "Language", "Countries"], + [ + "__EFMigrationsHistory", + "AspNetRoles", + "AspNetUsers", + "Language", + "Countries" + ], DbAdapter = DbAdapter.Postgres }); } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs index 7d92eb7cc..2c217abc6 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs @@ -46,7 +46,13 @@ public async Task InitialiseAsync() new RespawnerOptions { TablesToIgnore = - ["__EFMigrationsHistory", "AspNetRoles", "Language", "Countries"], + [ + "__EFMigrationsHistory", + "AspNetRoles", + "AspNetUsers", + "Language", + "Countries" + ], DbAdapter = DbAdapter.Postgres }); } @@ -61,10 +67,9 @@ public string GetConnectionString() return _connectionString; } - public async Task ResetAsync() { - await _respawner.ResetAsync(_connectionString); + await _respawner.ResetAsync(_connection); } public async Task DisposeAsync() diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs deleted file mode 100644 index a2e9ed037..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/DbTesting.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Vote.Monitor.Api.IntegrationTests.Db; - -namespace Vote.Monitor.Api.IntegrationTests; - -[SetUpFixture] -public class DbTesting -{ - private static ITestDatabase _database = null!; - - [OneTimeSetUp] - public async Task RunBeforeAnyTests() - { - _database = await TestDatabaseFactory.CreateAsync(); - await _database.InitialiseAsync(); - } - - public static async Task ResetState() - { - try - { - await _database.ResetAsync(); - } - catch (Exception) - { - } - } - - public static string DbConnectionString => _database.GetConnectionString(); - - [OneTimeTearDown] - public async Task RunAfterAnyTests() - { - await _database.DisposeAsync(); - } -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs index 963fccec7..eb710b524 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -3,6 +3,7 @@ using Feature.Forms; using Feature.Forms.Models; using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; using Vote.Monitor.Api.IntegrationTests.Models; using Vote.Monitor.Api.IntegrationTests.Scenarios; using Vote.Monitor.Core.Models; @@ -131,11 +132,58 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() .NgoByName(Ngos.Beta).Admin .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + var form = scenarioData + .NgoByName(Ngos.Beta).Admin + .GetResponse($"/api/election-rounds/{electionRoundId}/forms/{formId}"); + aliceForms.Forms.Should().BeEmpty(); bobForms.Forms.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); betaForms.Items.Select(x=>x.Id).Should().HaveCount(1).And.BeEquivalentTo([formId]); + form.Should().NotBeNull(); } + [Test] + public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithObserver(Observers.Alice) + .WithObserver(Observers.Bob) + .WithElectionRound(ElectionRounds.A, + er => er + .WithPollingStation(PollingStations.Iasi) + .WithPollingStation(PollingStations.Bacau) + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice).WithForm()) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + + scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + + var pollingStationId = scenarioData.ElectionRound.PollingStationByName(PollingStations.Iasi); + var questions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; + var submission = new FormSubmissionRequestFaker(formId, pollingStationId, questions).Generate(); + + var observer = scenarioData.ObserverByName(Observers.Bob); + + var submissionId = await observer.PostAsJsonAsync( + $"/api/election-rounds/{electionRoundId}/form-submissions", + submission); + + // Assert + submissionId.Should().HaveStatusCode(HttpStatusCode.OK); + } + + [Test] public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() { diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs index efd27647d..2634a2e91 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Vote.Monitor.Core.Services.Security; using Vote.Monitor.Core.Services.Serialization; @@ -13,26 +14,27 @@ using Vote.Monitor.TestUtils.Fakes.Aggregates; namespace Vote.Monitor.Api.IntegrationTests.Interceptors; -using static DbTesting; -public class AuditTrailInterceptorTests: BaseDbTestFixture + +using static ApiTesting; + +public class AuditTrailInterceptorTests : BaseDbTestFixture { - private ITimeProvider _fakeTimeProvider; - private ICurrentUserProvider _fakeCurrentUserProvider; - private VoteMonitorContext _context; + private ITimeProvider _fakeTimeProvider; + private ICurrentUserProvider _fakeCurrentUserProvider; + private VoteMonitorContext _context; [SetUp] public void Init() { - _fakeTimeProvider = Substitute.For(); - var fakeSerializationService = Substitute.For(); _fakeCurrentUserProvider = Substitute.For(); - + _fakeTimeProvider = Substitute.For(); + var options = new DbContextOptionsBuilder() .UseNpgsql(DbConnectionString) .Options; _context = new VoteMonitorContext(options, - fakeSerializationService, + new SerializerService(NullLogger.Instance), _fakeTimeProvider, _fakeCurrentUserProvider); } @@ -51,7 +53,7 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_AddsCreateAuditTrail( { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -80,7 +82,7 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_AddsCreateAuditTrail() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -110,8 +112,8 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_AddsUpdateAudit() //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -146,8 +148,8 @@ public async Task Interceptor_OnEntityUpdate_SaveChanges_AddsUpdateAudit() //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -182,8 +184,8 @@ public async Task Interceptor_OnEntityDelete_SaveChangesAsync_AddsDeleteAudit() //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -216,8 +218,8 @@ public async Task Interceptor_OnEntityDelete_SaveChanges_AddsDeleteAudit() //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -250,8 +252,8 @@ public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForF //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -265,11 +267,11 @@ public async Task Interceptor_OnAddFormSubmission_ShouldNotTriggerAuditTrailForF var applicationUser = ApplicationUser.CreateObserver("test", "test", "email", "222", "password"); var observer = Observer.Create(applicationUser); - var monitoringObserver = new MonitoringObserverFaker(electionRound: electionRound, monitoringNgo: monitoringNgo, - observer: observer) - .Generate(); + var monitoringObserver = + new MonitoringObserverFaker(electionRound: electionRound, monitoringNgo: monitoringNgo, observer: observer) + .Generate(); - await _context.Countries.AddAsync(CountriesList.AD.ToEntity()); + // await _context.Countries.AddAsync(CountriesList.AD.ToEntity()); await _context.ElectionRounds.AddAsync(electionRound); await _context.PollingStations.AddAsync(pollingStation); await _context.Ngos.AddAsync(ngo); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs index 4de8a2954..ca9280fe3 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs @@ -7,7 +7,7 @@ using Vote.Monitor.Domain.Entities.NgoAggregate; namespace Vote.Monitor.Api.IntegrationTests.Interceptors; -using static DbTesting; +using static ApiTesting; public class AuditingInterceptorTests : BaseDbTestFixture { private ITimeProvider _fakeTimeProvider; @@ -44,7 +44,7 @@ public async Task Interceptor_OnEntityAdd_SaveChangesAsync_SetsCreatedFields() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -68,7 +68,7 @@ public async Task Interceptor_OnEntityAdd_SaveChanges_SetsCreatedFields() { //Arrange var userId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn); _fakeCurrentUserProvider.GetUserId().Returns(userId); @@ -94,8 +94,8 @@ public async Task Interceptor_OnEntityUpdate_SaveChangesAsync_SetsLastModifiedFi //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); @@ -121,8 +121,8 @@ public async Task Interceptor_OnEntityUpdate_SaveChanges_SetsLastModifiedFields( //Arrange var userId = Guid.NewGuid(); var anotherUserId = Guid.NewGuid(); - var createdOn = new DateTime(2024, 03, 22, 12, 35, 46); - var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0); + var createdOn = new DateTime(2024, 03, 22, 12, 35, 46, DateTimeKind.Utc); + var lastModifiedOn = new DateTime(2024, 03, 24, 0, 0, 0, DateTimeKind.Utc); _fakeTimeProvider.UtcNow.Returns(createdOn, lastModifiedOn); _fakeCurrentUserProvider.GetUserId().Returns(userId, anotherUserId); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs index e33310b03..90ef17865 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -8,7 +8,8 @@ public class MonitoringNgoFormScenarioBuilder public readonly MonitoringNgoScenarioBuilder ParentBuilder; private readonly CreateFormRequest _form; - public Guid Id => _form.Id; + public Guid FormId => _form.Id; + public CreateFormRequest Form => _form; private bool _formIsPublished = false; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs index ada5a9cdd..e1afbc828 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -11,7 +11,8 @@ public class MonitoringNgoScenarioBuilder public readonly ElectionRoundScenarioBuilder ParentBuilder; private readonly HttpClient _platformAdmin; public NgoScenarioBuilder NgoScenario { get; } - public Guid FormId => _forms.First().Value.Id; + public Guid FormId => _forms.First().Value.FormId; + public CreateFormRequest Form => _forms.First().Value.Form; public MonitoringNgoScenarioBuilder(Guid electionRoundId, Guid monitoringNgoId, @@ -33,8 +34,7 @@ public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, var formRequest = Dummy.Form(); var admin = NgoScenario.Admin; - var ngoForm = - admin.PostWithResponse( + var ngoForm = admin.PostWithResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/forms", formRequest); diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs index dfd65f12c..4e9b2147e 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormAggregate/FormTests.Clone.cs @@ -1,6 +1,6 @@ using FluentValidation; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormAggregate; @@ -10,7 +10,7 @@ public partial class FormTests public void Clone_ShouldReturnClonedForm_WhenStatusIsPublishedAndLanguagesAreValid() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -50,7 +50,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() { // Arrange // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -81,7 +81,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -114,7 +114,7 @@ public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupporte public void Clone_ShouldThrowValidationException_WhenOneOrMoreLanguagesAreUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs index eb39627e7..8b5f6a5a0 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.AddTranslations.cs @@ -1,6 +1,6 @@ using Vote.Monitor.Core.Helpers; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; @@ -14,7 +14,7 @@ public void WhenAddingTranslations_AndDuplicatedLanguageCodes_ThenOnlyNewLanguag var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; @@ -34,7 +34,7 @@ public void WhenAddingTranslations_AndNoNewLanguages_ThenFormStaysTheSame() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); var formBefore = formTemplate.DeepClone(); @@ -54,7 +54,7 @@ public void WhenAddingTranslations_AndEmptyNewLanguages_ThenFormStaysTheSame() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); var formBefore = formTemplate.DeepClone(); @@ -74,7 +74,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForFormTemplateDetails() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; @@ -105,7 +105,7 @@ public void WhenAddingTranslations_ThenAddsTranslationsForEachQuestion() new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); string[] newLanguages = [LanguagesList.RO.Iso1, LanguagesList.HU.Iso1]; diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs index e3f5ddecb..7dfc999d4 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Clone.cs @@ -1,6 +1,6 @@ using FluentValidation; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests @@ -9,7 +9,7 @@ public partial class FormTests public void Clone_ShouldReturnClonedForm_WhenStatusIsPublishedAndLanguagesAreValid() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -49,7 +49,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() { // Arrange // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -79,7 +79,7 @@ public void Clone_ShouldThrowValidationException_WhenStatusIsNotPublished() public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), @@ -111,7 +111,7 @@ public void Clone_ShouldThrowValidationException_WhenDefaultLanguageIsUnsupporte public void Clone_ShouldThrowValidationException_WhenOneOrMoreLanguagesAreUnsupported() { // Arrange - var template = Form.Create(FormType.Opening, + var template = FormTemplate.Create(FormType.Opening, "A", LanguagesList.RO.Iso1, new TranslatedStringFaker(_languages), diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs index 7fac90802..42357cc2a 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.Duplicate.cs @@ -1,6 +1,5 @@ using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; public partial class FormTests @@ -22,7 +21,7 @@ public void WhenDuplicate_ThenCreatesNewDraftedFormTemplate() new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, questions); formTemplate.Publish(); // Act diff --git a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs index 5b0da10e3..735872473 100644 --- a/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs +++ b/api/tests/Vote.Monitor.Domain.UnitTests/Entities/FormTemplateAggregate/FormTemplateTests.RemoveTranslation.cs @@ -1,6 +1,6 @@ using Vote.Monitor.Core.Helpers; +using Vote.Monitor.Domain.Entities.FormTemplateAggregate; using Vote.Monitor.TestUtils.Fakes.Aggregates; -using Form = Vote.Monitor.Domain.Entities.FormTemplateAggregate.Form; namespace Vote.Monitor.Domain.UnitTests.Entities.FormTemplateAggregate; @@ -14,7 +14,7 @@ public void WhenRemovingTranslation_AndFormTemplateDoesNotHaveIt_ThenFormTemplat var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages,[]); var formBefore = formTemplate.DeepClone(); @@ -34,7 +34,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForFormTemplateDetails var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); // Act @@ -54,7 +54,7 @@ public void WhenRemovingTranslation_AndDefaultLanguageIsRemoved_ThenException() var name = new TranslatedStringFaker(languages).Generate(); var description = new TranslatedStringFaker(languages).Generate(); - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages, []); // Act @@ -83,7 +83,7 @@ public void WhenRemovingTranslation_ThenRemovesTranslationForEachQuestion() new MultiSelectQuestionFaker(languageList: languages).Generate() ]; - var formTemplate = Form.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, + var formTemplate = FormTemplate.Create(FormType.Voting, "code", LanguagesList.RO.Iso1, name, description, languages,questions); // Act diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs index 3cf54b0ec..174980aea 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/FormTemplateAggregateFaker.cs @@ -4,7 +4,7 @@ namespace Vote.Monitor.TestUtils.Fakes.Aggregates; -public sealed class FormTemplateAggregateFaker : PrivateFaker +public sealed class FormTemplateAggregateFaker : PrivateFaker { private readonly List _statuses = [FormTemplateStatus.Drafted, FormTemplateStatus.Published]; private readonly DateTime _baseCreationDate = new(2024, 01, 01, 00, 00, 00, DateTimeKind.Utc); diff --git a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs index 7b3c5fcd5..899b6a2cd 100644 --- a/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs +++ b/api/tests/Vote.Monitor.TestUtils/Fakes/Aggregates/MonitoringObserverFaker.cs @@ -12,11 +12,13 @@ public MonitoringObserverFaker(ElectionRound? electionRound = null, MonitoringNg ObserverAggregate? observer = null, Guid? id = null, Guid? observerId = null) { UsePrivateConstructor(); - + electionRound ??= new ElectionRoundAggregateFaker().Generate(); monitoringNgo ??= new MonitoringNgoAggregateFaker(electionRound: electionRound).Generate(); observer ??= new ObserverAggregateFaker(observerId).Generate(); RuleFor(fake => fake.Id, fake => id ?? fake.Random.Guid()); + RuleFor(fake => fake.ElectionRoundId, electionRound.Id); + RuleFor(fake => fake.ElectionRound, electionRound); RuleFor(fake => fake.MonitoringNgo, monitoringNgo); RuleFor(fake => fake.MonitoringNgoId, monitoringNgo.Id); RuleFor(fake => fake.Observer, observer); @@ -24,4 +26,4 @@ public MonitoringObserverFaker(ElectionRound? electionRound = null, MonitoringNg RuleFor(fake => fake.Status, fake => fake.PickRandom(_statuses)); RuleFor(fake => fake.Tags, []); } -} \ No newline at end of file +} From 99d6ae2269e0201ac38da12f4348fdbc2fb25ebc Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Tue, 12 Nov 2024 19:24:04 +0200 Subject: [PATCH 03/22] fix selects --- api/src/Feature.Forms/List/Endpoint.cs | 24 ++---- .../Export/Endpoint.cs | 80 +++++++++++-------- .../List/Endpoint.cs | 37 +++------ .../MonitoringObserverModel.cs | 5 +- .../MonitoringObserversList.tsx | 29 +++---- .../models/monitoring-observer.ts | 3 +- 6 files changed, 79 insertions(+), 99 deletions(-) diff --git a/api/src/Feature.Forms/List/Endpoint.cs b/api/src/Feature.Forms/List/Endpoint.cs index a28734a72..8c92211d0 100644 --- a/api/src/Feature.Forms/List/Endpoint.cs +++ b/api/src/Feature.Forms/List/Endpoint.cs @@ -69,11 +69,11 @@ public override async Task>, NotFound>> 1 ) SELECT - COUNT(*) COUNT + COUNT (DISTINCT "Id") COUNT FROM ( SELECT - 1 + F."Id" FROM "CoalitionFormAccess" CFA JOIN "Forms" F ON CFA."FormId" = F."Id" @@ -115,10 +115,10 @@ @type IS NULL @status IS NULL OR F."Status" = @status ) - UNION ALL + UNION -- If not a coalition leader, get published forms specific to the MonitoringNgo SELECT - 1 + F."Id" FROM "Forms" F WHERE @@ -128,12 +128,6 @@ UNION ALL "MonitoringNgoId" FROM "MonitoringNgoData" - ) - AND NOT ( - SELECT - "IsCoalitionLeader" - FROM - "MonitoringNgoData" ) AND ( @searchText IS NULL @@ -188,7 +182,7 @@ @status IS NULL LIMIT 1 ) - SELECT + SELECT F."Id", F."Code", F."Name", @@ -267,7 +261,7 @@ @type IS NULL @status IS NULL OR F."Status" = @status ) - UNION ALL + UNION -- If not a coalition leader, get published forms specific to the MonitoringNgo SELECT F."Id", @@ -295,12 +289,6 @@ false as "IsFormOwner" "MonitoringNgoId" FROM "MonitoringNgoData" - ) - AND NOT ( - SELECT - "IsCoalitionLeader" - FROM - "MonitoringNgoData" ) AND ( @searchText IS NULL diff --git a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs index 74e3a8d91..002e2bde4 100644 --- a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs @@ -1,13 +1,31 @@ using System.Globalization; +using System.Text.Json.Serialization; +using Ardalis.SmartEnum.SystemTextJson; using Authorization.Policies; using CsvHelper; using Dapper; +using Feature.MonitoringObservers.Parser; using Vote.Monitor.Domain.ConnectionFactory; +using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; namespace Feature.MonitoringObservers.Export; public class Endpoint(INpgsqlConnectionFactory dbConnectionFactory) : Endpoint { + private class MonitoringObserverExportModel + { + public Guid Id { get; init; } + public string FirstName { get; init; } + public string LastName { get; init; } + public string Email { get; init; } + public string PhoneNumber { get; init; } + public string[] Tags { get; init; } + public DateTime? LatestActivityAt { get; init; } + + [JsonConverter(typeof(SmartEnumNameConverter))] + public MonitoringObserverStatus Status { get; init; } + } + public override void Configure() { Get("api/election-rounds/{electionRoundId}/monitoring-observers:export"); @@ -28,33 +46,29 @@ public override void Configure() public override async Task HandleAsync(Request req, CancellationToken ct) { var sql = """ - SELECT - MO."Id", - U."FirstName", - U."LastName", - U."PhoneNumber", - U."Email", - MO."Tags", - MO."Status" - FROM - "MonitoringObservers" MO - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" - INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" - WHERE - MN."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId - """; - - var queryArgs = new - { - electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId - }; - IEnumerable monitoringObservers = []; + SELECT + MO."Id", + U."FirstName", + U."LastName", + U."PhoneNumber", + U."Email", + MO."Tags", + MO."Status" + FROM + "MonitoringObservers" MO + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "Observers" O ON O."Id" = MO."ObserverId" + INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + """; + + var queryArgs = new { electionRoundId = req.ElectionRoundId, ngoId = req.NgoId }; + IEnumerable monitoringObservers = []; using (var dbConnection = await dbConnectionFactory.GetOpenConnectionAsync(ct)) { - monitoringObservers = await dbConnection.QueryAsync(sql, queryArgs); + monitoringObservers = await dbConnection.QueryAsync(sql, queryArgs); } var monitoringObserverModels = monitoringObservers.ToArray(); @@ -84,7 +98,8 @@ await SendBytesAsync(memoryStream.ToArray(), "monitoring-observers.csv", } } - private void WriteData(MonitoringObserverModel[] monitoringObservers, CsvWriter csvWriter, List availableTags) + private void WriteData(MonitoringObserverExportModel[] monitoringObservers, CsvWriter csvWriter, + List availableTags) { foreach (var monitoringObserver in monitoringObservers) { @@ -107,12 +122,12 @@ private void WriteData(MonitoringObserverModel[] monitoringObservers, CsvWriter private static void WriteHeader(CsvWriter csvWriter, List availableTags) { - csvWriter.WriteField(nameof(MonitoringObserverModel.Id)); - csvWriter.WriteField(nameof(MonitoringObserverModel.FirstName)); - csvWriter.WriteField(nameof(MonitoringObserverModel.LastName)); - csvWriter.WriteField(nameof(MonitoringObserverModel.PhoneNumber)); - csvWriter.WriteField(nameof(MonitoringObserverModel.Email)); - csvWriter.WriteField(nameof(MonitoringObserverModel.Status)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Id)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.FirstName)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.LastName)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.PhoneNumber)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Email)); + csvWriter.WriteField(nameof(MonitoringObserverExportModel.Status)); foreach (var tag in availableTags) { @@ -133,5 +148,4 @@ private IReadOnlyList MergeTags(IReadOnlyList tags, List return result; } - } diff --git a/api/src/Feature.MonitoringObservers/List/Endpoint.cs b/api/src/Feature.MonitoringObservers/List/Endpoint.cs index e084ca438..cbefe7ccb 100644 --- a/api/src/Feature.MonitoringObservers/List/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/List/Endpoint.cs @@ -38,8 +38,7 @@ SELECT COUNT(*) count SELECT "Id", - "FirstName", - "LastName", + "DisplayName", "PhoneNumber", "Email", "Tags", @@ -48,8 +47,7 @@ SELECT COUNT(*) count FROM ( SELECT MO."Id", - U."FirstName", - U."LastName", + U."DisplayName", U."PhoneNumber", U."Email", MO."Tags", @@ -117,8 +115,7 @@ GROUP BY AND (@status IS NULL OR mo."Status" = @status) GROUP BY MO."Id", - U."FirstName", - U."LastName", + U."DisplayName", U."PhoneNumber", U."Email", MO."Tags", @@ -126,14 +123,8 @@ GROUP BY ) T ORDER BY - CASE WHEN @sortExpression = 'ObserverName ASC' THEN "DisplayName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN "DisplayName" END DESC, - - CASE WHEN @sortExpression = 'FirstName ASC' THEN "FirstName" END ASC, - CASE WHEN @sortExpression = 'FirstName DESC' THEN "FirstName" END DESC, - - CASE WHEN @sortExpression = 'LastName ASC' THEN "LastName" END ASC, - CASE WHEN @sortExpression = 'LastName DESC' THEN "LastName" END DESC, + CASE WHEN @sortExpression = 'DisplayName ASC' THEN "DisplayName" END ASC, + CASE WHEN @sortExpression = 'DisplayName DESC' THEN "DisplayName" END DESC, CASE WHEN @sortExpression = 'PhoneNumber ASC' THEN "PhoneNumber" END ASC, CASE WHEN @sortExpression = 'PhoneNumber DESC' THEN "PhoneNumber" END DESC, @@ -178,26 +169,16 @@ private static string GetSortExpression(string? sortColumnName, bool isAscending { if (string.IsNullOrWhiteSpace(sortColumnName)) { - return "ObserverName ASC"; + return "DisplayName ASC"; } var sortOrder = isAscendingSorting ? "ASC" : "DESC"; - if (string.Equals(sortColumnName, "name", StringComparison.InvariantCultureIgnoreCase)) - { - return $"ObserverName {sortOrder}"; - } - - if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.FirstName), StringComparison.InvariantCultureIgnoreCase)) + if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.DisplayName), StringComparison.InvariantCultureIgnoreCase)) { - return $"{nameof(MonitoringObserverModel.FirstName)} {sortOrder}"; + return $"DisplayName {sortOrder}"; } - - if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.LastName), StringComparison.InvariantCultureIgnoreCase)) - { - return $"{nameof(MonitoringObserverModel.LastName)} {sortOrder}"; - } - + if (string.Equals(sortColumnName, nameof(MonitoringObserverModel.Email), StringComparison.InvariantCultureIgnoreCase)) { return $"{nameof(MonitoringObserverModel.Email)} {sortOrder}"; diff --git a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs index 37659fffb..b74edcd48 100644 --- a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs +++ b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs @@ -7,8 +7,7 @@ namespace Feature.MonitoringObservers; public class MonitoringObserverModel { public Guid Id { get; init; } - public string FirstName { get; init; } - public string LastName { get; init; } + public string DisplayName { get; init; } public string Email { get; init; } public string PhoneNumber { get; init; } public string[] Tags { get; init; } @@ -17,3 +16,5 @@ public class MonitoringObserverModel [JsonConverter(typeof(SmartEnumNameConverter))] public MonitoringObserverStatus Status { get; init; } } + + diff --git a/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx b/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx index 1a00ae0db..913f16ae9 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserversList/MonitoringObserversList.tsx @@ -49,28 +49,22 @@ function MonitoringObserversList() { const monitoringObserverColDefs: ColumnDef[] = useMemo(() => { return [ { + id: 'displayName', header: ({ column }) => , - accessorKey: 'name', + accessorFn: (row)=> row.displayName, enableSorting: true, enableGlobalFilter: true, - cell: ({ - row: { - original: { firstName, lastName }, - }, - }) => ( -

- {firstName} {lastName} -

- ), }, { + id: 'email', header: ({ column }) => , - accessorKey: 'email', + accessorFn: (row)=> row.email, enableSorting: true, }, { + id: 'tags', header: ({ column }) => , - accessorKey: 'tags', + accessorFn: (row)=> row.tags, cell: ({ row: { original: { tags }, @@ -78,13 +72,15 @@ function MonitoringObserversList() { }) => , }, { + id: 'phoneNumber', header: ({ column }) => , - accessorKey: 'phoneNumber', + accessorFn: (row)=> row.phoneNumber, enableSorting: true, }, { + id: 'status', header: ({ column }) => , - accessorKey: 'status', + accessorFn: (row)=> row.status, enableSorting: true, cell: ({ row: { @@ -93,8 +89,9 @@ function MonitoringObserversList() { }) => {status}, }, { + id: 'latestActivityAt', header: ({ column }) => , - accessorKey: 'latestActivityAt', + accessorFn: (row)=> row.latestActivityAt, enableSorting: true, cell: ({ row: { @@ -103,8 +100,8 @@ function MonitoringObserversList() { }) =>

{latestActivityAt ? format(latestActivityAt, DateTimeFormat) : '-'}

, }, { + id: 'actions', header: '', - accessorKey: 'action', enableSorting: true, cell: ({ row }) => ( diff --git a/web/src/features/monitoring-observers/models/monitoring-observer.ts b/web/src/features/monitoring-observers/models/monitoring-observer.ts index c2c57589b..6c703df1d 100644 --- a/web/src/features/monitoring-observers/models/monitoring-observer.ts +++ b/web/src/features/monitoring-observers/models/monitoring-observer.ts @@ -12,8 +12,7 @@ export enum MonitoringObserverStatus { export interface MonitoringObserver { id: string; - firstName: string; - lastName: string; + displayName: string; email: string; status: MonitoringObserverStatus; phoneNumber: string; From c9bec15f356774789248c165b8c3c1f215a7655d Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Wed, 13 Nov 2024 15:55:19 +0200 Subject: [PATCH 04/22] Add form access ui --- .../Feature.Coalitions/FormAccess/Endpoint.cs | 20 +- .../ListByObserver/Endpoint.cs | 14 +- .../ListEntries/Endpoint.cs | 17 +- api/src/Feature.Forms/Create/Endpoint.cs | 1 + api/src/Feature.Forms/FormFeatureInstaller.cs | 7 +- api/src/Feature.Forms/FromForm/Endpoint.cs | 1 + .../Feature.Forms/FromTemplate/Endpoint.cs | 1 + api/src/Feature.Forms/Get/Endpoint.cs | 1 + api/src/Feature.Forms/List/Endpoint.cs | 61 +++-- .../Feature.Forms/Models/FormAccessModel.cs | 7 + .../{ => Models}/FormFullModel.cs | 4 +- .../{ => Models}/FormSlimModel.cs | 4 +- .../ListByObserver/Endpoint.cs | 14 +- .../ListEntries/Endpoint.cs | 2 + .../Export/Endpoint.cs | 1 - .../List/Endpoint.cs | 14 +- .../MonitoringObserverModel.cs | 2 + .../ListRecipients/Endpoint.cs | 2 +- .../Feature.Notifications/Send/Endpoint.cs | 2 +- .../Endpoints/CreateEndpointTests.cs | 1 + .../Endpoints/GetEndpointTests.cs | 1 + .../Features/Coalition/CreateTests.cs | 2 +- .../Features/Coalition/DeleteTests.cs | 2 +- .../Features/Coalition/FormAccessTests.cs | 50 +++- .../HttpClientExtensions.cs | 16 ++ .../ui/multiple-select-dropdown.tsx | 5 +- web/src/context/election-round.store.tsx | 12 +- .../forms/components/Dashboard/Dashboard.tsx | 242 +++++++++++++++--- .../Dashboard/EditFormAccessDialog.tsx | 182 +++++++++++++ .../forms/components/EditForm/EditForm.tsx | 4 +- .../EditFormTranslation.tsx | 5 +- web/src/features/forms/models/form.ts | 6 + web/src/features/forms/queries.ts | 8 + .../EditMonitoringObserver.tsx | 2 +- .../MonitoringObserverDetails.tsx | 2 +- .../MonitoringObserverDetailsView.tsx | 2 +- .../MonitoringObserverTagsSelect.tsx | 2 +- .../models/monitoring-observer.ts | 2 + .../utils/column-visibility-options.tsx | 5 + web/src/locales/en.json | 3 +- web/src/locales/ro.json | 3 +- 41 files changed, 607 insertions(+), 125 deletions(-) create mode 100644 api/src/Feature.Forms/Models/FormAccessModel.cs rename api/src/Feature.Forms/{ => Models}/FormFullModel.cs (98%) rename api/src/Feature.Forms/{ => Models}/FormSlimModel.cs (92%) create mode 100644 web/src/features/forms/components/Dashboard/EditFormAccessDialog.tsx diff --git a/api/src/Feature.Coalitions/FormAccess/Endpoint.cs b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs index df84dd405..ac9c24430 100644 --- a/api/src/Feature.Coalitions/FormAccess/Endpoint.cs +++ b/api/src/Feature.Coalitions/FormAccess/Endpoint.cs @@ -13,7 +13,7 @@ public class Endpoint( { public override void Configure() { - Post("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access"); + Put("/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access"); DontAutoTag(); Options(x => x.WithTags("coalitions")); Policies(PolicyNames.NgoAdminsOnly); @@ -62,7 +62,7 @@ public override async Task> Execute .Distinct() .ToList(); - var monitoringNgosWithAccessToForm = await context.MonitoringNgos + var coalitionMonitoringNgoIds = await context.MonitoringNgos .Where(x => x.ElectionRoundId == req.ElectionRoundId && requestNgoMembers.Contains(x.NgoId) && coalitionMembersIds.Contains(x.Id)) @@ -71,11 +71,11 @@ public override async Task> Execute var ngosWithRevokedAccess = coalition.FormAccess - .Where(x => !monitoringNgosWithAccessToForm.Contains(x.MonitoringNgoId)) + .Where(x => !coalitionMonitoringNgoIds.Contains(x.MonitoringNgoId)) .ToList(); - var ngosWithFormAccess = - monitoringNgosWithAccessToForm.Where(x => coalition.FormAccess.All(fa => fa.MonitoringNgoId != x)) + var ngosGainedFormAccess = + coalitionMonitoringNgoIds.Where(x => coalition.FormAccess.All(fa => fa.MonitoringNgoId != x)) .Select(id => CoalitionFormAccess.Create(coalition.Id, id, req.FormId)) .ToList(); @@ -83,12 +83,16 @@ public override async Task> Execute { context.CoalitionFormAccess.RemoveRange(ngosWithRevokedAccess); - await Task.WhenAll(ngosWithRevokedAccess.Select(memberId => cleanupService.CleanupFormSubmissionsAsync(req.ElectionRoundId, req.CoalitionId, memberId.MonitoringNgoId, form.Id))); + foreach (var memberId in ngosWithRevokedAccess) + { + await cleanupService.CleanupFormSubmissionsAsync( + req.ElectionRoundId, req.CoalitionId, memberId.MonitoringNgoId, form.Id); + } } - if (ngosWithFormAccess.Any()) + if (ngosGainedFormAccess.Any()) { - await context.CoalitionFormAccess.AddRangeAsync(ngosWithFormAccess, ct); + await context.CoalitionFormAccess.AddRangeAsync(ngosGainedFormAccess, ct); } await context.SaveChangesAsync(ct); diff --git a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs index ea3133386..7d5705904 100644 --- a/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListByObserver/Endpoint.cs @@ -37,7 +37,12 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR mo."Id"::TEXT ILIKE @searchText + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter); SELECT @@ -147,7 +152,12 @@ ELSE NULL WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR mo."Id"::TEXT ILIKE @searchText + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) ) T WHERE diff --git a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs index f7ce2d069..a0a111b0e 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs @@ -39,7 +39,12 @@ SELECT SUM(count) WHERE mn."ElectionRoundId" = @electionRoundId AND mn."NgoId" = @ngoId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR mo."Id"::TEXT ILIKE @searchText) AND (@formType IS NULL OR 'PSI' = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) @@ -71,7 +76,12 @@ UNION ALL SELECT count(*) AS count WHERE mn."ElectionRoundId" = @electionRoundId AND mn."NgoId" = @ngoId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR mo."Id"::TEXT ILIKE @searchText) AND (@formType IS NULL OR f."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) @@ -218,7 +228,8 @@ UNION ALL AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText) + OR u."PhoneNumber" ILIKE @searchText + OR mo."Id"::TEXT ILIKE @searchText) AND (@formType IS NULL OR s."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) diff --git a/api/src/Feature.Forms/Create/Endpoint.cs b/api/src/Feature.Forms/Create/Endpoint.cs index 6d8b9423b..7582a0b98 100644 --- a/api/src/Feature.Forms/Create/Endpoint.cs +++ b/api/src/Feature.Forms/Create/Endpoint.cs @@ -1,5 +1,6 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; diff --git a/api/src/Feature.Forms/FormFeatureInstaller.cs b/api/src/Feature.Forms/FormFeatureInstaller.cs index 385513a16..59c39b2f5 100644 --- a/api/src/Feature.Forms/FormFeatureInstaller.cs +++ b/api/src/Feature.Forms/FormFeatureInstaller.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using Dapper; +using Feature.Forms.Models; +using Microsoft.Extensions.DependencyInjection; +using Vote.Monitor.Core.Converters; namespace Feature.Forms; @@ -6,6 +9,8 @@ public static class FormFeatureInstaller { public static IServiceCollection AddFormFeature(this IServiceCollection services) { + SqlMapper.AddTypeHandler(typeof(FormAccessModel[]), new JsonToObjectConverter()); + return services; } } diff --git a/api/src/Feature.Forms/FromForm/Endpoint.cs b/api/src/Feature.Forms/FromForm/Endpoint.cs index eb6848b1e..8c86209d3 100644 --- a/api/src/Feature.Forms/FromForm/Endpoint.cs +++ b/api/src/Feature.Forms/FromForm/Endpoint.cs @@ -1,5 +1,6 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; diff --git a/api/src/Feature.Forms/FromTemplate/Endpoint.cs b/api/src/Feature.Forms/FromTemplate/Endpoint.cs index 1ea295329..a93c2f90d 100644 --- a/api/src/Feature.Forms/FromTemplate/Endpoint.cs +++ b/api/src/Feature.Forms/FromTemplate/Endpoint.cs @@ -1,5 +1,6 @@ using Authorization.Policies; using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.FormTemplateAggregate; diff --git a/api/src/Feature.Forms/Get/Endpoint.cs b/api/src/Feature.Forms/Get/Endpoint.cs index e24a91cae..4c5052cc9 100644 --- a/api/src/Feature.Forms/Get/Endpoint.cs +++ b/api/src/Feature.Forms/Get/Endpoint.cs @@ -1,4 +1,5 @@ using Authorization.Policies.Requirements; +using Feature.Forms.Models; using Feature.Forms.Specifications; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Domain.Entities.CoalitionAggregate; diff --git a/api/src/Feature.Forms/List/Endpoint.cs b/api/src/Feature.Forms/List/Endpoint.cs index 8c92211d0..43d904ee5 100644 --- a/api/src/Feature.Forms/List/Endpoint.cs +++ b/api/src/Feature.Forms/List/Endpoint.cs @@ -1,6 +1,7 @@ using Authorization.Policies; using Authorization.Policies.Requirements; using Dapper; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.ConnectionFactory; @@ -37,7 +38,6 @@ public override async Task>, NotFound>> SELECT MN."ElectionRoundId", MN."Id" AS "MonitoringNgoId", - MN."FormsVersion", -- Check if MonitoringNgo is a coalition leader EXISTS ( SELECT @@ -106,6 +106,7 @@ @searchText IS NULL OR F."Code" ILIKE @searchText OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText ) AND ( @type IS NULL @@ -116,7 +117,6 @@ @status IS NULL OR F."Status" = @status ) UNION - -- If not a coalition leader, get published forms specific to the MonitoringNgo SELECT F."Id" FROM @@ -135,6 +135,7 @@ @searchText IS NULL OR F."Code" ILIKE @searchText OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText ) AND ( @type IS NULL @@ -151,7 +152,6 @@ @status IS NULL SELECT MN."ElectionRoundId", MN."Id" AS "MonitoringNgoId", - MN."FormsVersion", -- Check if MonitoringNgo is a coalition leader EXISTS ( SELECT @@ -196,7 +196,23 @@ @status IS NULL F."Icon", F."LastModifiedOn", F."LastModifiedBy", - F."IsFormOwner" + F."IsFormOwner", + CASE WHEN f."IsFormOwner" THEN COALESCE( + (SELECT JSONB_AGG( + JSONB_BUILD_OBJECT( + 'NgoId', + N."Id", + 'Name', + N."Name" + ) + ) + FROM "CoalitionFormAccess" cfa + inner join "Coalitions" c on c."Id" = cfa."CoalitionId" + inner join "MonitoringNgos" mn on cfa."MonitoringNgoId" = mn."Id" + inner join "Ngos" n on mn."NgoId" = n."Id" + WHERE c."ElectionRoundId" = @electionRoundId + AND cfa."FormId" = F."Id"), + '[]'::JSONB) ELSE '[]'::jsonb END AS "FormAccess" FROM ( SELECT @@ -233,18 +249,8 @@ SELECT 1 ) AND C."ElectionRoundId" = @electionRoundId AND ( - ( - SELECT - "IsInACoalition" - FROM - "MonitoringNgoData" - ) - OR ( - SELECT - "IsCoalitionLeader" - FROM - "MonitoringNgoData" - ) + (SELECT "IsInACoalition" FROM "MonitoringNgoData") + OR (SELECT "IsCoalitionLeader" FROM "MonitoringNgoData") ) AND ( @searchText IS NULL @@ -252,6 +258,7 @@ @searchText IS NULL OR F."Code" ILIKE @searchText OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText ) AND ( @type IS NULL @@ -262,7 +269,6 @@ @status IS NULL OR F."Status" = @status ) UNION - -- If not a coalition leader, get published forms specific to the MonitoringNgo SELECT F."Id", F."Code", @@ -277,34 +283,24 @@ @status IS NULL F."Icon", COALESCE(F."LastModifiedOn", F."CreatedOn") AS "LastModifiedOn", COALESCE(UPDATER."DisplayName", CREATOR."DisplayName") AS "LastModifiedBy", - false as "IsFormOwner" + true as "IsFormOwner" FROM "Forms" F INNER JOIN "AspNetUsers" CREATOR ON F."CreatedBy" = CREATOR."Id" LEFT JOIN "AspNetUsers" UPDATER ON F."LastModifiedBy" = UPDATER."Id" WHERE F."ElectionRoundId" = @electionRoundId - AND F."MonitoringNgoId" = ( - SELECT - "MonitoringNgoId" - FROM - "MonitoringNgoData" - ) + AND F."MonitoringNgoId" = (SELECT "MonitoringNgoId" FROM "MonitoringNgoData") AND ( @searchText IS NULL OR @searchText = '' OR F."Code" ILIKE @searchText OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText ) - AND ( - @type IS NULL - OR F."FormType" = @type - ) - AND ( - @status IS NULL - OR F."Status" = @status - ) + AND (@type IS NULL OR F."FormType" = @type) + AND (@status IS NULL OR F."Status" = @status) ) F WHERE ( @@ -313,6 +309,7 @@ @searchText IS NULL OR F."Code" ILIKE @searchText OR F."Name" ->> F."DefaultLanguage" ILIKE @searchText OR F."Description" ->> F."DefaultLanguage" ILIKE @searchText + OR F."Id"::TEXT ILIKE @searchText ) AND ( @type IS NULL diff --git a/api/src/Feature.Forms/Models/FormAccessModel.cs b/api/src/Feature.Forms/Models/FormAccessModel.cs new file mode 100644 index 000000000..d07a0b17b --- /dev/null +++ b/api/src/Feature.Forms/Models/FormAccessModel.cs @@ -0,0 +1,7 @@ +namespace Feature.Forms.Models; + +public class FormAccessModel +{ + public Guid NgoId { get; set; } + public string Name { get; set; } +} diff --git a/api/src/Feature.Forms/FormFullModel.cs b/api/src/Feature.Forms/Models/FormFullModel.cs similarity index 98% rename from api/src/Feature.Forms/FormFullModel.cs rename to api/src/Feature.Forms/Models/FormFullModel.cs index b5ada17ce..e5087c019 100644 --- a/api/src/Feature.Forms/FormFullModel.cs +++ b/api/src/Feature.Forms/Models/FormFullModel.cs @@ -5,7 +5,7 @@ using Vote.Monitor.Form.Module.Mappers; using Vote.Monitor.Form.Module.Models; -namespace Feature.Forms; +namespace Feature.Forms.Models; public class FormFullModel { @@ -49,4 +49,4 @@ public static FormFullModel FromEntity(FormAggregate form) => form == null public IReadOnlyList Questions { get; init; } = []; -} \ No newline at end of file +} diff --git a/api/src/Feature.Forms/FormSlimModel.cs b/api/src/Feature.Forms/Models/FormSlimModel.cs similarity index 92% rename from api/src/Feature.Forms/FormSlimModel.cs rename to api/src/Feature.Forms/Models/FormSlimModel.cs index fe13dc99e..01aa897bc 100644 --- a/api/src/Feature.Forms/FormSlimModel.cs +++ b/api/src/Feature.Forms/Models/FormSlimModel.cs @@ -3,7 +3,7 @@ using Vote.Monitor.Core.Models; using Vote.Monitor.Domain.Entities.FormAggregate; -namespace Feature.Forms; +namespace Feature.Forms.Models; public class FormSlimModel { @@ -28,6 +28,8 @@ public class FormSlimModel public string LastModifiedBy { get; init; } public bool IsFormOwner { get; init; } + + public FormAccessModel[] FormAccess { get; init; } public LanguagesTranslationStatus LanguagesTranslationStatus { get; init; } } diff --git a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs index 96261f8b3..03191375a 100644 --- a/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListByObserver/Endpoint.cs @@ -38,7 +38,12 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter); SELECT "MonitoringObserverId", @@ -87,7 +92,12 @@ ELSE NULL INNER JOIN "AspNetUsers" U ON U."Id" = O."ApplicationUserId" WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR u."DisplayName" ILIKE @searchText OR u."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR u."DisplayName" ILIKE @searchText + OR u."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter)) T WHERE (@needsFollowUp IS NULL OR T."FollowUpStatus" = 'NeedsFollowUp') ORDER BY diff --git a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs index b084bf45b..184f910ad 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs @@ -48,6 +48,7 @@ public override async Task>, OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText ) AND (@level1 IS NULL OR PS."Level1" = @level1) AND (@level2 IS NULL OR PS."Level2" = @level2) @@ -195,6 +196,7 @@ @searchText IS NULL OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText ) AND (@level1 IS NULL OR PS."Level1" = @level1) AND (@level2 IS NULL OR PS."Level2" = @level2) diff --git a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs index 002e2bde4..5063539cd 100644 --- a/api/src/Feature.MonitoringObservers/Export/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/Export/Endpoint.cs @@ -4,7 +4,6 @@ using Authorization.Policies; using CsvHelper; using Dapper; -using Feature.MonitoringObservers.Parser; using Vote.Monitor.Domain.ConnectionFactory; using Vote.Monitor.Domain.Entities.MonitoringObserverAggregate; diff --git a/api/src/Feature.MonitoringObservers/List/Endpoint.cs b/api/src/Feature.MonitoringObservers/List/Endpoint.cs index cbefe7ccb..a2d1778be 100644 --- a/api/src/Feature.MonitoringObservers/List/Endpoint.cs +++ b/api/src/Feature.MonitoringObservers/List/Endpoint.cs @@ -32,7 +32,12 @@ SELECT COUNT(*) count WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR U."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR U."DisplayName" ILIKE @searchText + OR U."Email" ILIKE @searchText + OR U."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status); @@ -110,7 +115,12 @@ GROUP BY WHERE MN."ElectionRoundId" = @electionRoundId AND MN."NgoId" = @ngoId - AND (@searchText IS NULL OR @searchText = '' OR (U."DisplayName") ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText) + AND (@searchText IS NULL + OR @searchText = '' + OR U."DisplayName" ILIKE @searchText + OR U."Email" ILIKE @searchText + OR u."PhoneNumber" ILIKE @searchText + OR MO."Id"::TEXT ILIKE @searchText) AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@status IS NULL OR mo."Status" = @status) GROUP BY diff --git a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs index b74edcd48..554c2fc41 100644 --- a/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs +++ b/api/src/Feature.MonitoringObservers/MonitoringObserverModel.cs @@ -7,6 +7,8 @@ namespace Feature.MonitoringObservers; public class MonitoringObserverModel { public Guid Id { get; init; } + public string FirstName { get; init; } + public string LastName { get; init; } public string DisplayName { get; init; } public string Email { get; init; } public string PhoneNumber { get; init; } diff --git a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs index 42c2cb344..41bdb14ff 100644 --- a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs +++ b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs @@ -126,7 +126,7 @@ SELECT COUNT(DISTINCT OA."ObserverId") COUNT LEFT JOIN "PollingStations" ps ON OA."PollingStationId" = ps."Id" WHERE (@searchText IS NULL OR @searchText = '' - OR (U."DisplayName") ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText OR mo."Id"::text ILIKE @searchText) diff --git a/api/src/Feature.Notifications/Send/Endpoint.cs b/api/src/Feature.Notifications/Send/Endpoint.cs index 726a6aa44..88e734452 100644 --- a/api/src/Feature.Notifications/Send/Endpoint.cs +++ b/api/src/Feature.Notifications/Send/Endpoint.cs @@ -134,7 +134,7 @@ UNION ALL SELECT * LEFT JOIN "NotificationTokens" NT ON NT."ObserverId" = OA."ObserverId" WHERE (@searchText IS NULL OR @searchText = '' - OR (U."DisplayName") ILIKE @searchText + OR U."DisplayName" ILIKE @searchText OR U."Email" ILIKE @searchText OR u."PhoneNumber" ILIKE @searchText OR mo."Id"::text ILIKE @searchText) diff --git a/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs b/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs index a35e7e916..50864d6ee 100644 --- a/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs +++ b/api/tests/Feature.Forms.UnitTests/Endpoints/CreateEndpointTests.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Constants; using Vote.Monitor.Domain.Entities.FormAggregate; diff --git a/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs b/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs index 012eb338b..2c4d4c05c 100644 --- a/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs +++ b/api/tests/Feature.Forms.UnitTests/Endpoints/GetEndpointTests.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using Feature.Forms.Models; using Microsoft.AspNetCore.Authorization; using NSubstitute.ReturnsExtensions; using Vote.Monitor.Domain.Entities.FormAggregate; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs index a1e6d0a3b..f09a8c47b 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs @@ -1,6 +1,6 @@ using System.Net; using System.Net.Http.Json; -using Feature.Forms; +using Feature.Forms.Models; using Feature.NgoCoalitions.Models; using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Scenarios; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs index e20d8d07c..264e6db3e 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs @@ -1,7 +1,7 @@ using System.Net; using System.Net.Http.Json; using Feature.Form.Submissions.ListEntries; -using Feature.Forms; +using Feature.Forms.Models; using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Scenarios; using Vote.Monitor.Core.Models; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs index eb710b524..9897aab8a 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -1,6 +1,5 @@ using System.Net; using System.Net.Http.Json; -using Feature.Forms; using Feature.Forms.Models; using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Fakers; @@ -115,8 +114,8 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; - scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + scenarioData.NgoByName(Ngos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); // Assert @@ -165,8 +164,8 @@ public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; - scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + scenarioData.NgoByName(Ngos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); var pollingStationId = scenarioData.ElectionRound.PollingStationByName(PollingStations.Iasi); @@ -183,6 +182,35 @@ public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms submissionId.Should().HaveStatusCode(HttpStatusCode.OK); } + [Test] + public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithNgo(Ngos.Delta) + .WithElectionRound(ElectionRounds.A, + er => er + .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta, Ngos.Delta], c=>c.WithForm(sharedWithMembers:[Ngos.Beta]))) + .Please(); + + // Act + var electionRoundId = scenarioData.ElectionRoundId; + var coalitionId = scenarioData.ElectionRound.CoalitionId; + var formId = scenarioData.ElectionRound.Coalition.FormId; + + scenarioData.NgoByName(Ngos.Alfa).Admin + .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Delta) } }); + + // Assert + var deltaForms = scenarioData + .NgoByName(Ngos.Delta).Admin + .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); + + deltaForms.Items.Should().HaveCount(1); + } [Test] public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() @@ -197,8 +225,8 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - var response = await scenarioData.NgoByName(Ngos.Beta).Admin.PostAsJsonAsync( - $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + var response = await scenarioData.NgoByName(Ngos.Beta).Admin + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.NotFound); @@ -224,8 +252,8 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - var response = await scenarioData.ObserverByName(Observers.Alice).PostAsJsonAsync( - $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + var response = await scenarioData.ObserverByName(Observers.Alice) + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.Forbidden); @@ -245,8 +273,8 @@ public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - var response = await CreateClient().PostAsJsonAsync( - $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", + var response = await CreateClient() + .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs index 133d1fabb..06b87381b 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/HttpClientExtensions.cs @@ -34,6 +34,22 @@ public static void PostWithoutResponse( response.EnsureSuccessStatusCode(); } + + public static void PutWithoutResponse( + this HttpClient client, + [StringSyntax("Uri")] string? requestUri, + object value) + { + var response = client.PutAsJsonAsync(requestUri, value, new JsonSerializerOptions()).GetAwaiter().GetResult(); + + if (!response.IsSuccessStatusCode) + { + TestContext.Out.WriteLine(response.Content.ReadAsStringAsync().GetAwaiter().GetResult()); + } + + response.EnsureSuccessStatusCode(); + } + public static TResponse PostWithResponse( this HttpClient client, [StringSyntax("Uri")] string? requestUri, diff --git a/web/src/components/ui/multiple-select-dropdown.tsx b/web/src/components/ui/multiple-select-dropdown.tsx index 591bd3ed2..0b268c6b8 100644 --- a/web/src/components/ui/multiple-select-dropdown.tsx +++ b/web/src/components/ui/multiple-select-dropdown.tsx @@ -138,8 +138,9 @@ export const MultiSelectDropdown = React.forwardRef {selectedValues.length > 0 ? ( @@ -159,7 +160,7 @@ export const MultiSelectDropdown = React.forwardRef ) : (
- {placeholder} + {placeholder}
)} diff --git a/web/src/context/election-round.store.tsx b/web/src/context/election-round.store.tsx index f4dfc7cdf..b951b5cb6 100644 --- a/web/src/context/election-round.store.tsx +++ b/web/src/context/election-round.store.tsx @@ -41,19 +41,21 @@ export const CurrentElectionRoundStoreProvider = ({ children }: PropsWithChildre if (!storeRef.current) { storeRef.current = create()( persist( - (set, get) => ({ + (set) => ({ currentElectionRoundId: '', setCurrentElectionRoundId: (electionRoundId: string) => set({ currentElectionRoundId: electionRoundId }), - + isMonitoringNgoForCitizenReporting: false, setIsMonitoringNgoForCitizenReporting: (isMonitoringNgoForCitizenReporting: boolean) => set({ isMonitoringNgoForCitizenReporting }), - + isCoalitionLeader: false, setIsCoalitionLeader: (isCoalitionLeader: boolean) => set({ isCoalitionLeader }), }), { - name: 'current-election-round', // name of the item in the storage (must be unique), - })); + name: 'current-election-round', // name of the item in the storage (must be unique) + } + ) + ); } return ( diff --git a/web/src/features/forms/components/Dashboard/Dashboard.tsx b/web/src/features/forms/components/Dashboard/Dashboard.tsx index 9a0386a67..2895c6730 100644 --- a/web/src/features/forms/components/Dashboard/Dashboard.tsx +++ b/web/src/features/forms/components/Dashboard/Dashboard.tsx @@ -34,7 +34,7 @@ import { PhotoIcon, } from '@heroicons/react/24/outline'; import { useMutation } from '@tanstack/react-query'; -import { useNavigate } from '@tanstack/react-router'; +import { useNavigate, useRouter } from '@tanstack/react-router'; import { ColumnDef, createColumnHelper, Row } from '@tanstack/react-table'; import { useDebounce } from '@uidotdev/usehooks'; import { format } from 'date-fns'; @@ -44,6 +44,7 @@ import { formsKeys, useForms } from '../../queries'; import AddTranslationsDialog, { useAddTranslationsDialog } from './AddTranslationsDialog'; import CreateForm from './CreateForm'; import { FormFilters } from './FormFilters/FormFilters'; +import EditFormAccessDialog, { useEditFormAccessDialog } from './EditFormAccessDialog'; export default function FormsDashboard(): ReactElement { const navigate = useNavigate(); @@ -51,6 +52,7 @@ export default function FormsDashboard(): ReactElement { const debouncedSearch = useDebounce(search, 300); const [searchText, setSearchText] = useState(''); const { filteringIsActive } = useFilteringContainer(); + const router = useRouter(); const queryParams = useMemo(() => { const params = [ @@ -63,10 +65,13 @@ export default function FormsDashboard(): ReactElement { }, [searchText, debouncedSearch]); const addTranslationsDialog = useAddTranslationsDialog(); + const editFormAccessDialog = useEditFormAccessDialog(); + const confirm = useConfirm(); const { data: languages } = useLanguages(); - const { currentElectionRoundId, isMonitoringNgoForCitizenReporting } = useCurrentElectionRoundStore((s) => s); + const { currentElectionRoundId, isMonitoringNgoForCitizenReporting, isCoalitionLeader } = + useCurrentElectionRoundStore((s) => s); const columnHelper = createColumnHelper(); const formColDefs: ColumnDef[] = useMemo(() => { @@ -96,7 +101,6 @@ export default function FormsDashboard(): ReactElement { ), enableResizing: false, }), - columnHelper.display({ id: 'code', enableSorting: true, @@ -204,7 +208,77 @@ export default function FormsDashboard(): ReactElement { <> ), }), - columnHelper.display({ + ]; + + if (isMonitoringNgoForCitizenReporting) { + defaultColumns.splice( + 1, + 0, + columnHelper.display({ + id: 'icon', + enableSorting: true, + header: ({ column }) => ( + + ), + cell: ({ row }) => + row.depth === 0 ? ( + isNotNilOrWhitespace(row.original.icon) ? ( + + + + + + + + +
+
+
+
+ ) : ( + <> + ) + ) : ( + '' + ), + }) + ); + } + + if (isCoalitionLeader) { + defaultColumns.push( + columnHelper.display({ + id: 'sharedWith', + enableSorting: false, + header: ({ column }) => ( + + ), + cell: ({ row }) => + row.depth === 0 ? ( + row.original.formAccess.length ? ( + + + + + {row.original.formAccess.length} + + + + {row.original.formAccess.map((fa) => ( +
{fa.name}
+ ))} +
+
+
+ ) : ( + <>None + ) + ) : ( + null + ), + }) + ); + defaultColumns.push({ header: '', id: 'action', enableSorting: false, @@ -218,7 +292,9 @@ export default function FormsDashboard(): ReactElement { navigateToForm(row.original.id, row.original.defaultLanguage)}> View - + {row.depth === 0 && row.original.status === FormStatus.Published ? ( + editFormAccessDialog.trigger(row.original.id)}>Form access + ) : null} {row.depth === 0 ? ( ) : null} + {row.depth === 0 && row.original.status === FormStatus.Published ? ( handleObsoleteForm(row.original)}>Obsolete ) : null} @@ -260,8 +337,8 @@ export default function FormsDashboard(): ReactElement { row.original.status === FormStatus.Published ? ( <> Please note that this form is published and may contain associated data. Deleting this - form could result in the loss of any submitted answers from your observers. Once deleted,{' '} - the associated data cannot be retrieved + form could result in the loss of any submitted answers from your observers. Once + deleted, the associated data cannot be retrieved ) : ( 'This action is permanent and cannot be undone. Once deleted, this form cannot be retrieved.' @@ -271,7 +348,10 @@ export default function FormsDashboard(): ReactElement { cancelButton: 'Cancel', }) ) { - deleteFormMutation.mutate({ electionRoundId: currentElectionRoundId, formId: row.original.id }); + deleteFormMutation.mutate({ + electionRoundId: currentElectionRoundId, + formId: row.original.id, + }); } }}> Delete form @@ -306,46 +386,120 @@ export default function FormsDashboard(): ReactElement {
), - }), - ]; - - if (isMonitoringNgoForCitizenReporting) { - defaultColumns.splice( - 1, - 0, + }); + } else { + defaultColumns.push( columnHelper.display({ - id: 'icon', - enableSorting: true, - header: ({ column }) => ( - + header: '', + id: 'action', + enableSorting: false, + enableResizing: false, + cell: ({ row }) => ( + + + + + + navigateToForm(row.original.id, row.original.defaultLanguage)}> + View + + + {row.depth === 0 ? ( + navigateToEdit(row.original.id)}> + Edit + + ) : ( + navigateToEditTranslation(row.original.id, row.original.defaultLanguage)}> + Edit + + )} + + {row.depth === 0 ? ( + addTranslationsDialog.trigger(row.original.id, row.original.languages)}> + Add translations + + ) : null} + {row.depth === 0 && row.original.status === FormStatus.Published ? ( + handleObsoleteForm(row.original)}>Obsolete + ) : null} + {row.depth === 0 && row.original.status === FormStatus.Drafted ? ( + handlePublishForm(row.original)}>Publish + ) : null} + {row.depth === 0 ? ( + handleDuplicateForm(row.original)}>Duplicate + ) : null} + {row.depth === 0 ? ( + { + if ( + await confirm({ + title: `Delete form ${row.original.code}?`, + body: + row.original.status === FormStatus.Published ? ( + <> + Please note that this form is published and may contain associated data. Deleting this + form could result in the loss of any submitted answers from your observers. Once + deleted, the associated data cannot be retrieved + + ) : ( + 'This action is permanent and cannot be undone. Once deleted, this form cannot be retrieved.' + ), + actionButton: 'Delete', + actionButtonClass: buttonVariants({ variant: 'destructive' }), + cancelButton: 'Cancel', + }) + ) { + deleteFormMutation.mutate({ + electionRoundId: currentElectionRoundId, + formId: row.original.id, + }); + } + }}> + Delete form + + ) : ( + { + const languageCode = row.original.defaultLanguage; + const language = languages?.find((l) => languageCode === l.code); + const fullName = language ? `${language.name} / ${language.nativeName}` : ''; + + if ( + await confirm({ + title: `Delete translation ${fullName}?`, + body: 'This action is permanent and cannot be undone. Once deleted, this translation cannot be retrieved.', + actionButton: 'Delete', + actionButtonClass: buttonVariants({ variant: 'destructive' }), + cancelButton: 'Cancel', + }) + ) { + deleteTranslationMutation.mutate({ + electionRoundId: currentElectionRoundId, + formId: row.original.id, + languageCode, + }); + } + }}> + Delete translation + + )} + + ), - cell: ({ row }) => - row.depth === 0 ? ( - isNotNilOrWhitespace(row.original.icon) ? ( - - - - - - - - -
-
-
-
- ) : ( - <> - ) - ) : ( - '' - ), }) ); } return defaultColumns; - }, [currentElectionRoundId, isMonitoringNgoForCitizenReporting]); + }, [currentElectionRoundId, isMonitoringNgoForCitizenReporting, isCoalitionLeader]); const [isFiltering, setIsFiltering] = useState(filteringIsActive); @@ -397,6 +551,7 @@ export default function FormsDashboard(): ReactElement { }); queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); }, }); @@ -412,6 +567,7 @@ export default function FormsDashboard(): ReactElement { }); queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); }, onError: (error) => { @@ -445,6 +601,7 @@ export default function FormsDashboard(): ReactElement { }); queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); }, onError: () => { @@ -468,6 +625,7 @@ export default function FormsDashboard(): ReactElement { }); queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); }, onError: (error) => { @@ -489,6 +647,7 @@ export default function FormsDashboard(): ReactElement { description: 'Form deleted', }); await queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); }, }); @@ -548,6 +707,7 @@ export default function FormsDashboard(): ReactElement { getRowClassName={getRowClassName} /> + ); diff --git a/web/src/features/forms/components/Dashboard/EditFormAccessDialog.tsx b/web/src/features/forms/components/Dashboard/EditFormAccessDialog.tsx new file mode 100644 index 000000000..789663b33 --- /dev/null +++ b/web/src/features/forms/components/Dashboard/EditFormAccessDialog.tsx @@ -0,0 +1,182 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { create } from 'zustand'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from '@tanstack/react-router'; +import { authApi } from '@/common/auth-api'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { Switch } from '@/components/ui/switch'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { toast } from '@/components/ui/use-toast'; +import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { useCoalitionDetails } from '@/features/election-event/hooks/coalition-hooks'; +import { queryClient } from '@/main'; +import { FormBase } from '../../models/form'; +import { formsKeys } from '../../queries'; +import { sortBy } from 'lodash'; + +export interface EditFormAccessDialogProps { + isOpen: boolean; + formId: string; + trigger: (formId: string) => void; + dismiss: VoidFunction; +} + +export const useEditFormAccessDialog = create((set) => ({ + isOpen: false, + formId: '', + trigger: (formId: string) => set({ formId, isOpen: true }), + dismiss: () => set({ isOpen: false }), +})); + +function EditFormAccessDialog() { + const { formId, isOpen, trigger, dismiss } = useEditFormAccessDialog(); + const currentElectionRoundId = useCurrentElectionRoundStore((s) => s.currentElectionRoundId); + const form = queryClient.getQueryData(formsKeys.baseDetails(currentElectionRoundId, formId)); + const { data: coalitionDetails } = useCoalitionDetails(currentElectionRoundId); + const router = useRouter(); + + const [searchTerm, setSearchTerm] = useState(''); + const [ngosSharedWith, setNgosSharedWith] = useState([]); + + useEffect(() => { + const sharedWithNgos = form?.formAccess?.map((fa) => fa.ngoId) ?? []; + setNgosSharedWith(sharedWithNgos); + }, [form?.formAccess, isOpen, coalitionDetails?.members.length]); + + const onOpenChange = (open: boolean) => { + if (open) { + trigger(formId); + } else { + dismiss(); + setSearchTerm(''); + setNgosSharedWith([]); + } + }; + + const handleToggle = useCallback((ngoId: string) => { + setNgosSharedWith((prev) => (prev.includes(ngoId) ? prev.filter((id) => id !== ngoId) : [...prev, ngoId])); + }, []); + + const filteredNGOs = useMemo( + () => + sortBy(coalitionDetails?.members?.filter((ngo) => ngo.name.toLowerCase().includes(searchTerm.toLowerCase())), [ + (ngo) => ngo.name, + ]), + [coalitionDetails?.members, searchTerm] + ); + + const formAccessMutation = useMutation({ + mutationFn: ({ + electionRoundId, + coalitionId, + formId, + ngoMembersIds, + }: { + electionRoundId: string; + coalitionId: string; + formId: string; + ngoMembersIds: string[]; + }) => + authApi.put(`/election-rounds/${electionRoundId}/coalitions/${coalitionId}/forms/${formId}:access`, { + ngoMembersIds, + }), + onSuccess: async () => { + toast({ title: 'Success', description: 'Access modified' }); + await queryClient.invalidateQueries({ queryKey: formsKeys.all(currentElectionRoundId) }); + router.invalidate(); + dismiss(); + }, + }); + + function handleSubmit() { + formAccessMutation.mutate({ + electionRoundId: currentElectionRoundId, + coalitionId: coalitionDetails!.id, + formId, + ngoMembersIds: ngosSharedWith, + }); + } + + const handleToggleAll = (checked: boolean) => { + console.log(checked); + if (checked) { + setNgosSharedWith(filteredNGOs.map((ngo) => ngo.id)); + } else { + setNgosSharedWith([]); + } + }; + + const sharedWithAll = useMemo(() => { + return ngosSharedWith.length === (coalitionDetails?.members.length || 0); + }, [form?.formAccess, isOpen, coalitionDetails?.members.length, ngosSharedWith]); + + return ( + + e.preventDefault()} + onEscapeKeyDown={(e) => e.preventDefault()}> + + Assign this form to coalition members + + + + Select coalition members with access to this form + +
+ setSearchTerm(e.target.value)} + className='max-w-sm' + /> +
+ Toggle All + handleToggleAll(checked)} /> +
+ + + + NGO + Has access + + + + {filteredNGOs?.map((ngo) => ( + + {ngo.name} + + handleToggle(ngo.id)} /> + + + ))} + +
+ +
+ + + + + + +
+
+ ); +} + +export default EditFormAccessDialog; diff --git a/web/src/features/forms/components/EditForm/EditForm.tsx b/web/src/features/forms/components/EditForm/EditForm.tsx index 3fdc67932..dfeceed8b 100644 --- a/web/src/features/forms/components/EditForm/EditForm.tsx +++ b/web/src/features/forms/components/EditForm/EditForm.tsx @@ -34,7 +34,7 @@ import { import { queryClient } from '@/main'; import { Route } from '@/routes/forms_.$formId.edit'; import { useMutation } from '@tanstack/react-query'; -import { useBlocker, useNavigate } from '@tanstack/react-router'; +import { useBlocker, useNavigate, useRouter } from '@tanstack/react-router'; import { useEffect, useState } from 'react'; import { UpdateFormRequest } from '../../models/form'; import { formDetailsQueryOptions, formsKeys } from '../../queries'; @@ -222,6 +222,7 @@ export default function EditForm(): FunctionComponent { const confirm = useConfirm(); const [shouldExitEditor, setShouldExitEditor] = useState(false); const navigate = useNavigate(); + const router = useRouter(); const editQuestions = formData.questions.map((question) => { if (isNumberQuestion(question)) { @@ -429,6 +430,7 @@ export default function EditForm(): FunctionComponent { }); await queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId), type: 'all' }); + router.invalidate(); if (shouldExitEditor) { if ( diff --git a/web/src/features/forms/components/EditFormTranslation/EditFormTranslation.tsx b/web/src/features/forms/components/EditFormTranslation/EditFormTranslation.tsx index 9b0b8b944..8c10b3529 100644 --- a/web/src/features/forms/components/EditFormTranslation/EditFormTranslation.tsx +++ b/web/src/features/forms/components/EditFormTranslation/EditFormTranslation.tsx @@ -27,9 +27,10 @@ import { useCurrentElectionRoundStore } from '@/context/election-round.store'; import { cn, ensureTranslatedStringCorrectness } from '@/lib/utils'; import { queryClient } from '@/main'; import { Route } from '@/routes/forms_.$formId.edit-translation.$languageCode'; -import { useBlocker, useNavigate } from '@tanstack/react-router'; +import { useBlocker, useNavigate, useRouter } from '@tanstack/react-router'; import { useEffect, useState } from 'react'; import { UpdateFormRequest } from '../../models/form'; + import { formDetailsQueryOptions, formsKeys } from '../../queries'; import { EditDateQuestionType, @@ -51,6 +52,7 @@ export default function EditFormTranslation(): FunctionComponent { const confirm = useConfirm(); const [shouldExitEditor, setShouldExitEditor] = useState(false); const navigate = useNavigate(); + const router = useRouter(); const formQuestions = formData.questions.map((question) => { if (isNumberQuestion(question)) { @@ -237,6 +239,7 @@ export default function EditFormTranslation(): FunctionComponent { }); void queryClient.invalidateQueries({ queryKey: formsKeys.all(electionRoundId) }); + router.invalidate(); if (shouldExitEditor) { void navigate({ to: '/election-event/$tab', params: { tab: 'observer-forms' } }); diff --git a/web/src/features/forms/models/form.ts b/web/src/features/forms/models/form.ts index 2a22f2316..8efff3083 100644 --- a/web/src/features/forms/models/form.ts +++ b/web/src/features/forms/models/form.ts @@ -12,6 +12,11 @@ export const FormStatusList: FormStatus[] = [ FormStatus.Obsolete ] +export interface FormAccessModel { + ngoId: string; + name: string; +} + export interface FormBase { id: string; formType: FormType; @@ -26,6 +31,7 @@ export interface FormBase { lastModifiedBy: string; numberOfQuestions: number; languagesTranslationStatus: LanguagesTranslationStatus; + formAccess: FormAccessModel[] } export interface FormFull extends FormBase { diff --git a/web/src/features/forms/queries.ts b/web/src/features/forms/queries.ts index c7674ae9a..70d5988f5 100644 --- a/web/src/features/forms/queries.ts +++ b/web/src/features/forms/queries.ts @@ -3,6 +3,8 @@ import { DataTableParameters, PageResponse } from '@/common/types'; import { UseQueryResult, queryOptions, useQuery } from '@tanstack/react-query'; import { FormBase, FormFull } from './models/form'; import { buildURLSearchParams } from '@/lib/utils'; +import { queryClient } from '@/main'; +const STALE_TIME = 1000 * 60 * 5; // five minutes export const formsKeys = { all: (electionRoundId: string) => ['forms', electionRoundId] as const, @@ -11,6 +13,7 @@ export const formsKeys = { [...formsKeys.lists(electionRoundId), { ...params }] as const, details: (electionRoundId: string) => [...formsKeys.all(electionRoundId), 'detail'] as const, detail: (electionRoundId: string, id: string) => [...formsKeys.details(electionRoundId), id] as const, + baseDetails: (electionRoundId: string, id: string) => [...formsKeys.details(electionRoundId), 'base', id] as const, }; export const formDetailsQueryOptions = (electionRoundId: string, formId: string) => { @@ -58,8 +61,13 @@ export function useForms( throw new Error('Failed to fetch forms'); } + response.data.items.forEach((form) => { + queryClient.setQueryData(formsKeys.baseDetails(electionRoundId, form.id), form); + }); + return response.data; }, enabled: !!electionRoundId, + staleTime: STALE_TIME }); } diff --git a/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx b/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx index 0de0eacb2..61e4ae018 100644 --- a/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx +++ b/web/src/features/monitoring-observers/components/EditMonitoringObserver/EditMonitoringObserver.tsx @@ -97,7 +97,7 @@ export default function EditObserver() { return ( }> diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx index 0025b325d..133e36303 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverDetails/MonitoringObserverDetails.tsx @@ -37,7 +37,7 @@ export default function MonitoringObserverDetails(): FunctionComponent { return ( } - title={`${monitoringObserver.firstName} ${monitoringObserver.lastName}`}> + title={`${monitoringObserver.displayName}`}> Observer details diff --git a/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx b/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx index 166630e03..3e935de05 100644 --- a/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx +++ b/web/src/features/monitoring-observers/components/MonitoringObserverDetailsView/MonitoringObserverDetailsView.tsx @@ -44,7 +44,7 @@ export default function MonitoringObserverDetailsView(): FunctionComponent {

Name

- {monitoringObserver.firstName} {monitoringObserver.lastName} + {monitoringObserver.displayName}

diff --git a/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx b/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx index 1091b64db..3534eb42a 100644 --- a/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx +++ b/web/src/features/monitoring-observers/filtering/MonitoringObserverTagsSelect.tsx @@ -30,7 +30,7 @@ export const MonitoringObserverTagsSelect: FC onValueChange={toggleTagsFilter} placeholder='Observer tags' defaultValue={currentTags} - className='text-slate-700' + className='text-slate-900' selectionDisplay={
Observer tags diff --git a/web/src/features/monitoring-observers/models/monitoring-observer.ts b/web/src/features/monitoring-observers/models/monitoring-observer.ts index 6c703df1d..56e2ac760 100644 --- a/web/src/features/monitoring-observers/models/monitoring-observer.ts +++ b/web/src/features/monitoring-observers/models/monitoring-observer.ts @@ -12,6 +12,8 @@ export enum MonitoringObserverStatus { export interface MonitoringObserver { id: string; + firstName: string; + lastName: string; displayName: string; email: string; status: MonitoringObserverStatus; diff --git a/web/src/features/responses/utils/column-visibility-options.tsx b/web/src/features/responses/utils/column-visibility-options.tsx index 76adaa1d2..6ef9c043a 100644 --- a/web/src/features/responses/utils/column-visibility-options.tsx +++ b/web/src/features/responses/utils/column-visibility-options.tsx @@ -245,6 +245,11 @@ export const citizenReportsDefaultColumns: TableColumnVisibilityState Date: Wed, 13 Nov 2024 16:45:00 +0200 Subject: [PATCH 05/22] Remove unused columns --- .../GetFilters/Endpoint.cs | 86 +++++++++++++++---- .../FormSubmissionDetails.tsx | 5 -- .../FormSubmissionsByEntryTable.tsx | 1 + .../IncidentReportDetails.tsx | 5 -- .../responses/models/form-submission.ts | 1 - .../responses/models/incident-report.ts | 1 - .../features/responses/utils/column-defs.tsx | 32 ------- .../utils/column-visibility-options.tsx | 8 -- 8 files changed, 68 insertions(+), 71 deletions(-) diff --git a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs index bdfded7e6..8eed13bfa 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs @@ -53,25 +53,73 @@ SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", -- ===================================================================================== - SELECT DISTINCT F."Id" AS "FormId", - F."Name" ->> F."DefaultLanguage" "FormName", - F."Code" "FormCode" - FROM "FormSubmissions" FS - INNER JOIN "Forms" F ON F."Id" = FS."FormId" - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId + WITH + "MonitoringNgoDetails" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 + ), + -- if ngo is coalition leader they need to see all the responses + "AvailableMonitoringObservers" AS ( + SELECT + MO."Id", + MO."MonitoringNgoId", + U."DisplayName" + FROM + "Coalitions" C + INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" + INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgoDetails" MND ON MND."MonitoringNgoId" = MN."Id" + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + WHERE + CM."ElectionRoundId" = MN."ElectionRoundId" + AND ( + MND."IsCoalitionLeader" + OR MN."NgoId" = @ngoId + ) + ) + SELECT DISTINCT + F."Id" AS "FormId", + F."Name" ->> F."DefaultLanguage" "FormName", + F."Code" "FormCode" + FROM + "FormSubmissions" FS + INNER JOIN "Forms" F ON F."Id" = FS."FormId" + INNER JOIN "AvailableMonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" + WHERE + FS."ElectionRoundId" = @electionRoundId UNION ALL - SELECT DISTINCT F."Id" AS "FormId", - F."Name" ->> F."DefaultLanguage" "FormName", - F."Code" "FormCode" - FROM "PollingStationInformation" PSI - INNER JOIN "PollingStationInformationForms" F ON F."Id" = PSI."PollingStationInformationFormId" - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId + SELECT DISTINCT + F."Id" AS "FormId", + F."Name" ->> F."DefaultLanguage" "FormName", + F."Code" "FormCode" + FROM + "PollingStationInformation" PSI + INNER JOIN "PollingStationInformationForms" F ON F."Id" = PSI."PollingStationInformationFormId" + INNER JOIN "AvailableMonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + WHERE + PSI."ElectionRoundId" = @electionRoundId """; var queryArgs = new @@ -96,4 +144,4 @@ UNION ALL FormFilterOptions = formFilterOptions }); } -} \ No newline at end of file +} diff --git a/web/src/features/responses/components/FormSubmissionDetails/FormSubmissionDetails.tsx b/web/src/features/responses/components/FormSubmissionDetails/FormSubmissionDetails.tsx index c6a185489..6a520bb48 100644 --- a/web/src/features/responses/components/FormSubmissionDetails/FormSubmissionDetails.tsx +++ b/web/src/features/responses/components/FormSubmissionDetails/FormSubmissionDetails.tsx @@ -124,11 +124,6 @@ export default function FormSubmissionDetails(): FunctionComponent {
)} - -
-

Is completed:

- {formSubmission.isCompleted.toString()} -
{formSubmission.formType === ZFormType.Enum.PSI ? ( diff --git a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx index 252dd7c8f..6c380b028 100644 --- a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx @@ -43,6 +43,7 @@ export function FormSubmissionsByEntryTable({ searchText }: FormSubmissionsByEnt ['fromDateFilter', debouncedSearch.submissionsFromDate?.toISOString()], ['toDateFilter', debouncedSearch.submissionsToDate?.toISOString()], ['isCompletedFilter', debouncedSearch.formIsCompleted], + [''] ].filter(([_, value]) => value); return Object.fromEntries(params) as FormSubmissionsSearchParams; diff --git a/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx b/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx index cf7c1441e..4cc108ed5 100644 --- a/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx +++ b/web/src/features/responses/components/IncidentReportDetails/IncidentReportDetails.tsx @@ -138,11 +138,6 @@ export default function IncidentReportDetails(): FunctionComponent {
)} - -
-

Is completed:

- {incidentReport.isCompleted.toString()} -
diff --git a/web/src/features/responses/models/form-submission.ts b/web/src/features/responses/models/form-submission.ts index e1790c58e..5e814a51c 100644 --- a/web/src/features/responses/models/form-submission.ts +++ b/web/src/features/responses/models/form-submission.ts @@ -36,7 +36,6 @@ export interface FormSubmissionByEntry { tags: string[]; timeSubmitted: string; followUpStatus: FormSubmissionFollowUpStatus; - isCompleted: boolean; } export interface FormSubmissionByObserver { diff --git a/web/src/features/responses/models/incident-report.ts b/web/src/features/responses/models/incident-report.ts index f15e57663..930d47121 100644 --- a/web/src/features/responses/models/incident-report.ts +++ b/web/src/features/responses/models/incident-report.ts @@ -40,7 +40,6 @@ export interface IncidentReportByEntry { tags: string[]; timeSubmitted: string; followUpStatus: IncidentReportFollowUpStatus; - isCompleted: boolean; } export interface IncidentReportByObserver { diff --git a/web/src/features/responses/utils/column-defs.tsx b/web/src/features/responses/utils/column-defs.tsx index 57fd69554..ad1e7fcab 100644 --- a/web/src/features/responses/utils/column-defs.tsx +++ b/web/src/features/responses/utils/column-defs.tsx @@ -65,14 +65,6 @@ export const formSubmissionsByEntryColumnDefs: ColumnDef
{row.original.formName[row.original.defaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn:(row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn: (row)=> row.level1, @@ -237,14 +229,6 @@ export const observerFormSubmissionsColumnDefs: ColumnDef
{row.original.formName[row.original.defaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn: (row) => row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) => {row.original.isCompleted.toString()}, - }, { header: ({ column }) => , accessorFn: (row) => row.level1, @@ -1127,14 +1111,6 @@ export const incidentReportsByEntryColumnDefs: ColumnDef
{row.original.formName[row.original.formDefaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn: (row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn:(row)=> row.pollingStationLevel1, @@ -1302,14 +1278,6 @@ export const observerIncidentReportsColumnDefs: ColumnDef
{row.original.formName[row.original.formDefaultLanguage] ?? '-'}
, }, - { - header: ({ column }) => , - accessorFn:(row)=> row.isCompleted, - id: 'isCompleted', - enableSorting: true, - enableGlobalFilter: true, - cell: ({ row }) =>
{row.original.isCompleted.toString()}
, - }, { header: ({ column }) => , accessorFn:(row)=> row.locationType, diff --git a/web/src/features/responses/utils/column-visibility-options.tsx b/web/src/features/responses/utils/column-visibility-options.tsx index 6ef9c043a..bc09873de 100644 --- a/web/src/features/responses/utils/column-visibility-options.tsx +++ b/web/src/features/responses/utils/column-visibility-options.tsx @@ -16,7 +16,6 @@ export const formSubmissionsByEntryDefaultColumns: TableColumnVisibilityState[] = [ @@ -272,7 +269,6 @@ const incidentReportsByEntryColumnVisibilityOptions: ColumnOption[] = [ @@ -310,7 +306,6 @@ export const observersFormSubmissionsColumnVisibilityOptions: ColumnOption Date: Thu, 14 Nov 2024 14:02:43 +0200 Subject: [PATCH 06/22] Fix get filters --- .../GetFilters/Endpoint.cs | 59 +++++-- .../Vote.Monitor.Domain/DomainInstaller.cs | 29 ++- .../Vote.Monitor.Domain/VoteMonitorContext.cs | 23 +-- .../ApiTesting.cs | 13 +- .../Consts/Coalitions.cs | 7 - .../Consts/ElectionRounds.cs | 9 - .../Consts/Ngos.cs | 16 +- .../Consts/Observers.cs | 13 -- .../Consts/PollingStations.cs | 9 - .../Consts/ScenarioCoalition.cs | 7 + .../Consts/ScenarioElectionRound.cs | 9 + .../Consts/ScenarioObserver.cs | 13 ++ .../Consts/ScenarioPollingStation.cs | 9 + .../CustomWebApplicationFactory.cs | 21 ++- .../Db/PostgresTestDatabase.cs | 3 +- .../Db/TestcontainersTestDatabase.cs | 3 +- .../Features/Coalition/CreateTests.cs | 16 +- .../Features/Coalition/DeleteTests.cs | 78 ++++---- .../Features/Coalition/FormAccessTests.cs | 86 ++++----- .../Features/Coalition/UpdateTests.cs | 92 +++++----- .../FormSubmissions/GetFiltersTests.cs | 166 ++++++++++++++++++ .../AuditTrailInterceptorTests.cs | 10 +- .../Interceptors/AuditingInterceptorTests.cs | 7 +- .../Scenarios/CoalitionFormScenarioBuilder.cs | 18 +- .../Scenarios/CoalitionScenarioBuilder.cs | 16 +- .../Scenarios/ElectionRoundScenarioBuilder.cs | 43 +++-- .../MonitoringNgoFormScenarioBuilder.cs | 11 +- .../Scenarios/MonitoringNgoScenarioBuilder.cs | 11 +- .../Scenarios/ScenarioBuilder.cs | 101 ++++++----- .../Scenarios/ScenarioData.cs | 30 ++-- .../Vote.Monitor.Api.IntegrationTests.csproj | 4 + .../Vote.Monitor.TestUtils/TestContext.cs | 19 +- .../filtering/components/ActiveFilters.tsx | 1 - .../FormSubmissionsCompletionFilter.tsx | 20 --- web/src/features/filtering/filtering-enums.ts | 2 - .../PushMessageForm/PushMessageForm.tsx | 3 - .../models/push-message.ts | 1 - .../models/search-params.ts | 1 - .../FormSubmissionsAggregatedByFormTable.tsx | 2 - .../FormSubmissionsByEntryTable.tsx | 27 ++- .../FormSubmissionsFiltersByEntry.tsx | 2 - .../FormSubmissionsFiltersByForm.tsx | 3 +- .../FormSubmissionsTab/FormSubmissionsTab.tsx | 1 - .../IncidentReportsAggregatedByFormTable.tsx | 1 - .../IncidentReportsByEntryTable.tsx | 1 - .../IncidentReportsFiltersByEntry.tsx | 54 ------ .../IncidentReportsFiltersByForm.tsx | 54 ------ .../responses/models/search-params.ts | 1 - .../routes/responses/$formId.aggregated.tsx | 1 - 49 files changed, 616 insertions(+), 510 deletions(-) delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs delete mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs delete mode 100644 web/src/features/filtering/components/FormSubmissionsCompletionFilter.tsx diff --git a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs index 8eed13bfa..2701a3b6b 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs @@ -27,24 +27,64 @@ public override async Task, NotFound>> ExecuteAsync(Request } var sql = """ - WITH "CombinedTimestamps" AS ( + WITH + "MonitoringNgoDetails" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 + ), + -- if ngo is coalition leader they need to see all the responses + "AvailableMonitoringObservers" AS ( + SELECT + MO."Id", + MO."MonitoringNgoId", + U."DisplayName" + FROM + "Coalitions" C + INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" + INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = CM."MonitoringNgoId" + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + WHERE + CM."ElectionRoundId" = MN."ElectionRoundId" + AND ( + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") + OR MN."NgoId" = @ngoId + ) + ), + "CombinedTimestamps" AS ( -- First subquery for FormSubmissions SELECT MIN(COALESCE(FS."LastModifiedOn", FS."CreatedOn")) AS "FirstSubmissionTimestamp", MAX(COALESCE(FS."LastModifiedOn", FS."CreatedOn")) AS "LastSubmissionTimestamp" FROM "FormSubmissions" FS - INNER JOIN "MonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" + INNER JOIN "AvailableMonitoringObservers" MO ON MO."Id" = FS."MonitoringObserverId" WHERE FS."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId UNION ALL -- Second subquery for PollingStationInformation SELECT MIN(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "FirstSubmissionTimestamp", MAX(COALESCE(PSI."LastModifiedOn", PSI."CreatedOn")) AS "LastSubmissionTimestamp" FROM "PollingStationInformation" PSI - INNER JOIN "MonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" - WHERE PSI."ElectionRoundId" = @electionRoundId - AND MN."NgoId" = @ngoId) + INNER JOIN "AvailableMonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" + WHERE PSI."ElectionRoundId" = @electionRoundId) -- Final query to get the overall min and max SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", @@ -89,12 +129,11 @@ SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" INNER JOIN "MonitoringNgos" MN ON MN."Id" = CM."MonitoringNgoId" - INNER JOIN "MonitoringNgoDetails" MND ON MND."MonitoringNgoId" = MN."Id" INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" WHERE CM."ElectionRoundId" = MN."ElectionRoundId" AND ( - MND."IsCoalitionLeader" + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") OR MN."NgoId" = @ngoId ) ) diff --git a/api/src/Vote.Monitor.Domain/DomainInstaller.cs b/api/src/Vote.Monitor.Domain/DomainInstaller.cs index c86dabb42..e6c461c05 100644 --- a/api/src/Vote.Monitor.Domain/DomainInstaller.cs +++ b/api/src/Vote.Monitor.Domain/DomainInstaller.cs @@ -14,25 +14,35 @@ public static class DomainInstaller { public const string SectionKey = "Domain"; - public static IServiceCollection AddApplicationDomain(this IServiceCollection services, IConfiguration config, bool isProductionEnvironment) + public static IServiceCollection AddApplicationDomain(this IServiceCollection services, IConfiguration config, + bool isProductionEnvironment) { var connectionString = config.GetNpgsqlConnectionString("DbConnectionConfig"); NpgsqlConnection.GlobalTypeMapper.EnableDynamicJson(); - services.AddDbContext(options => + services.AddScoped(sp => + new AuditingInterceptor(sp.GetRequiredService(), + sp.GetRequiredService())); + + services.AddScoped(sp => + new AuditTrailInterceptor(sp.GetRequiredService(), + sp.GetRequiredService(), sp.GetRequiredService())); + + services.AddDbContext((sp, options) => { options.UseNpgsql(connectionString, sqlOptions => { sqlOptions.EnableRetryOnFailure( - maxRetryCount: 5, - maxRetryDelay: TimeSpan.FromSeconds(5), - errorCodesToAdd: null - ).UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - }); + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(5), + errorCodesToAdd: null + ) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + }).AddInterceptors(sp.GetRequiredService(), + sp.GetRequiredService()); options.EnableSensitiveDataLogging(!isProductionEnvironment); - }); services.AddSingleton(_ => new NpgsqlConnectionFactory(connectionString)); services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); @@ -65,7 +75,8 @@ public static IServiceCollection AddIdentity(this IServiceCollection services) = .AddDefaultTokenProviders() .Services; - public static async Task InitializeDatabasesAsync(this IServiceProvider services, CancellationToken cancellationToken = default) + public static async Task InitializeDatabasesAsync(this IServiceProvider services, + CancellationToken cancellationToken = default) { // Create a new scope to retrieve scoped services using var scope = services.CreateScope(); diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index 338906b22..8161f175b 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -35,18 +35,8 @@ namespace Vote.Monitor.Domain; public class VoteMonitorContext : IdentityDbContext, Guid> { - private readonly ISerializerService _serializerService; - private readonly ITimeProvider _timeProvider; - private readonly ICurrentUserProvider _currentUserProvider; - - public VoteMonitorContext(DbContextOptions options, - ISerializerService serializerService, - ITimeProvider timeProvider, - ICurrentUserProvider currentUserProvider) : base(options) + public VoteMonitorContext(DbContextOptions options) : base(options) { - _serializerService = serializerService; - _timeProvider = timeProvider; - _currentUserProvider = currentUserProvider; } public DbSet Countries { get; set; } @@ -165,9 +155,9 @@ protected override void OnModelCreating(ModelBuilder builder) builder.ApplyConfiguration(new IncidentReportConfiguration()); builder.ApplyConfiguration(new IncidentReportNoteConfiguration()); builder.ApplyConfiguration(new IncidentReportAttachmentConfiguration()); - + builder.ApplyConfiguration(new LocationConfiguration()); - + builder.ApplyConfiguration(new CoalitionConfiguration()); builder.ApplyConfiguration(new CoalitionMembershipConfiguration()); builder.ApplyConfiguration(new CoalitionFormAccessConfiguration()); @@ -177,11 +167,4 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura { configurationBuilder.ConfigureSmartEnum(); } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.AddInterceptors(new AuditingInterceptor(_currentUserProvider, _timeProvider)); - optionsBuilder.AddInterceptors(new AuditTrailInterceptor(_serializerService, _currentUserProvider, - _timeProvider)); - } } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs index 2df15bd06..5aaf6b91a 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/ApiTesting.cs @@ -1,4 +1,6 @@ -using Vote.Monitor.Api.IntegrationTests.Db; +using NSubstitute; +using Vote.Monitor.Api.IntegrationTests.Db; +using Vote.Monitor.Core.Services.Time; namespace Vote.Monitor.Api.IntegrationTests; @@ -7,13 +9,18 @@ public class ApiTesting { private static ITestDatabase _database = null!; private static CustomWebApplicationFactory _factory = null!; + private static ITimeProvider _apiTimeProvider = null!; [OneTimeSetUp] public async Task RunBeforeAnyTests() { _database = await TestDatabaseFactory.CreateAsync(); + _apiTimeProvider = Substitute.For(); + _apiTimeProvider.UtcNow.Returns(DateTime.UtcNow); + _apiTimeProvider.UtcNowDate.Returns(DateOnly.FromDateTime(DateTime.UtcNow)); + await _database.InitialiseAsync(); - _factory = new CustomWebApplicationFactory(_database.GetConnectionString(), _database.GetConnection()); + _factory = new CustomWebApplicationFactory(_database.GetConnectionString(), _database.GetConnection(), _apiTimeProvider); } public static string DbConnectionString => _database.GetConnectionString(); @@ -30,6 +37,8 @@ public static async Task ResetState() } } + public static ITimeProvider ApiTimeProvider => _apiTimeProvider; + public static HttpClient CreateClient() { return _factory.CreateClient(); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs deleted file mode 100644 index f438f3c05..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Coalitions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests.Consts; - -public class Coalitions -{ - public const string Youth = "Mega coalitia tineretului"; - public const string Old = "Brigata dementa"; -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs deleted file mode 100644 index 7ccf325f5..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ElectionRounds.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests.Consts; - -public class ElectionRounds -{ - public const string A = "ElectionRound-A"; - public const string B = "ElectionRound-B"; - public const string C = "ElectionRound-C"; - public const string D = "ElectionRound-D"; -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs index 6737f63ea..0f2a0d59f 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs @@ -1,15 +1,21 @@ namespace Vote.Monitor.Api.IntegrationTests.Consts; +public enum ScenarioNgo +{ + Alfa, + Beta, + Delta +} + public class Ngos { public static AlfaDetails Alfa => new(); public static BetaDetails Beta => new BetaDetails(); public static DeltaDetails Delta => new DeltaDetails(); - - + public class AlfaDetails { - public static implicit operator string(AlfaDetails _) => "Alfa NGO"; + public static implicit operator ScenarioNgo(AlfaDetails _) => ScenarioNgo.Alfa; public readonly string Anya = "anya@alfa.com"; public readonly string Ben = "ben@alfa.com"; public readonly string Cody = "cody@alfa.com"; @@ -17,7 +23,7 @@ public class AlfaDetails public class BetaDetails { - public static implicit operator string(BetaDetails _) => "Beta NGO"; + public static implicit operator ScenarioNgo(BetaDetails _) => ScenarioNgo.Beta; public readonly string Dana = "dana@beta.com"; public readonly string Ivy = "ivy@beta.com"; public readonly string Finn = "finn@beta.com"; @@ -25,7 +31,7 @@ public class BetaDetails public class DeltaDetails { - public static implicit operator string(DeltaDetails _) => "Delta NGO"; + public static implicit operator ScenarioNgo(DeltaDetails _) => ScenarioNgo.Delta; public readonly string Gia = "gia@delta.com"; public readonly string Mason = "mason@delta.com"; public readonly string Omar = "omar@delta.com"; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs deleted file mode 100644 index 0f1233118..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Observers.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests.Consts; - -public class Observers -{ - public const string Alice = "alice@observer.com"; - public const string Bob = "bod@observer.com"; - public const string Charlie = "charlie@observer.com"; - public const string Dave = "dave@observer.com"; - public const string Eve = "eve@obsever.com"; - public const string Frank = "frank@obsever.com"; - public const string Grace = "grace@obsever.com"; - public const string Mallory = "mallory@obsever.com"; -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs deleted file mode 100644 index 6180bdd93..000000000 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/PollingStations.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Vote.Monitor.Api.IntegrationTests.Consts; - -public class PollingStations -{ - public const string Iasi = "A"; - public const string Bacau = "B"; - public const string Cluj = "C"; - public const string D = "D"; -} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs new file mode 100644 index 000000000..c19a7d858 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioCoalition.cs @@ -0,0 +1,7 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioCoalition +{ + Youth, + Old, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs new file mode 100644 index 000000000..6f38a8741 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioElectionRound.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioElectionRound +{ + A, + B, + C, + D, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs new file mode 100644 index 000000000..e39301205 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioObserver.cs @@ -0,0 +1,13 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioObserver +{ + Alice, + Bob, + Charlie, + Dave, + Eve, + Frank, + Grace, + Mallory, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs new file mode 100644 index 000000000..c352b69bf --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioPollingStation.cs @@ -0,0 +1,9 @@ +namespace Vote.Monitor.Api.IntegrationTests.Consts; + +public enum ScenarioPollingStation +{ + Iasi, + Bacau, + Cluj, + Dolj, +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs index f531db23d..c305c67ea 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/CustomWebApplicationFactory.cs @@ -1,13 +1,15 @@ using System.Data.Common; +using Authorization.Policies; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Npgsql; using Serilog; +using Vote.Monitor.Api.IntegrationTests.Services; +using Vote.Monitor.Core.Services.Time; using Vote.Monitor.Domain; namespace Vote.Monitor.Api.IntegrationTests; @@ -15,15 +17,17 @@ namespace Vote.Monitor.Api.IntegrationTests; public class CustomWebApplicationFactory : WebApplicationFactory { private readonly DbConnection _connection; + private readonly ITimeProvider _timeProvider; private readonly NpgsqlConnectionStringBuilder _connectionDetails; public const string AdminEmail = "integration@testing.com"; public const string AdminPassword = "toTallyNotTestPassw0rd"; - public CustomWebApplicationFactory(string connectionString, DbConnection connection) + public CustomWebApplicationFactory(string connectionString, DbConnection connection, ITimeProvider timeProvider) { _connection = connection; _connectionDetails = new NpgsqlConnectionStringBuilder { ConnectionString = connectionString }; + _timeProvider = timeProvider; } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -53,24 +57,27 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureTestServices((services) => { + services.RemoveAll(); + services.AddSingleton(_ => _timeProvider); + services .RemoveAll>() .AddDbContext((sp, options) => { - options.AddInterceptors(sp.GetServices()); - options.UseNpgsql(_connection); + options.UseNpgsql(_connection) + .AddInterceptors(new AuditingInterceptor(new CurrentUserProvider(), _timeProvider)); }); - + services.AddLogging(logging => { Serilog.Debugging.SelfLog.Enable(Console.WriteLine); var loggerConfiguration = new LoggerConfiguration() - .WriteTo.NUnitOutput() .Enrich.FromLogContext() .Enrich.WithMachineName() .Enrich.WithEnvironmentUserName() - .Destructure.ToMaximumDepth(3); + .Destructure.ToMaximumDepth(3) + .WriteTo.NUnitOutput(); var logger = Log.Logger = loggerConfiguration.CreateLogger(); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs index c9686b35d..3f310dd3e 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/PostgresTestDatabase.cs @@ -38,8 +38,7 @@ public async Task InitialiseAsync() .UseNpgsql(_connectionString) .Options; - var context = new VoteMonitorContext(options, new SerializerService(NullLogger.Instance), - new CurrentUtcTimeProvider(), new CurrentUserProvider()); + var context = new VoteMonitorContext(options); context.Database.Migrate(); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs index 2c217abc6..1c2a2a7a0 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Db/TestcontainersTestDatabase.cs @@ -37,8 +37,7 @@ public async Task InitialiseAsync() .UseNpgsql(_connectionString) .Options; - var context = new VoteMonitorContext(options, new SerializerService(NullLogger.Instance), - new CurrentUtcTimeProvider(), new NoopCurrentUserProvider()); + var context = new VoteMonitorContext(options); context.Database.Migrate(); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs index f09a8c47b..899c8fd0d 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs @@ -19,7 +19,7 @@ public async Task PlatformAdmin_ShouldCreateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A) + .WithElectionRound(ScenarioElectionRound.A) .Please(); var coalitionName = Guid.NewGuid().ToString(); @@ -50,7 +50,7 @@ public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .WithNgo(Ngos.Delta) - .WithElectionRound(ElectionRounds.A, + .WithElectionRound(ScenarioElectionRound.A, electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) .Please(); @@ -83,7 +83,7 @@ public async Task ShouldAddLeaderAsMonitoringNgos_WhenTheyAreNotMonitoring() .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .WithNgo(Ngos.Delta) - .WithElectionRound(ElectionRounds.A, + .WithElectionRound(ScenarioElectionRound.A, electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) .Please(); @@ -115,7 +115,7 @@ public async Task ShouldNotGiveAccessToNgoFormsForCoalitionMembers() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, + .WithElectionRound(ScenarioElectionRound.A, electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A"))) .Please(); @@ -142,7 +142,7 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( public async Task NgoAdmin_CannotCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithElectionRound(ElectionRounds.A) + .WithElectionRound(ScenarioElectionRound.A) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .Please(); @@ -165,10 +165,10 @@ public async Task NgoAdmin_CannotCreateCoalition() public async Task Observer_CannotCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) + .WithObserver(ScenarioObserver.Alice) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A) + .WithElectionRound(ScenarioElectionRound.A) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -191,7 +191,7 @@ public async Task UnauthorizedClients_CannotCreateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A) + .WithElectionRound(ScenarioElectionRound.A) .Please(); var electionRoundId = scenarioData.ElectionRoundId; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs index 264e6db3e..b62035557 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs @@ -17,7 +17,7 @@ public async Task PlatformAdmin_ShouldDeleteCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -38,24 +38,24 @@ public async Task PlatformAdmin_ShouldDeleteCoalition() public async Task ShouldRemoveDataForMonitoringNgos() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) - .WithElectionRound(ElectionRounds.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithPollingStation(PollingStations.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg .WithForm("Common", [Ngos.Beta], form => form - .WithSubmission(Observers.Alice, PollingStations.Iasi) - .WithSubmission(Observers.Alice, PollingStations.Bacau) - .WithSubmission(Observers.Bob, PollingStations.Iasi) - .WithSubmission(Observers.Bob, PollingStations.Bacau)) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau)) ) ) .Please(); @@ -78,9 +78,9 @@ public async Task ShouldRemoveDataForMonitoringNgos() $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); var submission1 = - scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Iasi); + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, ScenarioPollingStation.Iasi); var submission2 = - scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Bacau); + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, ScenarioPollingStation.Bacau); alfaNgoSubmissions.Items.Select(x => x.SubmissionId) .Should() @@ -95,17 +95,17 @@ public async Task ShouldRemoveDataForMonitoringNgos() public async Task ShouldKeepFormForLeader() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) - .WithElectionRound(ElectionRounds.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithPollingStation(PollingStations.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg.WithForm("Common", [Ngos.Beta]) ) ) @@ -124,24 +124,24 @@ public async Task ShouldKeepFormForLeader() $"/api/election-rounds/{electionRoundId}/forms"); formResult.Items.Should().HaveCount(1); - formResult.Items.First().Id.Should().Be(scenarioData.ElectionRound.Coalition.Form.FormId); + formResult.Items.First().Id.Should().Be(scenarioData.ElectionRound.Coalition.FormId); } [Test] public async Task ShouldRemoveFormAccessFromExCoalitionMembers() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) - .WithElectionRound(ElectionRounds.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithPollingStation(PollingStations.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg .WithForm("Common", [Ngos.Beta]) ) @@ -169,7 +169,7 @@ public async Task NgoAdmin_CannotUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin()) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -191,10 +191,10 @@ public async Task NgoAdmin_CannotUpdateCoalition() public async Task Observer_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) + .WithObserver(ScenarioObserver.Alice) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -218,7 +218,7 @@ public async Task UnauthorizedClients_CannotUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs index 9897aab8a..9b8a38e57 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -20,13 +20,13 @@ public void ShouldNotGrantFormAccessForMonitoringObservers_WhenCreatingNewForm() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) - .WithElectionRound(ElectionRounds.A, + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); // Act @@ -45,10 +45,10 @@ public void ShouldNotGrantFormAccessForMonitoringObservers_WhenCreatingNewForm() // Assert var aliceForms = scenarioData - .ObserverByName(Observers.Alice) + .ObserverByName(ScenarioObserver.Alice) .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); var bobForms = scenarioData - .ObserverByName(Observers.Bob) + .ObserverByName(ScenarioObserver.Bob) .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); aliceForms.Forms.Should().BeEmpty(); @@ -62,13 +62,13 @@ public void ShouldNotGrantFormAccessForMonitoringNgos_WhenCreatingNewForm() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) - .WithElectionRound(ElectionRounds.A, + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); // Act @@ -100,13 +100,13 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) - .WithElectionRound(ElectionRounds.A, + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice).WithForm()) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); // Act @@ -120,11 +120,11 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() // Assert var aliceForms = scenarioData - .ObserverByName(Observers.Alice) + .ObserverByName(ScenarioObserver.Alice) .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); var bobForms = scenarioData - .ObserverByName(Observers.Bob) + .ObserverByName(ScenarioObserver.Bob) .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); var betaForms = scenarioData @@ -148,15 +148,15 @@ public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) - .WithElectionRound(ElectionRounds.A, + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithElectionRound(ScenarioElectionRound.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(Observers.Alice).WithForm()) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) .Please(); // Act @@ -168,11 +168,11 @@ public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); - var pollingStationId = scenarioData.ElectionRound.PollingStationByName(PollingStations.Iasi); + var pollingStationId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); var questions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; var submission = new FormSubmissionRequestFaker(formId, pollingStationId, questions).Generate(); - var observer = scenarioData.ObserverByName(Observers.Bob); + var observer = scenarioData.ObserverByName(ScenarioObserver.Bob); var submissionId = await observer.PostAsJsonAsync( $"/api/election-rounds/{electionRoundId}/form-submissions", @@ -190,9 +190,9 @@ public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .WithNgo(Ngos.Delta) - .WithElectionRound(ElectionRounds.A, + .WithElectionRound(ScenarioElectionRound.A, er => er - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta, Ngos.Delta], c=>c.WithForm(sharedWithMembers:[Ngos.Beta]))) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta, Ngos.Delta], c=>c.WithForm(sharedWithMembers:[Ngos.Beta]))) .Please(); // Act @@ -218,8 +218,8 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg.WithForm("A", []))) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg.WithForm("A", []))) .Please(); var electionRoundId = scenarioData.ElectionRoundId; var coalitionId = scenarioData.ElectionRound.CoalitionId; @@ -238,12 +238,12 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithElectionRound(ElectionRounds.A, er => er + .WithObserver(ScenarioObserver.Alice) + .WithElectionRound(ScenarioElectionRound.A, er => er .WithMonitoringNgo(Ngos.Alfa) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg .WithForm("A", []) - .WithMonitoringObserver(Ngos.Alfa, Observers.Alice) + .WithMonitoringObserver(Ngos.Alfa, ScenarioObserver.Alice) ) ) .Please(); @@ -252,7 +252,7 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - var response = await scenarioData.ObserverByName(Observers.Alice) + var response = await scenarioData.ObserverByName(ScenarioObserver.Alice) .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); @@ -265,8 +265,8 @@ public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, - er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], c => c.WithForm())) + .WithElectionRound(ScenarioElectionRound.A, + er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], c => c.WithForm())) .Please(); var electionRoundId = scenarioData.ElectionRoundId; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs index 2e0cffcdc..b235a7628 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs @@ -19,7 +19,7 @@ public async Task PlatformAdmin_ShouldUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -47,7 +47,7 @@ public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .WithNgo(Ngos.Delta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -80,7 +80,7 @@ public async Task NgosStayAsMonitoringNgos_WhenTheyAreKicked() .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) .WithNgo(Ngos.Delta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -111,37 +111,37 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( public async Task ShouldKeepObserverDatatForNgoForms_WhenNgoIsKicked() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) - .WithObserver(Observers.Charlie) - .WithObserver(Observers.Dave) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithObserver(ScenarioObserver.Charlie) + .WithObserver(ScenarioObserver.Dave) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithPollingStation(PollingStations.Cluj) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) .WithMonitoringNgo(Ngos.Alfa, ngo => ngo - .WithMonitoringObserver(Observers.Alice) - .WithMonitoringObserver(Observers.Bob) + .WithMonitoringObserver(ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioObserver.Bob) .WithForm("A", form => form .Publish() - .WithSubmission(Observers.Alice, PollingStations.Iasi) - .WithSubmission(Observers.Alice, PollingStations.Bacau) - .WithSubmission(Observers.Bob, PollingStations.Bacau) - .WithSubmission(Observers.Bob, PollingStations.Cluj)) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)) ) .WithMonitoringNgo(Ngos.Beta, ngo => ngo - .WithMonitoringObserver(Observers.Charlie) - .WithMonitoringObserver(Observers.Dave) + .WithMonitoringObserver(ScenarioObserver.Charlie) + .WithMonitoringObserver(ScenarioObserver.Dave) .WithForm("A", form => form .Publish() - .WithSubmission(Observers.Charlie, PollingStations.Iasi) - .WithSubmission(Observers.Charlie, PollingStations.Bacau) - .WithSubmission(Observers.Dave, PollingStations.Bacau) - .WithSubmission(Observers.Dave, PollingStations.Cluj)) + .WithSubmission(ScenarioObserver.Charlie, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Charlie, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Cluj)) ) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -172,25 +172,25 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( [Test] public async Task ShouldRemoveDataForSharedFormsOfKickedNgos() { - var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(Observers.Alice) - .WithObserver(Observers.Bob) + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) - .WithElectionRound(ElectionRounds.A, er => er - .WithPollingStation(PollingStations.Iasi) - .WithPollingStation(PollingStations.Bacau) - .WithPollingStation(PollingStations.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(Observers.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(Observers.Bob)) - .WithCoalition(Coalitions.Youth, Ngos.Alfa, [Ngos.Beta], + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg .WithForm("Common", [Ngos.Beta], form => form - .WithSubmission(Observers.Alice, PollingStations.Iasi) - .WithSubmission(Observers.Alice, PollingStations.Bacau) - .WithSubmission(Observers.Bob, PollingStations.Iasi) - .WithSubmission(Observers.Bob, PollingStations.Bacau)) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Iasi) + .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau)) ) ) .Please(); @@ -218,10 +218,12 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); var submission1 = - scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Iasi); + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, + ScenarioPollingStation.Iasi); var submission2 = - scenarioData.ElectionRound.Coalition.Form.GetSubmissionId(Observers.Alice, PollingStations.Bacau); - + scenarioData.ElectionRound.Coalition.FormData.GetSubmissionId(ScenarioObserver.Alice, + ScenarioPollingStation.Bacau); + alfaNgoSubmissions.Items .Should() .HaveCount(2) @@ -240,7 +242,7 @@ public async Task NgoAdmin_CannotUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -264,8 +266,8 @@ public async Task Observer_CannotUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithObserver(Observers.Alice) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithObserver(ScenarioObserver.Alice) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -289,7 +291,7 @@ public async Task UnauthorizedClients_CannotUpdateCoalition() var scenarioData = ScenarioBuilder.New(CreateClient) .WithNgo(Ngos.Alfa) .WithNgo(Ngos.Beta) - .WithElectionRound(ElectionRounds.A, er => er.WithCoalition(Coalitions.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs new file mode 100644 index 000000000..4d4ec4a1f --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs @@ -0,0 +1,166 @@ +using NSubstitute; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using GetFiltersResponse = Feature.Form.Submissions.GetFilters.Response; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class GetFiltersTests : BaseApiTestFixture +{ + private readonly DateTime _now = DateTime.UtcNow.AddDays(1000); + + [Test] + public void ShouldIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg + .WithForm("Shared", [Ngos.Alfa, Ngos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var firstSubmissionAt = _now.AddDays(-5); + var secondSubmissionAt = _now.AddDays(-3); + var thirdSubmissionAt = _now.AddDays(-1); + + ApiTimeProvider.UtcNow + .Returns(firstSubmissionAt, secondSubmissionAt, thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + + var iasiSubmission = + new FormSubmissionRequestFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var filters = scenarioData.NgoByName(Ngos.Alfa).Admin + .GetResponse($"/api/election-rounds/{electionRoundId}/form-submissions:filters"); + + // Assert + filters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(2) + .And + .BeEquivalentTo([alfaFormId, coalitionFormId]); + + filters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should().BeCloseTo(firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + filters.TimestampsFilterOptions.LastSubmissionTimestamp.Should().BeCloseTo(thirdSubmissionAt,TimeSpan.FromMicroseconds(100)); + } + + [Test] + public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsCoalitionMember() + { + // Arrange + var scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(Ngos.Alfa) + .WithNgo(Ngos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) + .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg + .WithForm("Shared", [Ngos.Alfa, Ngos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var firstSubmissionAt = _now.AddDays(-5); + var secondSubmissionAt = _now.AddDays(-3); + var thirdSubmissionAt = _now.AddDays(-1); + + ApiTimeProvider.UtcNow + .Returns(firstSubmissionAt, secondSubmissionAt, thirdSubmissionAt); + + var electionRoundId = scenarioData.ElectionRoundId; + var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + + var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; + var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + + var iasiSubmission = + new FormSubmissionRequestFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + + var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + clujSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + iasiSubmission); + + bob.PostWithoutResponse( + $"/api/election-rounds/{electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var filters = scenarioData.NgoByName(Ngos.Beta).Admin + .GetResponse($"/api/election-rounds/{electionRoundId}/form-submissions:filters"); + + // Assert + filters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([coalitionFormId]); + + filters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should().BeCloseTo(secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + filters.TimestampsFilterOptions.LastSubmissionTimestamp.Should().BeCloseTo(thirdSubmissionAt,TimeSpan.FromMicroseconds(100)); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs index 2634a2e91..501c64f64 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditTrailInterceptorTests.cs @@ -28,15 +28,15 @@ public void Init() { _fakeCurrentUserProvider = Substitute.For(); _fakeTimeProvider = Substitute.For(); - + var options = new DbContextOptionsBuilder() .UseNpgsql(DbConnectionString) + .AddInterceptors(new AuditTrailInterceptor(new SerializerService(NullLogger.Instance), + _fakeCurrentUserProvider, + _fakeTimeProvider)) .Options; - _context = new VoteMonitorContext(options, - new SerializerService(NullLogger.Instance), - _fakeTimeProvider, - _fakeCurrentUserProvider); + _context = new VoteMonitorContext(options); } [TearDown] diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs index ca9280fe3..7a0777abd 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Interceptors/AuditingInterceptorTests.cs @@ -18,17 +18,14 @@ public class AuditingInterceptorTests : BaseDbTestFixture public void Init() { _fakeTimeProvider = Substitute.For(); - var fakeSerializationService = Substitute.For(); _fakeCurrentUserProvider = Substitute.For(); var options = new DbContextOptionsBuilder() .UseNpgsql(DbConnectionString) + .AddInterceptors(new AuditingInterceptor(_fakeCurrentUserProvider,_fakeTimeProvider)) .Options; - _context = new VoteMonitorContext(options, - fakeSerializationService, - _fakeTimeProvider, - _fakeCurrentUserProvider); + _context = new VoteMonitorContext(options); } [TearDown] diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs index ac9983ef1..65cb1fef4 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs @@ -1,4 +1,5 @@ -using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; using Vote.Monitor.Api.IntegrationTests.Models; namespace Vote.Monitor.Api.IntegrationTests.Scenarios; @@ -18,23 +19,24 @@ internal CoalitionFormScenarioBuilder( _form = form; } - public CoalitionFormScenarioBuilder WithSubmission(string observerEmail, string pollingStationName) + public CoalitionFormScenarioBuilder WithSubmission(ScenarioObserver observer, ScenarioPollingStation pollingStation) { - var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStationName); + var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStation); var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); - var observer = _parentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observerEmail); + var observerClient = _parentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observer); - var submissionId = observer.PostWithResponse( + var submissionId = observerClient.PostWithResponse( $"/api/election-rounds/{_parentBuilder.ParentBuilder.ElectionRoundId}/form-submissions", submission).Id; - _submissions.Add($"{observerEmail}_{pollingStationName}", submissionId); + _submissions.Add($"{observer}_{pollingStation}", submissionId); return this; } - public Guid GetSubmissionId(string observerEmail, string pollingStationName) => - _submissions[$"{observerEmail}_{pollingStationName}"]; + public Guid GetSubmissionId(ScenarioObserver observer, ScenarioPollingStation pollingStation) => + _submissions[$"{observer}_{pollingStation}"]; public Guid FormId => _form.Id; + public CreateFormRequest Form => _form; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs index a7c11cdf3..71057894c 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs @@ -1,4 +1,5 @@ using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Models; namespace Vote.Monitor.Api.IntegrationTests.Scenarios; @@ -23,10 +24,10 @@ public CoalitionScenarioBuilder(HttpClient platformAdmin, HttpClient coalitionLe public Guid CoalitionId => _coalition.Id; - public CoalitionScenarioBuilder WithForm(string? formCode = null, string[]? sharedWithMembers = null, + public CoalitionScenarioBuilder WithForm(string? formCode = null, ScenarioNgo[]? sharedWithMembers = null, Action? cfg = null) { - sharedWithMembers ??= Array.Empty(); + sharedWithMembers ??= Array.Empty(); formCode ??= Guid.NewGuid().ToString(); var formRequest = Dummy.Form(); @@ -44,7 +45,7 @@ public CoalitionScenarioBuilder WithForm(string? formCode = null, string[]? shar var members = sharedWithMembers.Select(member => ParentBuilder.ParentBuilder.NgoIdByName(member)) .ToList(); - _coalitionLeaderAdminAdmin.PostWithoutResponse( + _coalitionLeaderAdminAdmin.PutWithoutResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/coalitions/{CoalitionId}/forms/{ngoForm.Id}:access", new { @@ -59,9 +60,9 @@ public CoalitionScenarioBuilder WithForm(string? formCode = null, string[]? shar return this; } - public CoalitionScenarioBuilder WithMonitoringObserver(string ngo, string observerEmail) + public CoalitionScenarioBuilder WithMonitoringObserver(ScenarioNgo ngo, ScenarioObserver observer) { - var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observerEmail); + var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observer); _platformAdmin .PostWithResponse( @@ -71,7 +72,8 @@ public CoalitionScenarioBuilder WithMonitoringObserver(string ngo, string observ return this; } - public CoalitionFormScenarioBuilder Form => _forms.First().Value; + public CreateFormRequest Form => _forms.First().Value.Form; + public CoalitionFormScenarioBuilder FormData => _forms.First().Value; public Guid FormId => _forms.First().Value.FormId; - public CoalitionFormScenarioBuilder FormByCode(string formCode) => _forms[formCode]; + public CreateFormRequest FormByCode(string formCode) => _forms[formCode].Form; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs index 3b2c53970..f3392d18a 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ElectionRoundScenarioBuilder.cs @@ -1,5 +1,7 @@ using Feature.NgoCoalitions.Models; +using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Models; +using ListMonitoringNgos = Feature.Monitoring.List.Response; namespace Vote.Monitor.Api.IntegrationTests.Scenarios; @@ -7,16 +9,16 @@ public class ElectionRoundScenarioBuilder { public Guid ElectionRoundId { get; } - private readonly Dictionary _pollingStations = new(); - private readonly Dictionary _monitoringNgos = new(); - private readonly Dictionary _coalitions = new(); + private readonly Dictionary _pollingStations = new(); + private readonly Dictionary _monitoringNgos = new(); + private readonly Dictionary _coalitions = new(); private readonly HttpClient _platformAdmin; public readonly ScenarioBuilder ParentBuilder; - public ElectionRoundScenarioBuilder WithPollingStation(string name) + public ElectionRoundScenarioBuilder WithPollingStation(ScenarioPollingStation pollingStation) { - var pollingStation = _platformAdmin.PostWithResponse( + var createdPollingStation = _platformAdmin.PostWithResponse( $"/api/election-rounds/{ElectionRoundId}/polling-stations", new { @@ -27,7 +29,7 @@ public ElectionRoundScenarioBuilder WithPollingStation(string name) Tags = new { } }); - _pollingStations[name] = pollingStation.Id; + _pollingStations[pollingStation] = createdPollingStation.Id; return this; } @@ -40,7 +42,7 @@ public ElectionRoundScenarioBuilder(ScenarioBuilder parentBuilder, _platformAdmin = platformAdmin; } - public ElectionRoundScenarioBuilder WithMonitoringNgo(string ngo, + public ElectionRoundScenarioBuilder WithMonitoringNgo(ScenarioNgo ngo, Action? cfg = null) { var monitoringNgo = _platformAdmin.PostWithResponse( @@ -58,7 +60,7 @@ public ElectionRoundScenarioBuilder WithMonitoringNgo(string ngo, return this; } - public ElectionRoundScenarioBuilder WithCoalition(string name, string leader, string[] members, + public ElectionRoundScenarioBuilder WithCoalition(ScenarioCoalition name, ScenarioNgo leader, ScenarioNgo[] members, Action? cfg = null) { var coalition = _platformAdmin.PostWithResponse( @@ -69,22 +71,37 @@ public ElectionRoundScenarioBuilder WithCoalition(string name, string leader, st LeaderId = ParentBuilder.NgoIdByName(leader), NgoMembersIds = members.Select(member => ParentBuilder.NgoIdByName(member)).ToArray(), }); + + var response = _platformAdmin.GetResponse($"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos"); + foreach (var monitoringNgo in response.MonitoringNgos) + { + var ngo = ParentBuilder.NgoById(monitoringNgo.NgoId); + + var monitoringNgoScenarioBuilder = new MonitoringNgoScenarioBuilder(ElectionRoundId, monitoringNgo.Id, this, + _platformAdmin, + ngo.builder); + + + _monitoringNgos.TryAdd(ngo.ngo, monitoringNgoScenarioBuilder); + } + var coalitionScenarioBuilder = new CoalitionScenarioBuilder(_platformAdmin, ParentBuilder.NgoByName(leader).Admin, this, coalition); cfg?.Invoke(coalitionScenarioBuilder); + _coalitions.Add(name, coalitionScenarioBuilder); return this; } public MonitoringNgoScenarioBuilder MonitoringNgo => _monitoringNgos.First().Value; - public MonitoringNgoScenarioBuilder MonitoringNgoByName(string name) => _monitoringNgos[name]; - public Guid MonitoringNgoIdByName(string name) => _monitoringNgos[name].MonitoringNgoId; + public MonitoringNgoScenarioBuilder MonitoringNgoByName(ScenarioNgo ngo) => _monitoringNgos[ngo]; + public Guid MonitoringNgoIdByName(ScenarioNgo ngo) => _monitoringNgos[ngo].MonitoringNgoId; public CoalitionScenarioBuilder Coalition => _coalitions.First().Value; public Guid CoalitionId => _coalitions.First().Value.CoalitionId; - public CoalitionScenarioBuilder CoalitionByName(string name) => _coalitions[name]; - public Guid CoalitionIdByName(string name) => _coalitions[name].CoalitionId; + public CoalitionScenarioBuilder CoalitionByName(ScenarioCoalition coalition) => _coalitions[coalition]; + public Guid CoalitionIdByName(ScenarioCoalition coalition) => _coalitions[coalition].CoalitionId; public Guid PollingStation => _pollingStations.First().Value; - public Guid PollingStationByName(string pollingStationName) => _pollingStations[pollingStationName]; + public Guid PollingStationByName(ScenarioPollingStation pollingStation) => _pollingStations[pollingStation]; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs index 90ef17865..b5ec8ce44 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -1,3 +1,4 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Fakers; using Vote.Monitor.Api.IntegrationTests.Models; @@ -38,20 +39,20 @@ public MonitoringNgoFormScenarioBuilder Publish(string? adminEmail = null) return this; } - public MonitoringNgoFormScenarioBuilder WithSubmission(string observerEmail, - string pollingStationName) + public MonitoringNgoFormScenarioBuilder WithSubmission(ScenarioObserver observer, + ScenarioPollingStation pollingStation) { if (!_formIsPublished) { throw new ArgumentException("Form is not published"); } - var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStationName); + var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStation); var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); - var observer = ParentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observerEmail); + var observerClient = ParentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observer); - observer.PostWithResponse( + observerClient.PostWithResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/form-submissions", submission); return this; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs index e1afbc828..4707c1bad 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -1,3 +1,4 @@ +using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Models; namespace Vote.Monitor.Api.IntegrationTests.Scenarios; @@ -6,7 +7,7 @@ public class MonitoringNgoScenarioBuilder { public Guid ElectionRoundId { get; } private readonly Dictionary _forms = new(); - private readonly Dictionary _monitoringObservers = new(); + private readonly Dictionary _monitoringObservers = new(); public readonly Guid MonitoringNgoId; public readonly ElectionRoundScenarioBuilder ParentBuilder; private readonly HttpClient _platformAdmin; @@ -51,17 +52,17 @@ public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, return this; } - public MonitoringNgoScenarioBuilder WithMonitoringObserver(string observerEmail) + public MonitoringNgoScenarioBuilder WithMonitoringObserver(ScenarioObserver observer) { - var observer = ParentBuilder.ParentBuilder.ObserverByName(observerEmail); - var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observerEmail); + var observerClient = ParentBuilder.ParentBuilder.ObserverByName(observer); + var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observer); _platformAdmin .PostWithResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{MonitoringNgoId}/monitoring-observers" , new { observerId = observerId }); - _monitoringObservers.Add(observerEmail, observer); + _monitoringObservers.Add(observer, observerClient); return this; } } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs index f49c9e920..0ec893b46 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs @@ -1,4 +1,5 @@ using Vote.Monitor.Api.Feature.Ngo; +using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Models; using Vote.Monitor.Domain.Constants; @@ -8,27 +9,37 @@ public class ScenarioBuilder { public HttpClient PlatformAdmin { get; } private readonly Func _clientFactory; - private readonly Dictionary _electionRounds = new(); - private readonly Dictionary _ngos = new(); - private readonly Dictionary _observers = new(); - + private readonly Dictionary _electionRounds = new(); + private readonly Dictionary _ngos = new(); + private readonly Dictionary _observers = new(); public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; - - public ElectionRoundScenarioBuilder ElectionRoundByName(string name) => _electionRounds[name]; - public Guid ElectionRoundIdByName(string name) => _electionRounds[name].ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + public HttpClient Observer => _observers.First().Value.Client; public Guid ObserverId => _observers.First().Value.Id; - - public HttpClient ObserverByName(string name) => _observers[name].Client; - public Guid ObserverIdByName(string name) => _observers[name].Id; - + + public HttpClient ObserverByName(ScenarioObserver name) => _observers[name].Client; + public Guid ObserverIdByName(ScenarioObserver name) => _observers[name].Id; + public NgoScenarioBuilder Ngo => _ngos.First().Value; public Guid NgoId => _ngos.First().Value.NgoId; - public NgoScenarioBuilder NgoByName(string name) => _ngos[name]; - public Guid NgoIdByName(string name) => _ngos[name].NgoId; - + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + public (ScenarioNgo ngo,NgoScenarioBuilder builder) NgoById(Guid ngoId) + { + var ngo = _ngos.First(x => x.Value.NgoId == ngoId); + + return (ngo.Key, ngo.Value); + } + + public Guid NgoIdByName(ScenarioNgo ngo) => _ngos[ngo].NgoId; + public static ScenarioBuilder New(Func clientFactory) { return new ScenarioBuilder(clientFactory); @@ -41,69 +52,57 @@ private ScenarioBuilder(Func clientFactory) _clientFactory = clientFactory; } - public ScenarioBuilder WithNgo(string ngoName, Action? cfg = null) + public ScenarioBuilder WithNgo(ScenarioNgo ngo, Action? cfg = null) { - var ngo = PlatformAdmin.PostWithResponse("/api/ngos", new { name = Guid.NewGuid().ToString() }); + var createdNgo = + PlatformAdmin.PostWithResponse("/api/ngos", new { name = Guid.NewGuid().ToString() }); - var ngoScenarioBuilder = new NgoScenarioBuilder(PlatformAdmin, _clientFactory, ngo.Id); + var ngoScenarioBuilder = new NgoScenarioBuilder(PlatformAdmin, _clientFactory, createdNgo.Id); ngoScenarioBuilder.WithAdmin(); cfg?.Invoke(ngoScenarioBuilder); - _ngos.Add(ngoName, ngoScenarioBuilder); + _ngos.Add(ngo, ngoScenarioBuilder); return this; } - public ScenarioBuilder WithElectionRound(string name) + public ScenarioBuilder WithElectionRound(ScenarioElectionRound electionRound, + Action? cfg = null) { - var electionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + var title = "ER" + electionRound + Guid.NewGuid(); + var createdElectionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", new { - CountryId = CountriesList.RO.Id, - Title = name + Guid.NewGuid(), - EnglishTitle = name, - StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(69) + countryId = CountriesList.RO.Id, + title = title, + englishTitle = title, + StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddYears(100) }); var electionRoundScenarioBuilder = - new ElectionRoundScenarioBuilder(this, electionRound.Id, PlatformAdmin); - - _electionRounds.Add(name, electionRoundScenarioBuilder); + new ElectionRoundScenarioBuilder(this, createdElectionRound.Id, PlatformAdmin); + _electionRounds.Add(electionRound, electionRoundScenarioBuilder); + + cfg?.Invoke(electionRoundScenarioBuilder); return this; } - public ScenarioBuilder WithElectionRound(string name, Action cfg) + public ScenarioBuilder WithObserver(ScenarioObserver observer) { - var electionRound = PlatformAdmin.PostWithResponse("/api/election-rounds", + var realEmail = $"{Guid.NewGuid()}@example.org"; + var createdObserver = PlatformAdmin.PostWithResponse("/api/observers", new { - countryId = CountriesList.RO.Id, - title = name + Guid.NewGuid(), - englishTitle = name, - StartDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(69) + FirstName = "Observer", + LastName = observer + "-" + Guid.NewGuid(), + Email = realEmail, + Password = "string" }); - - var electionRoundScenarioBuilder = - new ElectionRoundScenarioBuilder(this, electionRound.Id, PlatformAdmin); - - cfg(electionRoundScenarioBuilder); - - _electionRounds.Add(name, electionRoundScenarioBuilder); - - return this; - } - - public ScenarioBuilder WithObserver(string observerEmail) - { - var realEmail = $"{Guid.NewGuid()}@example.org"; - var observer = PlatformAdmin.PostWithResponse("/api/observers", - new { FirstName = "Observer", LastName = observerEmail, Email = realEmail, Password = "string" }); - var observerClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); - _observers.Add(observerEmail, (observer.Id, observerClient)); + _observers.Add(observer, (createdObserver.Id, observerClient)); return this; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs index 79cb9d57a..a30773120 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs @@ -1,15 +1,17 @@ -namespace Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.Consts; + +namespace Vote.Monitor.Api.IntegrationTests.Scenarios; public class ScenarioData { public HttpClient PlatformAdmin { get; } - private IReadOnlyDictionary _electionRounds; - private IReadOnlyDictionary _ngos; - private IReadOnlyDictionary _observers; + private IReadOnlyDictionary _electionRounds; + private IReadOnlyDictionary _ngos; + private IReadOnlyDictionary _observers; - public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary electionRounds, - IReadOnlyDictionary ngos, - IReadOnlyDictionary observers) + public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary electionRounds, + IReadOnlyDictionary ngos, + IReadOnlyDictionary observers) { PlatformAdmin = platformAdmin; _electionRounds = electionRounds; @@ -20,17 +22,17 @@ public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary _electionRounds.First().Value; public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; - public ElectionRoundScenarioBuilder ElectionRoundByName(string name) => _electionRounds[name]; - public Guid ElectionRoundIdByName(string name) => _electionRounds[name].ElectionRoundId; + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => _electionRounds[electionRound]; + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => _electionRounds[electionRound].ElectionRoundId; public HttpClient Observer => _observers.First().Value.Client; public Guid ObserverId => _observers.First().Value.Id; - public HttpClient ObserverByName(string name) => _observers[name].Client; - public Guid ObserverIdByName(string name) => _observers[name].Id; + public HttpClient ObserverByName(ScenarioObserver observer) => _observers[observer].Client; + public Guid ObserverIdByName(ScenarioObserver observer) => _observers[observer].Id; public NgoScenarioBuilder Ngo => _ngos.First().Value; public Guid NgoId => _ngos.First().Value.NgoId; - public NgoScenarioBuilder NgoByName(string name) => _ngos[name]; - public HttpClient AdminOfNgo(string name) => _ngos[name].Admin; - public Guid NgoIdByName(string name) => _ngos[name].NgoId; + public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; + public HttpClient AdminOfNgo(ScenarioNgo name) => _ngos[name].Admin; + public Guid NgoIdByName(ScenarioNgo name) => _ngos[name].NgoId; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj index b6c9eca76..e693b30ba 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Vote.Monitor.Api.IntegrationTests.csproj @@ -53,4 +53,8 @@ + + + + diff --git a/api/tests/Vote.Monitor.TestUtils/TestContext.cs b/api/tests/Vote.Monitor.TestUtils/TestContext.cs index 6af6f317b..5db1e24aa 100644 --- a/api/tests/Vote.Monitor.TestUtils/TestContext.cs +++ b/api/tests/Vote.Monitor.TestUtils/TestContext.cs @@ -1,7 +1,4 @@ using Microsoft.EntityFrameworkCore; -using NSubstitute; -using Vote.Monitor.Core.Services.Security; -using Vote.Monitor.Core.Services.Serialization; using Vote.Monitor.Domain; using Vote.Monitor.Domain.Entities.FeedbackAggregate; using Vote.Monitor.Domain.Entities.LocationAggregate; @@ -11,11 +8,8 @@ namespace Vote.Monitor.TestUtils; public class TestContext : VoteMonitorContext { - public TestContext(DbContextOptions options, - ISerializerService serializerService, - ITimeProvider timeProvider, - ICurrentUserProvider currentUserProvider) - : base(options, serializerService, timeProvider, currentUserProvider) + public TestContext(DbContextOptions options) + : base(options) { } @@ -32,17 +26,10 @@ protected override void OnModelCreating(ModelBuilder builder) public static TestContext Fake() { - var timeProvider = Substitute.For(); - var serializationService = Substitute.For(); - var currentUserIdProvider = Substitute.For(); - var dbContextOptions = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()); - var context = new TestContext(dbContextOptions.Options, - serializationService, - timeProvider, - currentUserIdProvider); + var context = new TestContext(dbContextOptions.Options); return context; } diff --git a/web/src/features/filtering/components/ActiveFilters.tsx b/web/src/features/filtering/components/ActiveFilters.tsx index e1cfacc3b..db4feb795 100644 --- a/web/src/features/filtering/components/ActiveFilters.tsx +++ b/web/src/features/filtering/components/ActiveFilters.tsx @@ -54,7 +54,6 @@ const FILTER_LABELS = new Map([ [FILTER_KEY.FromDate, FILTER_LABEL.FromDate], [FILTER_KEY.ToDate, FILTER_LABEL.ToDate], [FILTER_KEY.SearchText, FILTER_LABEL.SearchText], - [FILTER_KEY.FormIsCompleted, FILTER_LABEL.FormCompleted], [FILTER_KEY.QuickReportIncidentCategory, FILTER_LABEL.QuickReportIncidentCategory], [FILTER_KEY.QuickReportFollowUpStatus, FILTER_LABEL.QuickReportFollowUpStatus], [FILTER_KEY.HasQuickReports, FILTER_LABEL.HasQuickReports], diff --git a/web/src/features/filtering/components/FormSubmissionsCompletionFilter.tsx b/web/src/features/filtering/components/FormSubmissionsCompletionFilter.tsx deleted file mode 100644 index 03fe81068..000000000 --- a/web/src/features/filtering/components/FormSubmissionsCompletionFilter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { BinarySelectFilter } from '@/features/filtering/components/SelectFilter'; -import { FILTER_KEY } from '@/features/filtering/filtering-enums'; -import { useFilteringContainer } from '@/features/filtering/hooks/useFilteringContainer'; -import { FC } from 'react'; - -export const FormSubmissionsCompletionFilter: FC = () => { - const { queryParams, navigateHandler } = useFilteringContainer(); - - const onChange = (value: string) => { - navigateHandler({ [FILTER_KEY.FormIsCompleted]: value }); - }; - - return ( - - ); -}; diff --git a/web/src/features/filtering/filtering-enums.ts b/web/src/features/filtering/filtering-enums.ts index 100928f63..4eac3fdf7 100644 --- a/web/src/features/filtering/filtering-enums.ts +++ b/web/src/features/filtering/filtering-enums.ts @@ -25,7 +25,6 @@ export const enum FILTER_KEY { FromDate = 'submissionsFromDate', ToDate = 'submissionsToDate', SearchText = 'searchText', - FormIsCompleted = 'formIsCompleted', QuickReportIncidentCategory ='incidentCategory', QuickReportFollowUpStatus ='quickReportFollowUpStatus', HasQuickReports ='hasQuickReports', @@ -53,7 +52,6 @@ export const enum FILTER_LABEL { FromDate = 'From date', ToDate = 'To Date', SearchText = 'Search text', - FormCompleted = 'Form completed', QuickReportIncidentCategory ='Incident category', QuickReportFollowUpStatus ='Quick report follow up status', HasQuickReports ='Has quick reports', diff --git a/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx b/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx index ae7889e1e..5dd7abce4 100644 --- a/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx +++ b/web/src/features/monitoring-observers/components/PushMessageForm/PushMessageForm.tsx @@ -32,7 +32,6 @@ import type { SendPushNotificationRequest } from '../../models/push-message'; import type { PushMessageTargetedObserversSearchParams } from '../../models/search-params'; import { targetedMonitoringObserverColDefs } from '../../utils/column-defs'; import { FILTER_KEY } from '@/features/filtering/filtering-enums'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { QuickReportsIncidentCategoryFilter } from '@/features/filtering/components/QuickReportsIncidentCategoryFilter'; import { FormSubmissionsFollowUpFilter } from '@/features/filtering/components/FormSubmissionsFollowUpFilter'; import { QuickReportsFollowUpFilter } from '@/features/filtering/components/QuickReportsFollowUpFilter'; @@ -89,7 +88,6 @@ function PushMessageForm(): FunctionComponent { formId: debouncedSearch.formId, fromDateFilter: debouncedSearch.submissionsFromDate?.toISOString(), toDateFilter: debouncedSearch.submissionsToDate?.toISOString(), - isCompletedFilter: toBoolean(debouncedSearch.formIsCompleted), monitoringObserverStatus: debouncedSearch.monitoringObserverStatus, quickReportIncidentCategory: debouncedSearch.incidentCategory, quickReportFollowUpStatus: debouncedSearch.quickReportFollowUpStatus, @@ -208,7 +206,6 @@ function PushMessageForm(): FunctionComponent { - diff --git a/web/src/features/monitoring-observers/models/push-message.ts b/web/src/features/monitoring-observers/models/push-message.ts index 0c95cd772..6091afa99 100644 --- a/web/src/features/monitoring-observers/models/push-message.ts +++ b/web/src/features/monitoring-observers/models/push-message.ts @@ -41,7 +41,6 @@ export interface SendPushNotificationRequest { questionsAnswered?: QuestionsAnswered; formId?: string; formTypeFilter?: FormType; - formIsCompleted?: boolean; followUpStatus?: FormSubmissionFollowUpStatus; monitoringObserverId?: string; hasFlaggedAnswers?: boolean; diff --git a/web/src/features/monitoring-observers/models/search-params.ts b/web/src/features/monitoring-observers/models/search-params.ts index 027a2332f..fcca16929 100644 --- a/web/src/features/monitoring-observers/models/search-params.ts +++ b/web/src/features/monitoring-observers/models/search-params.ts @@ -21,7 +21,6 @@ export const PushMessageTargetedObserversSearchParamsSchema = z.object({ questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), formId: z.string().optional(), formTypeFilter: ZFormType.optional(), - formIsCompleted: z.string().optional(), followUpStatus: z.nativeEnum(FormSubmissionFollowUpStatus).optional(), hasFlaggedAnswers: z.string().catch('').optional(), monitoringObserverId: z.string().catch('').optional(), diff --git a/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx b/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx index a7bfa4e17..407936de2 100644 --- a/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsAggregatedByFormTable/FormSubmissionsAggregatedByFormTable.tsx @@ -45,7 +45,6 @@ export function FormSubmissionsAggregatedByFormTable({ tagsFilter: search.tagsFilter, submissionsFromDate: search.submissionsFromDate, submissionsToDate: search.submissionsToDate, - formIsCompleted: search.formIsCompleted, }, }); }, @@ -70,7 +69,6 @@ export function FormSubmissionsAggregatedByFormTable({ ['formId', search.formId], ['fromDateFilter', search.submissionsFromDate?.toISOString()], ['toDateFilter', search.submissionsToDate?.toISOString()], - ['isCompletedFilter', search.formIsCompleted], ].filter(([_, value]) => value); return Object.fromEntries(params) as FormSubmissionsSearchParams; diff --git a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx index 6c380b028..85479f1c7 100644 --- a/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx +++ b/web/src/features/responses/components/FormSubmissionsByEntryTable/FormSubmissionsByEntryTable.tsx @@ -1,4 +1,4 @@ -import type { FunctionComponent } from '@/common/types'; +import type { FormSubmissionFollowUpStatus, FunctionComponent, QuestionsAnswered } from '@/common/types'; import { CardContent } from '@/components/ui/card'; import { QueryParamsDataTable } from '@/components/ui/DataTable/QueryParamsDataTable'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; @@ -15,6 +15,27 @@ type FormSubmissionsByEntryTableProps = { searchText: string; }; +export interface FormSubmissionsSearchRequest{ + searchText: string | undefined; + formTypeFilter: string | undefined; + hasFlaggedAnswers: boolean | undefined; + level1Filter: string | undefined; + level2Filter: string | undefined; + level3Filter: string | undefined; + level4Filter: string | undefined; + level5Filter: string | undefined; + pollingStationNumberFilter: string | undefined; + followUpStatus: FormSubmissionFollowUpStatus | undefined; + questionsAnswered: QuestionsAnswered | undefined; + hasNotes: boolean | undefined; + hasAttachments: boolean | undefined; + tagsFilter: string[] | undefined; + formId: string | undefined; + fromDateFilter: string | undefined; + toDateFilter: string | undefined; +} + + export function FormSubmissionsByEntryTable({ searchText }: FormSubmissionsByEntryTableProps): FunctionComponent { const navigate = useNavigate(); const search = Route.useSearch(); @@ -42,11 +63,9 @@ export function FormSubmissionsByEntryTable({ searchText }: FormSubmissionsByEnt ['formId', debouncedSearch.formId], ['fromDateFilter', debouncedSearch.submissionsFromDate?.toISOString()], ['toDateFilter', debouncedSearch.submissionsToDate?.toISOString()], - ['isCompletedFilter', debouncedSearch.formIsCompleted], - [''] ].filter(([_, value]) => value); - return Object.fromEntries(params) as FormSubmissionsSearchParams; + return Object.fromEntries(params) as FormSubmissionsSearchRequest; }, [searchText, debouncedSearch]); const navigateToFormSubmission = useCallback( diff --git a/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx b/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx index b0883dbed..b883c2cf4 100644 --- a/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx +++ b/web/src/features/responses/components/FormSubmissionsFiltersByEntry/FormSubmissionsFiltersByEntry.tsx @@ -1,6 +1,5 @@ import { PollingStationsFilters } from '@/components/PollingStationsFilters/PollingStationsFilters'; import { FilteringContainer } from '@/features/filtering/components/FilteringContainer'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { FormTypeFilter } from '@/features/filtering/components/FormTypeFilter'; import { MonitoringObserverTagsSelect } from '@/features/monitoring-observers/filtering/MonitoringObserverTagsSelect'; import { FC } from 'react'; @@ -18,7 +17,6 @@ export const FormSubmissionsFiltersByEntry: FC = () => { - diff --git a/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx b/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx index d693e2453..be9020650 100644 --- a/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx +++ b/web/src/features/responses/components/FormSubmissionsFiltersByForm/FormSubmissionsFiltersByForm.tsx @@ -1,6 +1,5 @@ import { PollingStationsFilters } from '@/components/PollingStationsFilters/PollingStationsFilters'; import { FilteringContainer } from '@/features/filtering/components/FilteringContainer'; -import { FormSubmissionsCompletionFilter } from '@/features/filtering/components/FormSubmissionsCompletionFilter'; import { FormSubmissionsFollowUpFilter } from '@/features/filtering/components/FormSubmissionsFollowUpFilter'; import { FormSubmissionsFormFilter } from '@/features/filtering/components/FormSubmissionsFormFilter'; import { FormSubmissionsFromDateFilter } from '@/features/filtering/components/FormSubmissionsFromDateFilter'; @@ -17,7 +16,7 @@ export const FormSubmissionsFiltersByForm: FC = () => { return ( - + diff --git a/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx b/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx index 31bfe1c0b..00581957f 100644 --- a/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx +++ b/web/src/features/responses/components/FormSubmissionsTab/FormSubmissionsTab.tsx @@ -74,7 +74,6 @@ export default function FormSubmissionsTab(): FunctionComponent { ['formId', search.formId], ['fromDateFilter', search.submissionsFromDate?.toISOString()], ['toDateFilter', search.submissionsToDate?.toISOString()], - ['isCompletedFilter', search.formIsCompleted], ].filter(([_, value]) => value); return Object.fromEntries(params); diff --git a/web/src/features/responses/components/IncidentReportsAggregatedByFormTable/IncidentReportsAggregatedByFormTable.tsx b/web/src/features/responses/components/IncidentReportsAggregatedByFormTable/IncidentReportsAggregatedByFormTable.tsx index 944498b4b..ae3270d15 100644 --- a/web/src/features/responses/components/IncidentReportsAggregatedByFormTable/IncidentReportsAggregatedByFormTable.tsx +++ b/web/src/features/responses/components/IncidentReportsAggregatedByFormTable/IncidentReportsAggregatedByFormTable.tsx @@ -29,7 +29,6 @@ export function IncidentReportsAggregatedByFormTable(): FunctionComponent { ['pollingStationNumberFilter', debouncedSearch.pollingStationNumberFilter], ['followUpStatus', debouncedSearch.incidentReportFollowUpStatus], ['locationType', debouncedSearch.incidentReportLocationType], - ['isCompletedFilter', debouncedSearch.formIsCompleted], ].filter(([_, value]) => value); return Object.fromEntries(params) as IncidentReportsSearchParams; diff --git a/web/src/features/responses/components/IncidentReportsByEntryTable/IncidentReportsByEntryTable.tsx b/web/src/features/responses/components/IncidentReportsByEntryTable/IncidentReportsByEntryTable.tsx index d8bd5d7bd..b04993efd 100644 --- a/web/src/features/responses/components/IncidentReportsByEntryTable/IncidentReportsByEntryTable.tsx +++ b/web/src/features/responses/components/IncidentReportsByEntryTable/IncidentReportsByEntryTable.tsx @@ -35,7 +35,6 @@ export function IncidentReportsByEntryTable({ searchText }: FormsTableByEntryPro ['pollingStationNumberFilter', debouncedSearch.pollingStationNumberFilter], ['followUpStatus', debouncedSearch.incidentReportFollowUpStatus], ['locationType', debouncedSearch.incidentReportLocationType], - ['isCompletedFilter', debouncedSearch.formIsCompleted], ].filter(([_, value]) => value); return Object.fromEntries(params) as IncidentReportsSearchParams; diff --git a/web/src/features/responses/components/IncidentReportsFiltersByEntry/IncidentReportsFiltersByEntry.tsx b/web/src/features/responses/components/IncidentReportsFiltersByEntry/IncidentReportsFiltersByEntry.tsx index 70e7c714c..9cf0f4859 100644 --- a/web/src/features/responses/components/IncidentReportsFiltersByEntry/IncidentReportsFiltersByEntry.tsx +++ b/web/src/features/responses/components/IncidentReportsFiltersByEntry/IncidentReportsFiltersByEntry.tsx @@ -100,26 +100,6 @@ export function IncidentReportsFiltersByEntry(): FunctionComponent { - - - - )} - {search.formIsCompleted && ( - - )} - {search.hasFlaggedAnswers && ( )} - - {search.formIsCompleted && ( - - )} )} diff --git a/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx b/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx index a7cfefff8..9a0f7717a 100644 --- a/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx +++ b/web/src/features/responses/components/IncidentReportsFiltersByForm/IncidentReportsFiltersByForm.tsx @@ -100,26 +100,6 @@ export function IncidentReportsFiltersByForm(): FunctionComponent { - - - - )} - {search.formIsCompleted && ( - - )} - {search.hasFlaggedAnswers && ( )} - - {search.formIsCompleted && ( - - )} )} diff --git a/web/src/features/responses/models/search-params.ts b/web/src/features/responses/models/search-params.ts index 2e92a84fc..8c341781f 100644 --- a/web/src/features/responses/models/search-params.ts +++ b/web/src/features/responses/models/search-params.ts @@ -44,7 +44,6 @@ export const FormSubmissionsSearchParamsSchema = ResponsesPageSearchParamsSchema questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), hasNotes: z.string().catch('').optional(), hasAttachments: z.string().catch('').optional(), - formIsCompleted: z.string().catch('').optional(), formId: z.string().optional(), submissionsFromDate: z.coerce.date().optional(), diff --git a/web/src/routes/responses/$formId.aggregated.tsx b/web/src/routes/responses/$formId.aggregated.tsx index e0296383d..d7d998d6e 100644 --- a/web/src/routes/responses/$formId.aggregated.tsx +++ b/web/src/routes/responses/$formId.aggregated.tsx @@ -51,7 +51,6 @@ export const SubmissionsAggregatedByFormSchema = z.object({ questionsAnswered: z.nativeEnum(QuestionsAnswered).optional(), hasNotes: z.string().catch('').optional(), hasAttachments: z.string().catch('').optional(), - formIsCompleted: z.string().catch('').optional(), submissionsFromDate: z.coerce.date().optional(), submissionsToDate: z.coerce.date().optional(), }); From 7fa56662edfd80f3d644ad94e92e1b46dce22394 Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Thu, 14 Nov 2024 17:04:13 +0200 Subject: [PATCH 07/22] Add data source filters for submissions --- .../GetAggregated/Endpoint.cs | 2 - .../GetFilters/Endpoint.cs | 44 ++-- .../GetFilters/Request.cs | 6 +- .../GetFilters/Validator.cs | 1 + .../ListByForm/Endpoint.cs | 5 +- .../ListEntries/Endpoint.cs | 197 ++++++++++++----- .../ListEntries/Request.cs | 6 +- .../ListEntries/Validator.cs | 1 + .../FormSubmissionsAggregateFilter.cs | 3 +- .../ListEntries/Endpoint.cs | 5 +- .../ListEntries/Request.cs | 3 +- .../ListRecipients/Endpoint.cs | 3 - .../ListRecipients/Request.cs | 3 +- .../Feature.Notifications/Send/Endpoint.cs | 3 - api/src/Feature.Notifications/Send/Request.cs | 3 +- .../Vote.Monitor.Core/Models/DataSource.cs | 29 +++ .../Models/TranslationStatus.cs | 2 +- .../Consts/{Ngos.cs => ScenarioNgos.cs} | 2 +- .../Features/Coalition/CreateTests.cs | 78 +++---- .../Features/Coalition/DeleteTests.cs | 76 +++---- .../Features/Coalition/FormAccessTests.cs | 112 +++++----- .../Features/Coalition/UpdateTests.cs | 108 ++++----- .../FormSubmissions/GetFiltersTests.cs | 206 ++++++++++-------- .../FormSubmissions/ListEntriesTests.cs | 197 +++++++++++++++++ .../Scenarios/CoalitionFormScenarioBuilder.cs | 2 +- .../Scenarios/CoalitionScenarioBuilder.cs | 17 +- .../MonitoringNgoFormScenarioBuilder.cs | 2 +- .../Scenarios/MonitoringNgoScenarioBuilder.cs | 20 +- .../Scenarios/ScenarioBuilder.cs | 37 ++-- .../Scenarios/ScenarioData.cs | 28 ++- .../TestCases/DataSourcesTestCases.cs | 13 ++ web/src/common/prev-data-source-store.ts | 2 +- web/src/common/types.ts | 2 +- .../DataSourceSwitcher/DataSourceSwitcher.tsx | 4 +- 34 files changed, 806 insertions(+), 416 deletions(-) create mode 100644 api/src/Vote.Monitor.Core/Models/DataSource.cs rename api/tests/Vote.Monitor.Api.IntegrationTests/Consts/{Ngos.cs => ScenarioNgos.cs} (94%) create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs create mode 100644 api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs diff --git a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs index 728e7ed91..285c6b286 100644 --- a/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetAggregated/Endpoint.cs @@ -130,7 +130,6 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA && x.Form.ElectionRoundId == req.ElectionRoundId && a.PollingStationId == x.PollingStationId && a.ElectionRoundId == x.ElectionRoundId) == 0)) - .Where(x => req.IsCompletedFilter == null || x.IsCompleted == req.IsCompletedFilter) .AsNoTracking() .AsSplitQuery() .ToListAsync(ct); @@ -246,7 +245,6 @@ private async Task, NotFound>> AggregateNgoFormSubmissionsA TagsFilter = req.TagsFilter, FollowUpStatus = req.FollowUpStatus, HasFlaggedAnswers = req.HasFlaggedAnswers, - IsCompletedFilter = req.IsCompletedFilter, MonitoringObserverStatus = req.MonitoringObserverStatus, PollingStationNumberFilter = req.PollingStationNumberFilter } diff --git a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs index 2701a3b6b..93362a665 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Endpoint.cs @@ -55,9 +55,7 @@ public override async Task, NotFound>> ExecuteAsync(Request -- if ngo is coalition leader they need to see all the responses "AvailableMonitoringObservers" AS ( SELECT - MO."Id", - MO."MonitoringNgoId", - U."DisplayName" + MO."Id" FROM "Coalitions" C INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" @@ -66,10 +64,19 @@ public override async Task, NotFound>> ExecuteAsync(Request INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" WHERE CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."ElectionRoundId" = @electionRoundId AND ( - (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") - OR MN."NgoId" = @ngoId - ) + (@dataSource = 'Ngo' AND MN."NgoId" = @ngoId) + OR + ( + @dataSource = 'Coalition' + AND + ( + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") + OR MN."NgoId" = @ngoId + ) + ) + ) ), "CombinedTimestamps" AS ( -- First subquery for FormSubmissions @@ -121,9 +128,7 @@ SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", -- if ngo is coalition leader they need to see all the responses "AvailableMonitoringObservers" AS ( SELECT - MO."Id", - MO."MonitoringNgoId", - U."DisplayName" + MO."Id" FROM "Coalitions" C INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" @@ -132,10 +137,19 @@ SELECT MIN("FirstSubmissionTimestamp") AS "FirstSubmissionTimestamp", INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" WHERE CM."ElectionRoundId" = MN."ElectionRoundId" - AND ( - (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") - OR MN."NgoId" = @ngoId - ) + AND C."ElectionRoundId" = @electionRoundId + AND ( + (@dataSource = 'Ngo' AND MN."NgoId" = @ngoId) + OR + ( + @dataSource = 'Coalition' + AND + ( + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") + OR MN."NgoId" = @ngoId + ) + ) + ) ) SELECT DISTINCT F."Id" AS "FormId", @@ -156,7 +170,6 @@ SELECT DISTINCT "PollingStationInformation" PSI INNER JOIN "PollingStationInformationForms" F ON F."Id" = PSI."PollingStationInformationFormId" INNER JOIN "AvailableMonitoringObservers" MO ON MO."Id" = PSI."MonitoringObserverId" - INNER JOIN "MonitoringNgos" MN ON MN."Id" = MO."MonitoringNgoId" WHERE PSI."ElectionRoundId" = @electionRoundId """; @@ -164,7 +177,8 @@ SELECT DISTINCT var queryArgs = new { electionRoundId = req.ElectionRoundId, - ngoId = req.NgoId + ngoId = req.NgoId, + dataSource = req.DataSource.ToString() }; SubmissionsTimestampsFilterOptions timestampFilterOptions; diff --git a/api/src/Feature.Form.Submissions/GetFilters/Request.cs b/api/src/Feature.Form.Submissions/GetFilters/Request.cs index a34764eb2..873a4ba1b 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Request.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Request.cs @@ -1,4 +1,6 @@ -using Vote.Monitor.Core.Security; +using Microsoft.AspNetCore.Mvc; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Core.Security; namespace Feature.Form.Submissions.GetFilters; @@ -8,4 +10,6 @@ public class Request [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + + [FromQuery] public DataSource DataSource { get; set; } } diff --git a/api/src/Feature.Form.Submissions/GetFilters/Validator.cs b/api/src/Feature.Form.Submissions/GetFilters/Validator.cs index ad2a96849..3c6d2065a 100644 --- a/api/src/Feature.Form.Submissions/GetFilters/Validator.cs +++ b/api/src/Feature.Form.Submissions/GetFilters/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotNull(); } } diff --git a/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs b/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs index ec89f14aa..a0da61cbe 100644 --- a/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListByForm/Endpoint.cs @@ -60,7 +60,6 @@ 0 AS "NumberOfNotes" OR (@questionsAnswered = 'None' AND psi."NumberOfQuestionsAnswered" = 0)) AND (@hasNotes is NULL OR (TRUE AND @hasNotes = false) OR (FALSE AND @hasNotes = true)) AND (@hasAttachments is NULL OR (TRUE AND @hasAttachments = false) OR (FALSE AND @hasAttachments = true)) - AND (@isCompleted is NULL OR psi."IsCompleted" = @isCompleted) GROUP BY F."Id" UNION ALL @@ -112,7 +111,6 @@ UNION ALL AND (@tagsFilter IS NULL OR cardinality(@tagsFilter) = 0 OR mo."Tags" && @tagsFilter) AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) AND (@formId IS NULL OR fs."FormId" = @formId) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) AND (@questionsAnswered is null OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") @@ -145,7 +143,6 @@ GROUP BY hasNotes = req.HasNotes, hasAttachments = req.HasAttachments, questionsAnswered = req.QuestionsAnswered?.ToString(), - isCompleted = req.IsCompletedFilter }; IEnumerable aggregatedFormOverviews; @@ -157,4 +154,4 @@ GROUP BY return TypedResults.Ok(new Response { AggregatedForms = aggregatedFormOverviews.ToList() }); } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs index a0a111b0e..a232ae063 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Endpoint.cs @@ -26,24 +26,76 @@ public override async Task>, NotFo } var sql = """ + WITH + "MonitoringNgoDetails" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 + ), + -- if ngo is coalition leader they need to see all the responses + "AvailableMonitoringObservers" AS ( + SELECT + MO."Id", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."DisplayName" END AS "DisplayName", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."Email" END AS "Email", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."PhoneNumber" END AS "PhoneNumber", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN '{}'::text[] ELSE MO."Tags" END AS "Tags", + mo."Status" AS "Status" + FROM + "Coalitions" C + INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" + INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = CM."MonitoringNgoId" + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + WHERE + CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."ElectionRoundId" = @electionRoundId + AND ( + (@dataSource = 'Ngo' AND MN."NgoId" = @ngoId) + OR + ( + @dataSource = 'Coalition' + AND + ( + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") + OR MN."NgoId" = @ngoId + ) + ) + ) + ) SELECT SUM(count) FROM (SELECT count(*) AS count FROM "PollingStationInformation" psi INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" INNER JOIN "PollingStations" ps ON ps."Id" = psi."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId + INNER JOIN "AvailableMonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" + WHERE psi."ElectionRoundId" = @electionRoundId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) AND (@searchText IS NULL OR @searchText = '' - OR u."DisplayName" ILIKE @searchText - OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText OR mo."Id"::TEXT ILIKE @searchText) AND (@formType IS NULL OR 'PSI' = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) @@ -64,23 +116,18 @@ OR u."PhoneNumber" ILIKE @searchText AND (@hasAttachments is NULL OR (TRUE AND @hasAttachments = false) OR (FALSE AND @hasAttachments = true)) AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR PSI."IsCompleted" = @isCompleted) UNION ALL SELECT count(*) AS count FROM "FormSubmissions" fs INNER JOIN "Forms" f ON f."Id" = fs."FormId" INNER JOIN "PollingStations" ps ON ps."Id" = fs."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = fs."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId + INNER JOIN "AvailableMonitoringObservers" mo ON mo."Id" = fs."MonitoringObserverId" + WHERE fs."ElectionRoundId" = @electionRoundId AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) AND (@searchText IS NULL OR @searchText = '' - OR u."DisplayName" ILIKE @searchText - OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText OR mo."Id"::TEXT ILIKE @searchText) AND (@formType IS NULL OR f."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) @@ -106,10 +153,64 @@ OR u."PhoneNumber" ILIKE @searchText OR ((SELECT COUNT(1) FROM "Notes" N WHERE N."FormId" = fs."FormId" AND N."MonitoringObserverId" = fs."MonitoringObserverId" AND fs."PollingStationId" = N."PollingStationId") > 0 AND @hasNotes = true)) AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) ) c; - WITH polling_station_submissions AS ( + WITH + "MonitoringNgoDetails" AS ( + SELECT + MN."ElectionRoundId", + MN."Id" AS "MonitoringNgoId", + -- Check if MonitoringNgo is a coalition leader + EXISTS ( + SELECT + 1 + FROM + "CoalitionMemberships" CM + JOIN "Coalitions" C ON CM."CoalitionId" = C."Id" + WHERE + CM."MonitoringNgoId" = MN."Id" + AND CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."LeaderId" = MN."Id" + ) AS "IsCoalitionLeader" + FROM + "MonitoringNgos" MN + WHERE + MN."ElectionRoundId" = @electionRoundId + AND MN."NgoId" = @ngoId + LIMIT + 1 + ), + -- if ngo is coalition leader they need to see all the responses + "AvailableMonitoringObservers" AS ( + SELECT + MO."Id", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."DisplayName" END AS "DisplayName", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."Email" END AS "Email", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN MO."Id"::text ELSE U."PhoneNumber" END AS "PhoneNumber", + CASE WHEN ((SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") AND MN."NgoId" <> @ngoId) THEN '{}'::text[] ELSE MO."Tags" END AS "Tags", + mo."Status" AS "Status" + FROM + "Coalitions" C + INNER JOIN "CoalitionMemberships" CM ON C."Id" = CM."CoalitionId" + INNER JOIN "MonitoringObservers" MO ON MO."MonitoringNgoId" = CM."MonitoringNgoId" + INNER JOIN "MonitoringNgos" MN ON MN."Id" = CM."MonitoringNgoId" + INNER JOIN "AspNetUsers" U ON U."Id" = MO."ObserverId" + WHERE + CM."ElectionRoundId" = MN."ElectionRoundId" + AND C."ElectionRoundId" = @electionRoundId + AND ( + (@dataSource = 'Ngo' AND MN."NgoId" = @ngoId) + OR + ( + @dataSource = 'Coalition' + AND + ( + (SELECT "IsCoalitionLeader" FROM "MonitoringNgoDetails") + OR MN."NgoId" = @ngoId + ) + ) + ) + ), polling_station_submissions AS ( SELECT psi."Id" AS "SubmissionId", 'PSI' AS "FormType", 'PSI' AS "FormCode", @@ -126,16 +227,19 @@ WITH polling_station_submissions AS ( psi."IsCompleted" FROM "PollingStationInformation" psi INNER JOIN "PollingStationInformationForms" psif ON psif."Id" = psi."PollingStationInformationFormId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId + INNER JOIN "AvailableMonitoringObservers" mo ON mo."Id" = psi."MonitoringObserverId" + WHERE psi."ElectionRoundId" = @electionRoundId + AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) + AND (@searchText IS NULL OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."Id"::TEXT ILIKE @searchText) AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) AND (@formId IS NULL OR psi."PollingStationInformationFormId" = @formId) AND (@fromDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(PSI."LastModifiedOn", PSI."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR psi."IsCompleted" = @isCompleted) AND (@questionsAnswered IS NULL OR (@questionsAnswered = 'All' AND psif."NumberOfQuestions" = psi."NumberOfQuestionsAnswered") OR (@questionsAnswered = 'Some' AND psif."NumberOfQuestions" <> psi."NumberOfQuestionsAnswered") @@ -171,16 +275,19 @@ SELECT COUNT(1) fs."IsCompleted" FROM "FormSubmissions" fs INNER JOIN "Forms" f ON f."Id" = fs."FormId" - INNER JOIN "MonitoringObservers" mo ON fs."MonitoringObserverId" = mo."Id" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId + INNER JOIN "AvailableMonitoringObservers" mo ON fs."MonitoringObserverId" = mo."Id" + WHERE fs."ElectionRoundId" = @electionRoundId + AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) + AND (@searchText IS NULL OR @searchText = '' + OR mo."DisplayName" ILIKE @searchText + OR mo."Email" ILIKE @searchText + OR mo."PhoneNumber" ILIKE @searchText + OR mo."Id"::TEXT ILIKE @searchText) AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) AND (@monitoringObserverStatus IS NULL OR mo."Status" = @monitoringObserverStatus) AND (@formId IS NULL OR fs."FormId" = @formId) AND (@fromDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(FS."LastModifiedOn", FS."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR FS."IsCompleted" = @isCompleted) AND (@questionsAnswered IS NULL OR (@questionsAnswered = 'All' AND f."NumberOfQuestions" = fs."NumberOfQuestionsAnswered") OR (@questionsAnswered = 'Some' AND f."NumberOfQuestions" <> fs."NumberOfQuestionsAnswered") @@ -200,9 +307,9 @@ SELECT COUNT(1) ps."Level5", ps."Number", s."MonitoringObserverId", - u."DisplayName" AS "ObserverName", - u."Email", - u."PhoneNumber", + mo."DisplayName" AS "ObserverName", + mo."Email", + mo."PhoneNumber", mo."Status", mo."Tags", s."NumberOfQuestionsAnswered", @@ -218,19 +325,9 @@ UNION ALL SELECT * FROM form_submissions ) s INNER JOIN "PollingStations" ps ON ps."Id" = s."PollingStationId" - INNER JOIN "MonitoringObservers" mo ON mo."Id" = s."MonitoringObserverId" - INNER JOIN "MonitoringNgos" mn ON mn."Id" = mo."MonitoringNgoId" - INNER JOIN "Observers" o ON o."Id" = mo."ObserverId" - INNER JOIN "AspNetUsers" u ON u."Id" = o."ApplicationUserId" - WHERE mn."ElectionRoundId" = @electionRoundId - AND mn."NgoId" = @ngoId - AND (@monitoringObserverId IS NULL OR mo."Id" = @monitoringObserverId) - AND (@searchText IS NULL OR @searchText = '' - OR u."DisplayName" ILIKE @searchText - OR u."Email" ILIKE @searchText - OR u."PhoneNumber" ILIKE @searchText - OR mo."Id"::TEXT ILIKE @searchText) - AND (@formType IS NULL OR s."FormType" = @formType) + INNER JOIN "AvailableMonitoringObservers" mo ON mo."Id" = s."MonitoringObserverId" + WHERE + (@formType IS NULL OR s."FormType" = @formType) AND (@level1 IS NULL OR ps."Level1" = @level1) AND (@level2 IS NULL OR ps."Level2" = @level2) AND (@level3 IS NULL OR ps."Level3" = @level3) @@ -267,8 +364,8 @@ ORDER BY CASE WHEN @sortExpression = 'Level5 DESC' THEN ps."Level5" END DESC, CASE WHEN @sortExpression = 'Number ASC' THEN ps."Number" END ASC, CASE WHEN @sortExpression = 'Number DESC' THEN ps."Number" END DESC, - CASE WHEN @sortExpression = 'ObserverName ASC' THEN u."DisplayName" END ASC, - CASE WHEN @sortExpression = 'ObserverName DESC' THEN u."DisplayName" END DESC, + CASE WHEN @sortExpression = 'ObserverName ASC' THEN mo."DisplayName" END ASC, + CASE WHEN @sortExpression = 'ObserverName DESC' THEN mo."DisplayName" END DESC, CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers ASC' THEN s."NumberOfFlaggedAnswers" END ASC, CASE WHEN @sortExpression = 'NumberOfFlaggedAnswers DESC' THEN s."NumberOfFlaggedAnswers" END DESC, CASE WHEN @sortExpression = 'NumberOfQuestionsAnswered ASC' THEN s."NumberOfQuestionsAnswered" END ASC, @@ -308,7 +405,7 @@ OFFSET @offset ROWS questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, + dataSource = req.DataSource?.ToString(), sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting) }; diff --git a/api/src/Feature.Form.Submissions/ListEntries/Request.cs b/api/src/Feature.Form.Submissions/ListEntries/Request.cs index 32c5dec3b..6cfb84816 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Request.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Request.cs @@ -11,6 +11,8 @@ public class Request : BaseSortPaginatedRequest [FromClaim(ApplicationClaimTypes.NgoId)] public Guid NgoId { get; set; } + + [QueryParam] public DataSource DataSource { get; set; } [QueryParam] public string? SearchText { get; set; } @@ -43,6 +45,4 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Form.Submissions/ListEntries/Validator.cs b/api/src/Feature.Form.Submissions/ListEntries/Validator.cs index 0ad43acee..fbd95e824 100644 --- a/api/src/Feature.Form.Submissions/ListEntries/Validator.cs +++ b/api/src/Feature.Form.Submissions/ListEntries/Validator.cs @@ -6,5 +6,6 @@ public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.DataSource).NotNull(); } } diff --git a/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs b/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs index 4a6580ae8..537f87253 100644 --- a/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs +++ b/api/src/Feature.Form.Submissions/Requests/FormSubmissionsAggregateFilter.cs @@ -35,5 +35,4 @@ public class FormSubmissionsAggregateFilter [QueryParam] public bool? HasNotes { get; set; } [QueryParam] public bool? HasAttachments { get; set; } [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs index 184f910ad..36ed084d7 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Endpoint.cs @@ -100,8 +100,7 @@ OR U."PhoneNumber" ILIKE @searchText OR (@hasNotes = TRUE AND (SELECT COUNT(1) FROM "IncidentReportNotes" N WHERE N."IncidentReportId" = IR."Id") > 0 ) )) AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) - AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted); + AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp); WITH @@ -150,7 +149,6 @@ INCIDENT_REPORTS AS ( ) AND (@fromDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") >= @fromDate::timestamp) AND (@toDate is NULL OR COALESCE(IR."LastModifiedOn", IR."CreatedOn") <= @toDate::timestamp) - AND (@isCompleted is NULL OR IR."IsCompleted" = @isCompleted) ) SELECT IR."IncidentReportId", @@ -267,7 +265,6 @@ OFFSET @offset ROWS fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), sortExpression = GetSortExpression(req.SortColumnName, req.IsAscendingSorting), - iscompleted = req.IsCompletedFilter }; int totalRowCount; diff --git a/api/src/Feature.IncidentReports/ListEntries/Request.cs b/api/src/Feature.IncidentReports/ListEntries/Request.cs index acf4a9f85..03a622a43 100644 --- a/api/src/Feature.IncidentReports/ListEntries/Request.cs +++ b/api/src/Feature.IncidentReports/ListEntries/Request.cs @@ -41,5 +41,4 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs index 41bdb14ff..8a64462d6 100644 --- a/api/src/Feature.Notifications/ListRecipients/Endpoint.cs +++ b/api/src/Feature.Notifications/ListRecipients/Endpoint.cs @@ -147,7 +147,6 @@ OR u."PhoneNumber" ILIKE @searchText AND (@hasNotes IS NULL OR (OA."NotesCount" = 0 AND @hasNotes = FALSE) OR (OA."NotesCount" > 0 AND @hasNotes = TRUE)) AND (@fromDate IS NULL OR OA."LastModifiedOn" >= @fromDate::timestamp) AND (@toDate IS NULL OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL OR OA."IsCompleted" = @isCompleted) AND (@hasQuickReports IS NULL OR (@hasQuickReports = TRUE AND OA."QuickReportId" IS NOT NULL) OR (@hasQuickReports = FALSE AND OA."QuickReportId" IS NULL)) AND (@quickReportFollowUpStatus IS NULL OR OA."QuickReportFollowUpStatus" = @quickReportFollowUpStatus) AND (@quickReportIncidentCategory IS NULL OR OA."IncidentCategory" = @quickReportIncidentCategory); @@ -285,7 +284,6 @@ OR u."PhoneNumber" ILIKE @searchText AND (@hasNotes IS NULL OR (OA."NotesCount" = 0 AND @hasNotes = FALSE) OR (OA."NotesCount" > 0 AND @hasNotes = TRUE)) AND (@fromDate IS NULL OR OA."LastModifiedOn" >= @fromDate::timestamp) AND (@toDate IS NULL OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL OR OA."IsCompleted" = @isCompleted) AND (@hasQuickReports IS NULL OR (@hasQuickReports = TRUE AND OA."QuickReportId" IS NOT NULL) OR (@hasQuickReports = FALSE AND OA."QuickReportId" IS NULL)) AND (@quickReportFollowUpStatus IS NULL OR OA."QuickReportFollowUpStatus" = @quickReportFollowUpStatus) @@ -328,7 +326,6 @@ ORDER BY CASE questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, hasQuickReports = req.HasQuickReports, quickReportFollowUpStatus = req.QuickReportFollowUpStatus?.ToString(), diff --git a/api/src/Feature.Notifications/ListRecipients/Request.cs b/api/src/Feature.Notifications/ListRecipients/Request.cs index 795b258cb..563ba2e8d 100644 --- a/api/src/Feature.Notifications/ListRecipients/Request.cs +++ b/api/src/Feature.Notifications/ListRecipients/Request.cs @@ -43,9 +43,8 @@ public class Request : BaseSortPaginatedRequest [QueryParam] public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } [QueryParam] public DateTime? FromDateFilter { get; set; } [QueryParam] public DateTime? ToDateFilter { get; set; } - [QueryParam] public bool? IsCompletedFilter { get; set; } [QueryParam] public QuickReportFollowUpStatus? QuickReportFollowUpStatus { get; set; } [QueryParam] public IncidentCategory? QuickReportIncidentCategory { get; set; } [QueryParam] public bool? HasQuickReports { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Feature.Notifications/Send/Endpoint.cs b/api/src/Feature.Notifications/Send/Endpoint.cs index 88e734452..1d328c298 100644 --- a/api/src/Feature.Notifications/Send/Endpoint.cs +++ b/api/src/Feature.Notifications/Send/Endpoint.cs @@ -179,8 +179,6 @@ OR cardinality(@tagsFilter) = 0 OR OA."LastModifiedOn" >= @fromDate::timestamp) AND (@toDate IS NULL OR OA."LastModifiedOn" <= @toDate::timestamp) - AND (@isCompleted IS NULL - OR OA."IsCompleted" = @isCompleted) AND (@hasQuickReports IS NULL OR (@hasQuickReports = TRUE AND OA."QuickReportId" IS NOT NULL) @@ -214,7 +212,6 @@ OR cardinality(@tagsFilter) = 0 questionsAnswered = req.QuestionsAnswered?.ToString(), fromDate = req.FromDateFilter?.ToString("O"), toDate = req.ToDateFilter?.ToString("O"), - isCompleted = req.IsCompletedFilter, hasQuickReports = req.HasQuickReports, quickReportFollowUpStatus = req.QuickReportFollowUpStatus?.ToString(), diff --git a/api/src/Feature.Notifications/Send/Request.cs b/api/src/Feature.Notifications/Send/Request.cs index d7636af70..12064b2a9 100644 --- a/api/src/Feature.Notifications/Send/Request.cs +++ b/api/src/Feature.Notifications/Send/Request.cs @@ -49,8 +49,7 @@ public class Request public QuestionsAnsweredFilter? QuestionsAnswered { get; set; } public DateTime? FromDateFilter { get; set; } public DateTime? ToDateFilter { get; set; } - public bool? IsCompletedFilter { get; set; } public QuickReportFollowUpStatus? QuickReportFollowUpStatus { get; set; } public IncidentCategory? QuickReportIncidentCategory { get; set; } public bool? HasQuickReports { get; set; } -} \ No newline at end of file +} diff --git a/api/src/Vote.Monitor.Core/Models/DataSource.cs b/api/src/Vote.Monitor.Core/Models/DataSource.cs new file mode 100644 index 000000000..aca061971 --- /dev/null +++ b/api/src/Vote.Monitor.Core/Models/DataSource.cs @@ -0,0 +1,29 @@ +using Ardalis.SmartEnum; + +namespace Vote.Monitor.Core.Models; + +[JsonConverter(typeof(SmartEnumValueConverter))] +public sealed class DataSource : SmartEnum +{ + public static readonly DataSource Ngo = new(nameof(Ngo), nameof(Ngo)); + public static readonly DataSource Coalition = new(nameof(Coalition), nameof(Coalition)); + + /// Gets an item associated with the specified value. Parses SmartEnum when used as query params + /// this issue + /// The value of the item to get. + /// + /// When this method returns, contains the item associated with the specified value, if the value is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified name; otherwise, false. + /// + public static bool TryParse(string value, out DataSource result) + { + return TryFromValue(value, out result); + } + + [JsonConstructor] + private DataSource(string name, string value) : base(name, value) + { + } +} diff --git a/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs b/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs index e09ac1486..2cab56934 100644 --- a/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs +++ b/api/src/Vote.Monitor.Core/Models/TranslationStatus.cs @@ -26,4 +26,4 @@ public static bool TryParse(string value, out TranslationStatus result) private TranslationStatus(string name, string value) : base(name, value) { } -} \ No newline at end of file +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs similarity index 94% rename from api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs rename to api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs index 0f2a0d59f..278439f37 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/Ngos.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Consts/ScenarioNgos.cs @@ -7,7 +7,7 @@ public enum ScenarioNgo Delta } -public class Ngos +public class ScenarioNgos { public static AlfaDetails Alfa => new(); public static BetaDetails Beta => new BetaDetails(); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs index 899c8fd0d..67c471a93 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/CreateTests.cs @@ -17,8 +17,8 @@ public class CreateTests : BaseApiTestFixture public async Task PlatformAdmin_ShouldCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A) .Please(); @@ -30,28 +30,28 @@ public async Task PlatformAdmin_ShouldCreateCoalition() new { CoalitionName = coalitionName, - LeaderId = scenarioData.NgoIdByName(Ngos.Alfa), - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + LeaderId = scenarioData.NgoIdByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } }); coalition.Should().NotBeNull(); coalition.Name.Should().Be(coalitionName); coalition.Members.Should().HaveCount(2); - coalition.LeaderId.Should().Be(scenarioData.NgoByName(Ngos.Alfa).NgoId); + coalition.LeaderId.Should().Be(scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId); coalition.Members.Select(x => x.Id).Should() - .Contain([scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId]); + .Contain([scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId]); } [Test] public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithNgo(Ngos.Delta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) .WithElectionRound(ScenarioElectionRound.A, - electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa).WithMonitoringNgo(ScenarioNgos.Beta)) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -61,8 +61,8 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Delta).NgoId } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Delta).NgoId } }); var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( @@ -71,8 +71,8 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( monitoringNgos.MonitoringNgos.Should().HaveCount(3); monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() .Contain([ - scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId, - scenarioData.NgoByName(Ngos.Delta).NgoId + scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, + scenarioData.NgoByName(ScenarioNgos.Delta).NgoId ]); } @@ -80,11 +80,11 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( public async Task ShouldAddLeaderAsMonitoringNgos_WhenTheyAreNotMonitoring() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithNgo(Ngos.Delta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) .WithElectionRound(ScenarioElectionRound.A, - electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa).WithMonitoringNgo(Ngos.Beta)) + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa).WithMonitoringNgo(ScenarioNgos.Beta)) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -94,7 +94,7 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Delta).NgoId, + LeaderId = scenarioData.NgoByName(ScenarioNgos.Delta).NgoId, NgoMembersIds = Array.Empty() }); @@ -104,8 +104,8 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( monitoringNgos.MonitoringNgos.Should().HaveCount(3); monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() .Contain([ - scenarioData.NgoByName(Ngos.Alfa).NgoId, scenarioData.NgoByName(Ngos.Beta).NgoId, - scenarioData.NgoByName(Ngos.Delta).NgoId + scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, + scenarioData.NgoByName(ScenarioNgos.Delta).NgoId ]); } @@ -113,10 +113,10 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( public async Task ShouldNotGiveAccessToNgoFormsForCoalitionMembers() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A, - electionRound => electionRound.WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A"))) + electionRound => electionRound.WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A"))) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -126,12 +126,12 @@ await scenarioData.PlatformAdmin.PostWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Beta).NgoId, + LeaderId = scenarioData.NgoByName(ScenarioNgos.Beta).NgoId, NgoMembersIds = Array.Empty() }); var formResult = await scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/forms"); @@ -143,19 +143,19 @@ public async Task NgoAdmin_CannotCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) .WithElectionRound(ScenarioElectionRound.A) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .Please(); var electionRoundId = scenarioData.ElectionRoundId; - var coalitionResponseMessage = await scenarioData.NgoByName(Ngos.Alfa).Admin.PostAsJsonAsync( + var coalitionResponseMessage = await scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostAsJsonAsync( $"/api/election-rounds/{electionRoundId}/coalitions", new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } }); coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); @@ -166,8 +166,8 @@ public async Task Observer_CannotCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A) .Please(); @@ -178,8 +178,8 @@ public async Task Observer_CannotCreateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } }); coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Forbidden); @@ -189,8 +189,8 @@ public async Task Observer_CannotCreateCoalition() public async Task UnauthorizedClients_CannotCreateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A) .Please(); @@ -201,8 +201,8 @@ public async Task UnauthorizedClients_CannotCreateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta).NgoId } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta).NgoId } }); coalitionResponseMessage.StatusCode.Should().Be(HttpStatusCode.Unauthorized); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs index b62035557..10801c0a5 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/DeleteTests.cs @@ -16,8 +16,8 @@ public class DeleteTests : BaseApiTestFixture public async Task PlatformAdmin_ShouldDeleteCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -40,17 +40,17 @@ public async Task ShouldRemoveDataForMonitoringNgos() var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) - .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg - .WithForm("Common", [Ngos.Beta], + .WithForm("Common", [ScenarioNgos.Beta], form => form .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) @@ -68,12 +68,12 @@ public async Task ShouldRemoveDataForMonitoringNgos() deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); var alfaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Alfa).Admin + .NgoByName(ScenarioNgos.Alfa).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); var betaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); @@ -97,16 +97,16 @@ public async Task ShouldKeepFormForLeader() var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) - .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], - cfg => cfg.WithForm("Common", [Ngos.Beta]) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], + cfg => cfg.WithForm("Common", [ScenarioNgos.Beta]) ) ) .Please(); @@ -119,7 +119,7 @@ public async Task ShouldKeepFormForLeader() deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); var formResult = await scenarioData - .NgoByName(Ngos.Alfa).Admin + .NgoByName(ScenarioNgos.Alfa).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/forms"); @@ -133,17 +133,17 @@ public async Task ShouldRemoveFormAccessFromExCoalitionMembers() var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) - .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg - .WithForm("Common", [Ngos.Beta]) + .WithForm("Common", [ScenarioNgos.Beta]) ) ) .Please(); @@ -156,7 +156,7 @@ public async Task ShouldRemoveFormAccessFromExCoalitionMembers() deleteResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); var formResult = await scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/forms"); @@ -167,21 +167,21 @@ public async Task ShouldRemoveFormAccessFromExCoalitionMembers() public async Task NgoAdmin_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin()) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin()) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; var coalitionId = scenarioData.ElectionRound.CoalitionId; - var coalitionResponseMessage = await scenarioData.NgoByName(Ngos.Alfa).Admin.PutAsJsonAsync( + var coalitionResponseMessage = await scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PutAsJsonAsync( $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, - NgoMembersIds = new[] { scenarioData.NgoByName(Ngos.Beta) } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, + NgoMembersIds = new[] { scenarioData.NgoByName(ScenarioNgos.Beta) } }); coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); @@ -192,9 +192,9 @@ public async Task Observer_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -205,7 +205,7 @@ public async Task Observer_CannotUpdateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, NgoMembersIds = Array.Empty() }); @@ -216,9 +216,9 @@ public async Task Observer_CannotUpdateCoalition() public async Task UnauthorizedClients_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -229,7 +229,7 @@ public async Task UnauthorizedClients_CannotUpdateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa).NgoId, + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa).NgoId, NgoMembersIds = Array.Empty() }); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs index 9b8a38e57..bddd64a01 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/FormAccessTests.cs @@ -18,26 +18,26 @@ public void ShouldNotGrantFormAccessForMonitoringObservers_WhenCreatingNewForm() { // Arrange var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); // Act var formRequest = Dummy.Form(); var electionRoundId = scenarioData.ElectionRoundId; var ngoForm = - scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithResponse( + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostWithResponse( $"/api/election-rounds/{electionRoundId}/forms", formRequest); - scenarioData.NgoByName(Ngos.Alfa).Admin + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", null) .GetAwaiter().GetResult() @@ -60,26 +60,26 @@ public void ShouldNotGrantFormAccessForMonitoringNgos_WhenCreatingNewForm() { // Arrange var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); // Act var formRequest = Dummy.Form(); var electionRoundId = scenarioData.ElectionRoundId; var ngoForm = - scenarioData.NgoByName(Ngos.Alfa).Admin.PostWithResponse( + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin.PostWithResponse( $"/api/election-rounds/{electionRoundId}/forms", formRequest); - scenarioData.NgoByName(Ngos.Alfa).Admin + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin .PostAsync($"/api/election-rounds/{electionRoundId}/forms/{ngoForm.Id}:publish", null) .GetAwaiter().GetResult() @@ -87,7 +87,7 @@ public void ShouldNotGrantFormAccessForMonitoringNgos_WhenCreatingNewForm() // Assert var betaForms = scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); betaForms.Items.Should().BeEmpty(); @@ -98,25 +98,25 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() { // Arrange var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); // Act var electionRoundId = scenarioData.ElectionRoundId; var coalitionId = scenarioData.ElectionRound.CoalitionId; - var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; - scenarioData.NgoByName(Ngos.Alfa).Admin + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); // Assert var aliceForms = scenarioData @@ -128,11 +128,11 @@ public void ShouldGrantFormAccessForCoalitionMembersAndTheirObservers() .GetResponse($"/api/election-rounds/{electionRoundId}/forms:fetchAll"); var betaForms = scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); var form = scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponse($"/api/election-rounds/{electionRoundId}/forms/{formId}"); aliceForms.Forms.Should().BeEmpty(); @@ -146,30 +146,30 @@ public async Task ShouldAllowMonitoringObserversToAddSubmissionsToCoalitionForms { // Arrange var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) - .WithMonitoringNgo(Ngos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) - .WithMonitoringNgo(Ngos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta])) + .WithMonitoringNgo(ScenarioNgos.Alfa, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Alice).WithForm()) + .WithMonitoringNgo(ScenarioNgos.Beta, alfa => alfa.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta])) .Please(); // Act var electionRoundId = scenarioData.ElectionRoundId; var coalitionId = scenarioData.ElectionRound.CoalitionId; - var formId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; + var formId = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; - scenarioData.NgoByName(Ngos.Alfa).Admin + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); var pollingStationId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); - var questions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; + var questions = scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; var submission = new FormSubmissionRequestFaker(formId, pollingStationId, questions).Generate(); var observer = scenarioData.ObserverByName(ScenarioObserver.Bob); @@ -187,12 +187,12 @@ public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() { // Arrange var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithNgo(Ngos.Delta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta, Ngos.Delta], c=>c.WithForm(sharedWithMembers:[Ngos.Beta]))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta, ScenarioNgos.Delta], c=>c.WithForm(sharedWithMembers:[ScenarioNgos.Beta]))) .Please(); // Act @@ -200,13 +200,13 @@ public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - scenarioData.NgoByName(Ngos.Alfa).Admin + scenarioData.NgoByName(ScenarioNgos.Alfa).Admin .PutWithoutResponse($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Delta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Delta) } }); // Assert var deltaForms = scenarioData - .NgoByName(Ngos.Delta).Admin + .NgoByName(ScenarioNgos.Delta).Admin .GetResponse>($"/api/election-rounds/{electionRoundId}/forms"); deltaForms.Items.Should().HaveCount(1); @@ -216,18 +216,18 @@ public void ShouldGrantFormAccess_WhenFormIsSharedWithOtherCoalitionMembers() public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg.WithForm("A", []))) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg.WithForm("A", []))) .Please(); var electionRoundId = scenarioData.ElectionRoundId; var coalitionId = scenarioData.ElectionRound.CoalitionId; var formId = scenarioData.ElectionRound.Coalition.FormId; - var response = await scenarioData.NgoByName(Ngos.Beta).Admin + var response = await scenarioData.NgoByName(ScenarioNgos.Beta).Admin .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.NotFound); } @@ -236,14 +236,14 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionMember() public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) .WithElectionRound(ScenarioElectionRound.A, er => er - .WithMonitoringNgo(Ngos.Alfa) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg + .WithMonitoringNgo(ScenarioNgos.Alfa) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg .WithForm("A", []) - .WithMonitoringObserver(Ngos.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgos.Alfa, ScenarioObserver.Alice) ) ) .Please(); @@ -254,7 +254,7 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() var response = await scenarioData.ObserverByName(ScenarioObserver.Alice) .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.Forbidden); } @@ -263,10 +263,10 @@ public async Task ShouldNotUpdateFormAccess_WhenCoalitionObserver() public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A, - er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], c => c.WithForm())) + er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], c => c.WithForm())) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -275,7 +275,7 @@ public async Task ShouldNotUpdateFormAccess_WhenUnauthorizedClients() var response = await CreateClient() .PutAsJsonAsync($"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}/forms/{formId}:access", - new { NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); response.Should().HaveStatusCode(HttpStatusCode.Unauthorized); } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs index b235a7628..88cb0646c 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/Coalition/UpdateTests.cs @@ -17,9 +17,9 @@ public class UpdateTests : BaseApiTestFixture public async Task PlatformAdmin_ShouldUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -28,26 +28,26 @@ public async Task PlatformAdmin_ShouldUpdateCoalition() var newCoalitionName = Guid.NewGuid().ToString(); var updatedCoalition = await scenarioData.PlatformAdmin.PutWithResponseAsync( $"/api/election-rounds/{electionRoundId}/coalitions/{coalitionId}", - new { CoalitionName = newCoalitionName, NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } }); + new { CoalitionName = newCoalitionName, NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); updatedCoalition.Should().NotBeNull(); updatedCoalition.Name.Should().Be(newCoalitionName); updatedCoalition.Members.Should().HaveCount(2); - updatedCoalition.LeaderId.Should().Be(scenarioData.NgoIdByName(Ngos.Alfa)); + updatedCoalition.LeaderId.Should().Be(scenarioData.NgoIdByName(ScenarioNgos.Alfa)); updatedCoalition.Members.Select(x => x.Id).Should() - .Contain([scenarioData.NgoIdByName(Ngos.Alfa), scenarioData.NgoIdByName(Ngos.Beta)]); + .Contain([scenarioData.NgoIdByName(ScenarioNgos.Alfa), scenarioData.NgoIdByName(ScenarioNgos.Beta)]); } [Test] public async Task ShouldAddMembersAsMonitoringNgos_WhenTheyAreNotMonitoring() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithNgo(Ngos.Delta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -58,7 +58,7 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta), scenarioData.NgoIdByName(Ngos.Delta) } + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta), scenarioData.NgoIdByName(ScenarioNgos.Delta) } }); var monitoringNgos = await scenarioData.PlatformAdmin.GetResponseAsync( @@ -67,9 +67,9 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( monitoringNgos.MonitoringNgos.Should().HaveCount(3); monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() .Contain([ - scenarioData.NgoIdByName(Ngos.Alfa), - scenarioData.NgoIdByName(Ngos.Beta), - scenarioData.NgoIdByName(Ngos.Delta) + scenarioData.NgoIdByName(ScenarioNgos.Alfa), + scenarioData.NgoIdByName(ScenarioNgos.Beta), + scenarioData.NgoIdByName(ScenarioNgos.Delta) ]); } @@ -77,10 +77,10 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( public async Task NgosStayAsMonitoringNgos_WhenTheyAreKicked() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithNgo(Ngos.Delta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithNgo(ScenarioNgos.Delta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -91,7 +91,7 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta), scenarioData.NgoIdByName(Ngos.Delta) } + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta), scenarioData.NgoIdByName(ScenarioNgos.Delta) } }); @@ -100,9 +100,9 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( monitoringNgos.MonitoringNgos.Should().HaveCount(3); monitoringNgos.MonitoringNgos.Select(x => x.NgoId).Should() .Contain([ - scenarioData.NgoIdByName(Ngos.Alfa), - scenarioData.NgoIdByName(Ngos.Beta), - scenarioData.NgoIdByName(Ngos.Delta) + scenarioData.NgoIdByName(ScenarioNgos.Alfa), + scenarioData.NgoIdByName(ScenarioNgos.Beta), + scenarioData.NgoIdByName(ScenarioNgos.Delta) ]); } @@ -115,13 +115,13 @@ public async Task ShouldKeepObserverDatatForNgoForms_WhenNgoIsKicked() .WithObserver(ScenarioObserver.Bob) .WithObserver(ScenarioObserver.Charlie) .WithObserver(ScenarioObserver.Dave) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo .WithMonitoringObserver(ScenarioObserver.Alice) .WithMonitoringObserver(ScenarioObserver.Bob) .WithForm("A", form => form @@ -131,7 +131,7 @@ public async Task ShouldKeepObserverDatatForNgoForms_WhenNgoIsKicked() .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Bacau) .WithSubmission(ScenarioObserver.Bob, ScenarioPollingStation.Cluj)) ) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo .WithMonitoringObserver(ScenarioObserver.Charlie) .WithMonitoringObserver(ScenarioObserver.Dave) .WithForm("A", form => form @@ -141,7 +141,7 @@ public async Task ShouldKeepObserverDatatForNgoForms_WhenNgoIsKicked() .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Bacau) .WithSubmission(ScenarioObserver.Dave, ScenarioPollingStation.Cluj)) ) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -152,16 +152,16 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); var alfaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Alfa).Admin + .NgoByName(ScenarioNgos.Alfa).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); var betaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); @@ -175,17 +175,17 @@ public async Task ShouldRemoveDataForSharedFormsOfKickedNgos() var scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa, ngo => ngo.WithAdmin(Ngos.Alfa.Anya)) - .WithNgo(Ngos.Beta, ngo => ngo.WithAdmin(Ngos.Beta.Dana)) + .WithNgo(ScenarioNgos.Alfa, ngo => ngo.WithAdmin(ScenarioNgos.Alfa.Anya)) + .WithNgo(ScenarioNgos.Beta, ngo => ngo.WithAdmin(ScenarioNgos.Beta.Dana)) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) - .WithMonitoringNgo(Ngos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Alice)) + .WithMonitoringNgo(ScenarioNgos.Beta, ngo => ngo.WithMonitoringObserver(ScenarioObserver.Bob)) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg - .WithForm("Common", [Ngos.Beta], + .WithForm("Common", [ScenarioNgos.Beta], form => form .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Iasi) .WithSubmission(ScenarioObserver.Alice, ScenarioPollingStation.Bacau) @@ -204,16 +204,16 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( new { CoalitionName = Guid.NewGuid().ToString(), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Alfa) } + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Alfa) } }); var alfaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Alfa).Admin + .NgoByName(ScenarioNgos.Alfa).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); var betaNgoSubmissions = await scenarioData - .NgoByName(Ngos.Beta).Admin + .NgoByName(ScenarioNgos.Beta).Admin .GetResponseAsync>( $"/api/election-rounds/{electionRoundId}/form-submissions:byEntry"); @@ -240,9 +240,9 @@ await scenarioData.PlatformAdmin.PutWithResponseAsync( public async Task NgoAdmin_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -253,8 +253,8 @@ public async Task NgoAdmin_CannotUpdateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); @@ -264,10 +264,10 @@ public async Task NgoAdmin_CannotUpdateCoalition() public async Task Observer_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithObserver(ScenarioObserver.Alice) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -278,8 +278,8 @@ public async Task Observer_CannotUpdateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Forbidden); @@ -289,9 +289,9 @@ public async Task Observer_CannotUpdateCoalition() public async Task UnauthorizedClients_CannotUpdateCoalition() { var scenarioData = ScenarioBuilder.New(CreateClient) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [])) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er.WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [])) .Please(); var electionRoundId = scenarioData.ElectionRoundId; @@ -302,8 +302,8 @@ public async Task UnauthorizedClients_CannotUpdateCoalition() new { CoalitionName = Guid.NewGuid().ToString(), - LeaderId = scenarioData.NgoByName(Ngos.Alfa), - NgoMembersIds = new[] { scenarioData.NgoIdByName(Ngos.Beta) } + LeaderId = scenarioData.NgoByName(ScenarioNgos.Alfa), + NgoMembersIds = new[] { scenarioData.NgoIdByName(ScenarioNgos.Beta) } }); coalitionResponseMessage.Should().HaveStatusCode(HttpStatusCode.Unauthorized); diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs index 4d4ec4a1f..04355fb43 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/GetFiltersTests.cs @@ -2,6 +2,9 @@ using Vote.Monitor.Api.IntegrationTests.Consts; using Vote.Monitor.Api.IntegrationTests.Fakers; using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Form.Module.Requests; using GetFiltersResponse = Feature.Form.Submissions.GetFilters.Response; namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; @@ -12,155 +15,188 @@ public class GetFiltersTests : BaseApiTestFixture { private readonly DateTime _now = DateTime.UtcNow.AddDays(1000); - [Test] - public void ShouldIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader() + private ScenarioData _scenarioData; + private Guid _electionRoundId; + private Guid _alfaFormId; + private Guid _coalitionFormId; + + private Guid _psIasiId; + private Guid _psBacauId; + private Guid _psClujId; + + private List _alfaFormQuestions; + private List _coalitionFormQuestions; + + private DateTime _firstSubmissionAt; + private DateTime _secondSubmissionAt; + private DateTime _thirdSubmissionAt; + + [SetUp] + public void Setup() { - // Arrange - var scenarioData = ScenarioBuilder.New(CreateClient) + _scenarioData = ScenarioBuilder.New(CreateClient) .WithObserver(ScenarioObserver.Alice) .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) .WithElectionRound(ScenarioElectionRound.A, er => er .WithPollingStation(ScenarioPollingStation.Iasi) .WithPollingStation(ScenarioPollingStation.Bacau) .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg - .WithForm("Shared", [Ngos.Alfa, Ngos.Beta]) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) )) .Please(); - - var firstSubmissionAt = _now.AddDays(-5); - var secondSubmissionAt = _now.AddDays(-3); - var thirdSubmissionAt = _now.AddDays(-1); - + + _firstSubmissionAt = _now.AddDays(-5); + _secondSubmissionAt = _now.AddDays(-3); + _thirdSubmissionAt = _now.AddDays(-1); + ApiTimeProvider.UtcNow - .Returns(firstSubmissionAt, secondSubmissionAt, thirdSubmissionAt); + .Returns(_firstSubmissionAt, _secondSubmissionAt, _thirdSubmissionAt); - var electionRoundId = scenarioData.ElectionRoundId; - var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; - var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + _electionRoundId = _scenarioData.ElectionRoundId; + _alfaFormId = _scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + _coalitionFormId = _scenarioData.ElectionRound.Coalition.FormId; - var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); - var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); - var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + _psIasiId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + _psBacauId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + _psClujId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); - var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; - var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + _alfaFormQuestions = _scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + _coalitionFormQuestions = _scenarioData.ElectionRound.Coalition.Form.Questions; + } + [Test] + public void ShouldIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceCoalition() + { + // Arrange var iasiSubmission = - new FormSubmissionRequestFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); - var clujSubmission = new FormSubmissionRequestFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + new FormSubmissionRequestFaker(_coalitionFormId, _psIasiId, _coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); var bacauSubmission = - new FormSubmissionRequestFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = _scenarioData.ObserverByName(ScenarioObserver.Bob); - var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); - var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); - alice.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", clujSubmission); alice.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", iasiSubmission); - + bob.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", bacauSubmission); // Act - var filters = scenarioData.NgoByName(Ngos.Alfa).Admin - .GetResponse($"/api/election-rounds/{electionRoundId}/form-submissions:filters"); + var alfaNgoFilters = _scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse($"/api/election-rounds/{_electionRoundId}/form-submissions:filters?dataSource=Coalition"); // Assert - filters.FormFilterOptions + alfaNgoFilters.FormFilterOptions .Select(x => x.FormId) .Should() .HaveCount(2) .And - .BeEquivalentTo([alfaFormId, coalitionFormId]); + .BeEquivalentTo([_alfaFormId, _coalitionFormId]); - filters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should().BeCloseTo(firstSubmissionAt, TimeSpan.FromMicroseconds(100)); - filters.TimestampsFilterOptions.LastSubmissionTimestamp.Should().BeCloseTo(thirdSubmissionAt,TimeSpan.FromMicroseconds(100)); + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); } + [Test] - public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsCoalitionMember() + public void ShouldNotIncludeCoalitionMembersResponses_WhenGettingFiltersAsCoalitionLeader_And_DataSourceNgo() { // Arrange - var scenarioData = ScenarioBuilder.New(CreateClient) - .WithObserver(ScenarioObserver.Alice) - .WithObserver(ScenarioObserver.Bob) - .WithNgo(Ngos.Alfa) - .WithNgo(Ngos.Beta) - .WithElectionRound(ScenarioElectionRound.A, er => er - .WithPollingStation(ScenarioPollingStation.Iasi) - .WithPollingStation(ScenarioPollingStation.Bacau) - .WithPollingStation(ScenarioPollingStation.Cluj) - .WithMonitoringNgo(Ngos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) - .WithCoalition(ScenarioCoalition.Youth, Ngos.Alfa, [Ngos.Beta], cfg => cfg - .WithForm("Shared", [Ngos.Alfa, Ngos.Beta]) - .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) - .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) - )) - .Please(); + var iasiSubmission = + new FormSubmissionRequestFaker(_alfaFormId, _psIasiId, _alfaFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = _scenarioData.ObserverByName(ScenarioObserver.Bob); + + alice.PostWithoutResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + iasiSubmission); - var firstSubmissionAt = _now.AddDays(-5); - var secondSubmissionAt = _now.AddDays(-3); - var thirdSubmissionAt = _now.AddDays(-1); + alice.PostWithoutResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + clujSubmission); - ApiTimeProvider.UtcNow - .Returns(firstSubmissionAt, secondSubmissionAt, thirdSubmissionAt); + bob.PostWithoutResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + bacauSubmission); - var electionRoundId = scenarioData.ElectionRoundId; - var alfaFormId = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).FormId; - var coalitionFormId = scenarioData.ElectionRound.Coalition.FormId; + // Act + var alfaNgoFilters = _scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse($"/api/election-rounds/{_electionRoundId}/form-submissions:filters?dataSource=Ngo"); - var psIasiId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); - var psBacauId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); - var psClujId = scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + // Assert + alfaNgoFilters.FormFilterOptions + .Select(x => x.FormId) + .Should() + .HaveCount(1) + .And + .BeEquivalentTo([_alfaFormId]); - var alfaFormQuestions = scenarioData.ElectionRound.MonitoringNgoByName(Ngos.Alfa).Form.Questions; - var coalitionFormQuestions = scenarioData.ElectionRound.Coalition.Form.Questions; + alfaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_firstSubmissionAt, TimeSpan.FromMicroseconds(100)); + alfaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + } + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldIncludeOnlyNgoResponses_WhenGettingFiltersAsCoalitionMember(DataSource dataSource) + { + // Arrange var iasiSubmission = - new FormSubmissionRequestFaker(coalitionFormId, psIasiId, coalitionFormQuestions).Generate(); - var clujSubmission = new FormSubmissionRequestFaker(alfaFormId, psClujId, alfaFormQuestions).Generate(); + new FormSubmissionRequestFaker(_coalitionFormId, _psIasiId, _coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); var bacauSubmission = - new FormSubmissionRequestFaker(coalitionFormId, psBacauId, coalitionFormQuestions).Generate(); + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = _scenarioData.ObserverByName(ScenarioObserver.Bob); - var alice = scenarioData.ObserverByName(ScenarioObserver.Alice); - var bob = scenarioData.ObserverByName(ScenarioObserver.Bob); - alice.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", clujSubmission); bob.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", iasiSubmission); - + bob.PostWithoutResponse( - $"/api/election-rounds/{electionRoundId}/form-submissions", + $"/api/election-rounds/{_electionRoundId}/form-submissions", bacauSubmission); // Act - var filters = scenarioData.NgoByName(Ngos.Beta).Admin - .GetResponse($"/api/election-rounds/{electionRoundId}/form-submissions:filters"); + var betaNgoFilters = _scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse($"/api/election-rounds/{_electionRoundId}/form-submissions:filters?dataSource={dataSource}"); // Assert - filters.FormFilterOptions + betaNgoFilters.FormFilterOptions .Select(x => x.FormId) .Should() .HaveCount(1) .And - .BeEquivalentTo([coalitionFormId]); + .BeEquivalentTo([_coalitionFormId]); - filters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should().BeCloseTo(secondSubmissionAt, TimeSpan.FromMicroseconds(100)); - filters.TimestampsFilterOptions.LastSubmissionTimestamp.Should().BeCloseTo(thirdSubmissionAt,TimeSpan.FromMicroseconds(100)); + betaNgoFilters.TimestampsFilterOptions.FirstSubmissionTimestamp.Should() + .BeCloseTo(_secondSubmissionAt, TimeSpan.FromMicroseconds(100)); + betaNgoFilters.TimestampsFilterOptions.LastSubmissionTimestamp.Should() + .BeCloseTo(_thirdSubmissionAt, TimeSpan.FromMicroseconds(100)); } } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs new file mode 100644 index 000000000..c70f130f9 --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Features/FormSubmissions/ListEntriesTests.cs @@ -0,0 +1,197 @@ +using Feature.Form.Submissions.ListEntries; +using NSubstitute; +using Vote.Monitor.Api.IntegrationTests.Consts; +using Vote.Monitor.Api.IntegrationTests.Fakers; +using Vote.Monitor.Api.IntegrationTests.Models; +using Vote.Monitor.Api.IntegrationTests.Scenarios; +using Vote.Monitor.Api.IntegrationTests.TestCases; +using Vote.Monitor.Core.Models; +using Vote.Monitor.Form.Module.Requests; + +namespace Vote.Monitor.Api.IntegrationTests.Features.FormSubmissions; + +using static ApiTesting; + +public class ListEntriesTests : BaseApiTestFixture +{ + private readonly DateTime _now = DateTime.UtcNow.AddDays(1000); + private ScenarioData _scenarioData; + private Guid _electionRoundId; + private Guid _alfaFormId; + private Guid _coalitionFormId; + private Guid _psIasiId; + private Guid _psBacauId; + private Guid _psClujId; + private List _alfaFormQuestions; + private List _coalitionFormQuestions; + + [SetUp] + public void Setup() + { + _scenarioData = ScenarioBuilder.New(CreateClient) + .WithObserver(ScenarioObserver.Alice) + .WithObserver(ScenarioObserver.Bob) + .WithNgo(ScenarioNgos.Alfa) + .WithNgo(ScenarioNgos.Beta) + .WithElectionRound(ScenarioElectionRound.A, er => er + .WithPollingStation(ScenarioPollingStation.Iasi) + .WithPollingStation(ScenarioPollingStation.Bacau) + .WithPollingStation(ScenarioPollingStation.Cluj) + .WithMonitoringNgo(ScenarioNgos.Alfa, ngo => ngo.WithForm("A", form => form.Publish())) + .WithCoalition(ScenarioCoalition.Youth, ScenarioNgos.Alfa, [ScenarioNgos.Beta], cfg => cfg + .WithForm("Shared", [ScenarioNgos.Alfa, ScenarioNgos.Beta]) + .WithMonitoringObserver(ScenarioNgo.Alfa, ScenarioObserver.Alice) + .WithMonitoringObserver(ScenarioNgo.Beta, ScenarioObserver.Bob) + )) + .Please(); + + var firstSubmissionAt = _now.AddDays(-5); + var secondSubmissionAt = _now.AddDays(-3); + var thirdSubmissionAt = _now.AddDays(-1); + + ApiTimeProvider.UtcNow + .Returns(firstSubmissionAt, secondSubmissionAt, thirdSubmissionAt); + + _electionRoundId = _scenarioData.ElectionRoundId; + _alfaFormId = _scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).FormId; + _coalitionFormId = _scenarioData.ElectionRound.Coalition.FormId; + + _psIasiId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Iasi); + _psBacauId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Bacau); + _psClujId = _scenarioData.ElectionRound.PollingStationByName(ScenarioPollingStation.Cluj); + + _alfaFormQuestions = _scenarioData.ElectionRound.MonitoringNgoByName(ScenarioNgos.Alfa).Form.Questions; + _coalitionFormQuestions = _scenarioData.ElectionRound.Coalition.Form.Questions; + } + + [Test] + public void ShouldNotIncludeCoalitionMembersResponses_WhenGettingSubmissionsAsCoalitionLeader_And_DataSourceNgo() + { + // Arrange + var iasiSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psIasiId, _coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = _scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFormSubmissions = _scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{_electionRoundId}/form-submissions:byEntry?dataSource=Ngo"); + + // Assert + alfaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And.BeEquivalentTo([firstSubmission.Id, secondSubmission.Id]); + } + + [Test] + public void ShouldIncludeAnonymizedCoalitionMembersResponses_WhenGettingSubmissionsAsCoalitionLeader_And_DataSourceCoalition() + { + // Arrange + var iasiSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psIasiId, _coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Alfa) + .ObserverByName(ScenarioObserver.Alice); + + var bob = _scenarioData.ElectionRound + .MonitoringNgoByName(ScenarioNgos.Beta) + .ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = alice.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.Client.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var alfaNgoFormSubmissions = _scenarioData.NgoByName(ScenarioNgos.Alfa).Admin + .GetResponse>( + $"/api/election-rounds/{_electionRoundId}/form-submissions:byEntry?dataSource=Coalition"); + + // Assert + alfaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(3) + .And.BeEquivalentTo([firstSubmission.Id, secondSubmission.Id, thirdSubmission.Id]); + + alfaNgoFormSubmissions.Items.Select(x => x.ObserverName).Should() + .BeEquivalentTo(alice.FullName, alice.FullName, bob.MonitoringObserverId.ToString()); + alfaNgoFormSubmissions.Items.Select(x => x.Email).Should() + .BeEquivalentTo(alice.Email, alice.Email, bob.MonitoringObserverId.ToString()); + alfaNgoFormSubmissions.Items.Select(x => x.PhoneNumber).Should() + .BeEquivalentTo(alice.PhoneNumber, alice.PhoneNumber, bob.MonitoringObserverId.ToString()); + } + + [TestCaseSource(typeof(DataSourcesTestCases))] + public void ShouldAlwaysGetOnlyNgoResponses_WhenGettingSubmissionsAsCoalitionMember(DataSource dataSource) + { + // Arrange + var iasiSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psIasiId, _coalitionFormQuestions).Generate(); + var clujSubmission = new FormSubmissionRequestFaker(_alfaFormId, _psClujId, _alfaFormQuestions).Generate(); + var bacauSubmission = + new FormSubmissionRequestFaker(_coalitionFormId, _psBacauId, _coalitionFormQuestions).Generate(); + + var alice = _scenarioData.ObserverByName(ScenarioObserver.Alice); + var bob = _scenarioData.ObserverByName(ScenarioObserver.Bob); + + var firstSubmission = alice.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + clujSubmission); + + var secondSubmission = bob.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + iasiSubmission); + + var thirdSubmission = bob.PostWithResponse( + $"/api/election-rounds/{_electionRoundId}/form-submissions", + bacauSubmission); + + // Act + var betaNgoFormSubmissions = _scenarioData.NgoByName(ScenarioNgos.Beta).Admin + .GetResponse>( + $"/api/election-rounds/{_electionRoundId}/form-submissions:byEntry?dataSource={dataSource}"); + + // Assert + betaNgoFormSubmissions.Items + .Select(x => x.SubmissionId) + .Should() + .HaveCount(2) + .And.BeEquivalentTo([secondSubmission.Id, thirdSubmission.Id]); + } +} diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs index 65cb1fef4..e1e2a4968 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionFormScenarioBuilder.cs @@ -24,7 +24,7 @@ public CoalitionFormScenarioBuilder WithSubmission(ScenarioObserver observer, Sc var pollingStationId = _parentBuilder.ParentBuilder.PollingStationByName(pollingStation); var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); - var observerClient = _parentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observer); + var observerClient = _parentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); var submissionId = observerClient.PostWithResponse( $"/api/election-rounds/{_parentBuilder.ParentBuilder.ElectionRoundId}/form-submissions", diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs index 71057894c..cc4a80e0d 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/CoalitionScenarioBuilder.cs @@ -24,12 +24,13 @@ public CoalitionScenarioBuilder(HttpClient platformAdmin, HttpClient coalitionLe public Guid CoalitionId => _coalition.Id; - public CoalitionScenarioBuilder WithForm(string? formCode = null, ScenarioNgo[]? sharedWithMembers = null, + public CoalitionScenarioBuilder WithForm(string? formCode = null, + ScenarioNgo[]? sharedWithMembers = null, Action? cfg = null) { sharedWithMembers ??= Array.Empty(); formCode ??= Guid.NewGuid().ToString(); - + var formRequest = Dummy.Form(); var ngoForm = _coalitionLeaderAdminAdmin.PostWithResponse( @@ -47,10 +48,7 @@ public CoalitionScenarioBuilder WithForm(string? formCode = null, ScenarioNgo[]? .ToList(); _coalitionLeaderAdminAdmin.PutWithoutResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/coalitions/{CoalitionId}/forms/{ngoForm.Id}:access", - new - { - NgoMembersIds = members - }); + new { NgoMembersIds = members }); var coalitionFormScenarioBuilder = new CoalitionFormScenarioBuilder(this, ngoForm); cfg?.Invoke(coalitionFormScenarioBuilder); @@ -62,12 +60,7 @@ public CoalitionScenarioBuilder WithForm(string? formCode = null, ScenarioNgo[]? public CoalitionScenarioBuilder WithMonitoringObserver(ScenarioNgo ngo, ScenarioObserver observer) { - var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observer); - - _platformAdmin - .PostWithResponse( - $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{ParentBuilder.MonitoringNgoIdByName(ngo)}/monitoring-observers", - new { observerId = observerId }); + ParentBuilder.ParentBuilder.ElectionRound.MonitoringNgoByName(ngo).WithMonitoringObserver(observer); return this; } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs index b5ec8ce44..2c80f3218 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoFormScenarioBuilder.cs @@ -50,7 +50,7 @@ public MonitoringNgoFormScenarioBuilder WithSubmission(ScenarioObserver observer var pollingStationId = ParentBuilder.ParentBuilder.PollingStationByName(pollingStation); var submission = new FormSubmissionRequestFaker(_form.Id, pollingStationId, _form.Questions).Generate(); - var observerClient = ParentBuilder.ParentBuilder.ParentBuilder.ObserverByName(observer); + var observerClient = ParentBuilder.ParentBuilder.ParentBuilder.ClientFor(observer); observerClient.PostWithResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/form-submissions", diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs index 4707c1bad..58d4b1397 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/MonitoringNgoScenarioBuilder.cs @@ -7,7 +7,7 @@ public class MonitoringNgoScenarioBuilder { public Guid ElectionRoundId { get; } private readonly Dictionary _forms = new(); - private readonly Dictionary _monitoringObservers = new(); + private readonly Dictionary _monitoringObservers = new(); public readonly Guid MonitoringNgoId; public readonly ElectionRoundScenarioBuilder ParentBuilder; private readonly HttpClient _platformAdmin; @@ -15,6 +15,11 @@ public class MonitoringNgoScenarioBuilder public Guid FormId => _forms.First().Value.FormId; public CreateFormRequest Form => _forms.First().Value.Form; + public (Guid ObserverId, Guid MonitoringObserverId , HttpClient Client, string FullName, string Email, string PhoneNumber) Observer => _monitoringObservers.First().Value; + + public (Guid ObserverId, Guid MonitoringObserverId , HttpClient Client, string FullName, string Email, string PhoneNumber) ObserverByName(ScenarioObserver name) => + _monitoringObservers[name]; + public MonitoringNgoScenarioBuilder(Guid electionRoundId, Guid monitoringNgoId, ElectionRoundScenarioBuilder parentBuilder, @@ -54,15 +59,20 @@ public MonitoringNgoScenarioBuilder WithForm(string? formCode = null, public MonitoringNgoScenarioBuilder WithMonitoringObserver(ScenarioObserver observer) { - var observerClient = ParentBuilder.ParentBuilder.ObserverByName(observer); - var observerId = ParentBuilder.ParentBuilder.ObserverIdByName(observer); + var observerClient = ParentBuilder.ParentBuilder.ClientFor(observer); + var observerId = ParentBuilder.ParentBuilder.IdFor(observer); + var fullName = ParentBuilder.ParentBuilder.FullNameFor(observer); + var email = ParentBuilder.ParentBuilder.EmailFor(observer); + var phone = ParentBuilder.ParentBuilder.PhoneNumberFor(observer); - _platformAdmin + var monitoringObserver = _platformAdmin .PostWithResponse( $"/api/election-rounds/{ParentBuilder.ElectionRoundId}/monitoring-ngos/{MonitoringNgoId}/monitoring-observers" , new { observerId = observerId }); - _monitoringObservers.Add(observer, observerClient); + _monitoringObservers.Add(observer, (observerId,monitoringObserver.Id, observerClient, fullName, email,phone)); return this; } + + } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs index 0ec893b46..c19d02eaf 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioBuilder.cs @@ -11,8 +11,8 @@ public class ScenarioBuilder private readonly Func _clientFactory; private readonly Dictionary _electionRounds = new(); private readonly Dictionary _ngos = new(); - private readonly Dictionary _observers = new(); - + private readonly Dictionary _observers = new(); + public ElectionRoundScenarioBuilder ElectionRound => _electionRounds.First().Value; public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; @@ -22,19 +22,25 @@ public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound el public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => _electionRounds[electionRound].ElectionRoundId; + public string ObserverEmail => _observers.First().Value.Email; + public string ObserverFullName => _observers.First().Value.FullName; + public string ObserverPhoneNumber => _observers.First().Value.PhoneNumber; public HttpClient Observer => _observers.First().Value.Client; public Guid ObserverId => _observers.First().Value.Id; - public HttpClient ObserverByName(ScenarioObserver name) => _observers[name].Client; - public Guid ObserverIdByName(ScenarioObserver name) => _observers[name].Id; - + public string FullNameFor(ScenarioObserver observer) => _observers[observer].FullName; + public HttpClient ClientFor(ScenarioObserver observer) => _observers[observer].Client; + public Guid IdFor(ScenarioObserver observer) => _observers[observer].Id; + public string EmailFor(ScenarioObserver observer)=> _observers[observer].Email; + public string PhoneNumberFor(ScenarioObserver observer)=> _observers[observer].PhoneNumber; public NgoScenarioBuilder Ngo => _ngos.First().Value; public Guid NgoId => _ngos.First().Value.NgoId; public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; - public (ScenarioNgo ngo,NgoScenarioBuilder builder) NgoById(Guid ngoId) + + public (ScenarioNgo ngo, NgoScenarioBuilder builder) NgoById(Guid ngoId) { var ngo = _ngos.First(x => x.Value.NgoId == ngoId); - + return (ngo.Key, ngo.Value); } @@ -82,7 +88,7 @@ public ScenarioBuilder WithElectionRound(ScenarioElectionRound electionRound, var electionRoundScenarioBuilder = new ElectionRoundScenarioBuilder(this, createdElectionRound.Id, PlatformAdmin); _electionRounds.Add(electionRound, electionRoundScenarioBuilder); - + cfg?.Invoke(electionRoundScenarioBuilder); return this; @@ -91,18 +97,15 @@ public ScenarioBuilder WithElectionRound(ScenarioElectionRound electionRound, public ScenarioBuilder WithObserver(ScenarioObserver observer) { var realEmail = $"{Guid.NewGuid()}@example.org"; + var lastName = observer + "-" + Guid.NewGuid(); + var phoneNumber = Guid.NewGuid().ToString("N"); + var createdObserver = PlatformAdmin.PostWithResponse("/api/observers", - new - { - FirstName = "Observer", - LastName = observer + "-" + Guid.NewGuid(), - Email = realEmail, - Password = "string" - }); + new { FirstName = "Observer", LastName = lastName, Email = realEmail, Password = "string" , PhoneNumber= phoneNumber}); var observerClient = _clientFactory.NewForAuthenticatedUser(realEmail, "string"); - _observers.Add(observer, (createdObserver.Id, observerClient)); + _observers.Add(observer, (createdObserver.Id, observerClient, $"Observer {lastName}", realEmail, phoneNumber)); return this; } @@ -110,4 +113,6 @@ public ScenarioData Please() { return new(PlatformAdmin, _electionRounds, _ngos, _observers); } + + } diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs index a30773120..4812513df 100644 --- a/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/Scenarios/ScenarioData.cs @@ -5,13 +5,17 @@ namespace Vote.Monitor.Api.IntegrationTests.Scenarios; public class ScenarioData { public HttpClient PlatformAdmin { get; } - private IReadOnlyDictionary _electionRounds; - private IReadOnlyDictionary _ngos; - private IReadOnlyDictionary _observers; + private readonly IReadOnlyDictionary _electionRounds; + private readonly IReadOnlyDictionary _ngos; - public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary electionRounds, + private readonly IReadOnlyDictionary _observers; + + public ScenarioData(HttpClient platformAdmin, + IReadOnlyDictionary electionRounds, IReadOnlyDictionary ngos, - IReadOnlyDictionary observers) + IReadOnlyDictionary observers) { PlatformAdmin = platformAdmin; _electionRounds = electionRounds; @@ -21,15 +25,19 @@ public ScenarioData(HttpClient platformAdmin, IReadOnlyDictionary _electionRounds.First().Value; public Guid ElectionRoundId => _electionRounds.First().Value.ElectionRoundId; - - public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => _electionRounds[electionRound]; - public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => _electionRounds[electionRound].ElectionRoundId; + + public ElectionRoundScenarioBuilder ElectionRoundByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound]; + + public Guid ElectionRoundIdByName(ScenarioElectionRound electionRound) => + _electionRounds[electionRound].ElectionRoundId; + public HttpClient Observer => _observers.First().Value.Client; public Guid ObserverId => _observers.First().Value.Id; - + public HttpClient ObserverByName(ScenarioObserver observer) => _observers[observer].Client; public Guid ObserverIdByName(ScenarioObserver observer) => _observers[observer].Id; - + public NgoScenarioBuilder Ngo => _ngos.First().Value; public Guid NgoId => _ngos.First().Value.NgoId; public NgoScenarioBuilder NgoByName(ScenarioNgo ngo) => _ngos[ngo]; diff --git a/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs b/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs new file mode 100644 index 000000000..7b75da5ba --- /dev/null +++ b/api/tests/Vote.Monitor.Api.IntegrationTests/TestCases/DataSourcesTestCases.cs @@ -0,0 +1,13 @@ +using System.Collections; +using Vote.Monitor.Core.Models; + +namespace Vote.Monitor.Api.IntegrationTests.TestCases; + +public class DataSourcesTestCases : IEnumerable +{ + public IEnumerator GetEnumerator() + { + yield return new object[] { DataSource.Ngo }; + yield return new object[] { DataSource.Coalition }; + } +} diff --git a/web/src/common/prev-data-source-store.ts b/web/src/common/prev-data-source-store.ts index 3f530d394..2d1e37443 100644 --- a/web/src/common/prev-data-source-store.ts +++ b/web/src/common/prev-data-source-store.ts @@ -10,7 +10,7 @@ type PrevDataSourceStore = { const usePrevDataSourceStore = create( persist( (set) => ({ - dataSource: DataSources.MyNgo, + dataSource: DataSources.Ngo, setDataSource: (dataSource: DataSources) => { set({ dataSource }); }, diff --git a/web/src/common/types.ts b/web/src/common/types.ts index 05c532036..531149b1f 100644 --- a/web/src/common/types.ts +++ b/web/src/common/types.ts @@ -284,6 +284,6 @@ export interface Coalition { } export enum DataSources { - MyNgo = 'myNgo', + Ngo = 'ngo', Coalition = 'coalition', } diff --git a/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx index 04c7b975e..15d894184 100644 --- a/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx +++ b/web/src/components/DataSourceSwitcher/DataSourceSwitcher.tsx @@ -37,7 +37,7 @@ export function DataSourceSwitcher(): FunctionComponent { useEffect(() => { if (search.dataSource === undefined) { - navigateHandler(prevDataSource ?? DataSources.MyNgo); + navigateHandler(prevDataSource ?? DataSources.Ngo); return; } @@ -49,7 +49,7 @@ export function DataSourceSwitcher(): FunctionComponent { navigateHandler(checked ? DataSources.Coalition : DataSources.MyNgo)} + onCheckedChange={(checked) => navigateHandler(checked ? DataSources.Coalition : DataSources.Ngo)} className='data-[state=checked]:bg-primary' />