From 65d8e29e0329f26957708399933da7610951e3de Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Mon, 23 Sep 2024 16:11:07 +0300 Subject: [PATCH 1/7] Add citizen reports api --- api/Vote.Monitor.sln | 14 ++ ...enReportingNgoAdminAuthorizationHandler.cs | 49 +++++ .../MonitoringNgoAdminAuthorizationHandler.cs | 11 +- .../CitizenReportingNgoAdminRequirement.cs | 6 + .../MonitoringNgoAdminRequirement.cs | 2 +- ...izenReportingMonitoringNgoSpecification.cs | 27 +++ .../GetMonitoringNgoSpecification.cs | 2 +- .../CitizenReportsGuideModel.cs | 23 +++ .../CitizenReportsGuidesFeatureInstaller.cs | 11 ++ .../Create/Endpoint.cs | 103 ++++++++++ .../Create/Request.cs | 22 +++ .../Create/Validator.cs | 28 +++ .../Delete/Endpoint.cs | 41 ++++ .../Delete/Request.cs | 7 + .../Delete/Validator.cs | 10 + .../EnableTesting.cs | 5 + .../Feature.CitizenReports.Guides.csproj | 20 ++ .../GlobalUsings.cs | 8 + .../List/Endpoint.cs | 100 ++++++++++ .../List/Request.cs | 6 + .../List/Response.cs | 6 + .../List/Validator.cs | 9 + .../GetObserverGuideByIdSpecification.cs | 15 ++ .../Update/Endpoint.cs | 69 +++++++ .../Update/Request.cs | 10 + .../Update/Validator.cs | 17 ++ .../Feature.ObserverGuide/Create/Endpoint.cs | 46 +++-- .../Feature.ObserverGuide/Create/Request.cs | 4 + .../Feature.ObserverGuide/Create/Validator.cs | 21 ++- .../ObserverGuideModel.cs | 6 +- api/src/Vote.Monitor.Api/Dockerfile | 2 + api/src/Vote.Monitor.Api/Program.cs | 177 ++++++++++-------- .../Vote.Monitor.Api/Vote.Monitor.Api.csproj | 1 + .../Validators/FileSizeValidator.cs | 6 +- .../Validators/FileSizeValidatorExtension.cs | 2 +- .../Validators/UriValidator.cs | 20 ++ .../Validators/UriValidatorExtension.cs | 9 + .../CitizenReportGuide.cs | 99 ++++++++++ .../CitizenReportGuideType.cs | 27 +++ .../ObserverGuideAggregate/ObserverGuide.cs | 45 ++++- .../ObserverGuideType.cs | 1 + .../Vote.Monitor.Domain/VoteMonitorContext.cs | 2 + ...ure.CitizenReports.Guides.UnitTests.csproj | 37 ++++ .../GlobalUsings.cs | 1 + .../Validators/CreateValidatorTests.cs | 158 ++++++++++++++++ .../Validators/DeleteValidatorTests.cs | 37 ++++ .../Validators/ListValidatorTests.cs | 38 ++++ .../Validators/UpdateValidatorTests.cs | 106 +++++++++++ 48 files changed, 1346 insertions(+), 120 deletions(-) create mode 100644 api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs create mode 100644 api/src/Authorization.Policies/Requirements/CitizenReportingNgoAdminRequirement.cs create mode 100644 api/src/Authorization.Policies/Specifications/GetCitizenReportingMonitoringNgoSpecification.cs create mode 100644 api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs create mode 100644 api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Create/Request.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Create/Validator.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Delete/Request.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Delete/Validator.cs create mode 100644 api/src/Feature.CitizenReports.Guides/EnableTesting.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj create mode 100644 api/src/Feature.CitizenReports.Guides/GlobalUsings.cs create mode 100644 api/src/Feature.CitizenReports.Guides/List/Endpoint.cs create mode 100644 api/src/Feature.CitizenReports.Guides/List/Request.cs create mode 100644 api/src/Feature.CitizenReports.Guides/List/Response.cs create mode 100644 api/src/Feature.CitizenReports.Guides/List/Validator.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Update/Request.cs create mode 100644 api/src/Feature.CitizenReports.Guides/Update/Validator.cs create mode 100644 api/src/Vote.Monitor.Core/Validators/UriValidator.cs create mode 100644 api/src/Vote.Monitor.Core/Validators/UriValidatorExtension.cs create mode 100644 api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs create mode 100644 api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs create mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs diff --git a/api/Vote.Monitor.sln b/api/Vote.Monitor.sln index 91be0f178..01585e85f 100644 --- a/api/Vote.Monitor.sln +++ b/api/Vote.Monitor.sln @@ -150,6 +150,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations", "src\Fe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations.UnitTests", "tests\Feature.Locations.UnitTests\Feature.Locations.UnitTests.csproj", "{6DC3922B-5AC8-4968-AE5C-557315576720}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Guides", "src\Feature.CitizenReports.Guides\Feature.CitizenReports.Guides.csproj", "{A4842D5C-A8B6-4260-9071-A2431889EA24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Guides.UnitTests", "tests\Feature.CitizenReports.Guides.UnitTests\Feature.CitizenReports.Guides.UnitTests.csproj", "{4EFDD417-A568-4F39-BE44-0D97003EC42E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -428,6 +432,14 @@ Global {6DC3922B-5AC8-4968-AE5C-557315576720}.Debug|Any CPU.Build.0 = Debug|Any CPU {6DC3922B-5AC8-4968-AE5C-557315576720}.Release|Any CPU.ActiveCfg = Release|Any CPU {6DC3922B-5AC8-4968-AE5C-557315576720}.Release|Any CPU.Build.0 = Release|Any CPU + {A4842D5C-A8B6-4260-9071-A2431889EA24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4842D5C-A8B6-4260-9071-A2431889EA24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4842D5C-A8B6-4260-9071-A2431889EA24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4842D5C-A8B6-4260-9071-A2431889EA24}.Release|Any CPU.Build.0 = Release|Any CPU + {4EFDD417-A568-4F39-BE44-0D97003EC42E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EFDD417-A568-4F39-BE44-0D97003EC42E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EFDD417-A568-4F39-BE44-0D97003EC42E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EFDD417-A568-4F39-BE44-0D97003EC42E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -501,6 +513,8 @@ Global {CCE23C74-3E33-40B7-A1E8-7672BAC5F814} = {3441EE1D-E3C6-45BE-A020-553816015081} {54B0E751-AD8A-48F5-998C-E6A5701E3EF0} = {17945B3C-5A4C-4279-8022-65ABC606A510} {6DC3922B-5AC8-4968-AE5C-557315576720} = {3441EE1D-E3C6-45BE-A020-553816015081} + {A4842D5C-A8B6-4260-9071-A2431889EA24} = {17945B3C-5A4C-4279-8022-65ABC606A510} + {4EFDD417-A568-4F39-BE44-0D97003EC42E} = {3441EE1D-E3C6-45BE-A020-553816015081} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {50C20C9F-F2AF-45D8-994A-06661772B31C} diff --git a/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs new file mode 100644 index 000000000..8171bf3b1 --- /dev/null +++ b/api/src/Authorization.Policies/RequirementHandlers/CitizenReportingNgoAdminAuthorizationHandler.cs @@ -0,0 +1,49 @@ +using Authorization.Policies.Requirements; +using Authorization.Policies.Specifications; +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.RequirementHandlers; + +internal class CitizenReportingNgoAdminAuthorizationHandler( + ICurrentUserProvider currentUserProvider, + ICurrentUserRoleProvider currentUserRoleProvider, + IReadRepository electionRoundRepository) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + CitizenReportingNgoAdminRequirement requirement) + { + if (!currentUserRoleProvider.IsNgoAdmin()) + { + context.Fail(); + return; + } + + var ngoId = currentUserProvider.GetNgoId(); + if (ngoId is null) + { + context.Fail(); + return; + } + + var getMonitoringNgoSpecification = + new GetCitizenReportingMonitoringNgoSpecification(requirement.ElectionRoundId, ngoId.Value); + var result = await electionRoundRepository.FirstOrDefaultAsync(getMonitoringNgoSpecification); + + 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); + } +} \ No newline at end of file diff --git a/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminAuthorizationHandler.cs b/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminAuthorizationHandler.cs index 20cf7158d..c077ed4cc 100644 --- a/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminAuthorizationHandler.cs +++ b/api/src/Authorization.Policies/RequirementHandlers/MonitoringNgoAdminAuthorizationHandler.cs @@ -19,7 +19,14 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext } var ngoId = currentUserProvider.GetNgoId(); - var getMonitoringNgoSpecification = new GetMonitoringNgoSpecification(adminRequirement.ElectionRoundId, ngoId!.Value); + if (ngoId is null) + { + context.Fail(); + return; + } + + + var getMonitoringNgoSpecification = new GetMonitoringNgoSpecification(adminRequirement.ElectionRoundId, ngoId.Value); var result = await monitoringNgoRepository.FirstOrDefaultAsync(getMonitoringNgoSpecification); if (result is null) @@ -38,4 +45,4 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context.Succeed(adminRequirement); } -} +} \ No newline at end of file diff --git a/api/src/Authorization.Policies/Requirements/CitizenReportingNgoAdminRequirement.cs b/api/src/Authorization.Policies/Requirements/CitizenReportingNgoAdminRequirement.cs new file mode 100644 index 000000000..efa071545 --- /dev/null +++ b/api/src/Authorization.Policies/Requirements/CitizenReportingNgoAdminRequirement.cs @@ -0,0 +1,6 @@ +namespace Authorization.Policies.Requirements; + +public class CitizenReportingNgoAdminRequirement(Guid electionRoundId) : IAuthorizationRequirement +{ + public Guid ElectionRoundId { get; } = electionRoundId; +} \ No newline at end of file diff --git a/api/src/Authorization.Policies/Requirements/MonitoringNgoAdminRequirement.cs b/api/src/Authorization.Policies/Requirements/MonitoringNgoAdminRequirement.cs index e373d2cf2..4779f24a8 100644 --- a/api/src/Authorization.Policies/Requirements/MonitoringNgoAdminRequirement.cs +++ b/api/src/Authorization.Policies/Requirements/MonitoringNgoAdminRequirement.cs @@ -3,4 +3,4 @@ public class MonitoringNgoAdminRequirement(Guid electionRoundId) : IAuthorizationRequirement { public Guid ElectionRoundId { get; } = electionRoundId; -} +} \ No newline at end of file diff --git a/api/src/Authorization.Policies/Specifications/GetCitizenReportingMonitoringNgoSpecification.cs b/api/src/Authorization.Policies/Specifications/GetCitizenReportingMonitoringNgoSpecification.cs new file mode 100644 index 000000000..b53d90070 --- /dev/null +++ b/api/src/Authorization.Policies/Specifications/GetCitizenReportingMonitoringNgoSpecification.cs @@ -0,0 +1,27 @@ +using Ardalis.Specification; +using Vote.Monitor.Domain.Entities.ElectionRoundAggregate; + +namespace Authorization.Policies.Specifications; + +internal sealed class GetCitizenReportingMonitoringNgoSpecification : SingleResultSpecification +{ + public GetCitizenReportingMonitoringNgoSpecification(Guid electionRoundId, Guid ngoId) + { + Query + .Include(x => x.MonitoringNgoForCitizenReporting) + .ThenInclude(x => x.Ngo) + .Where(x => x.CitizenReportingEnabled && x.Id == electionRoundId && + x.MonitoringNgoForCitizenReporting.NgoId == ngoId) + .AsNoTracking(); + + Query.Select(x => new MonitoringNgoView + { + ElectionRoundId = x.Id, + ElectionRoundStatus = x.Status, + NgoId = x.MonitoringNgoForCitizenReporting.NgoId, + NgoStatus = x.MonitoringNgoForCitizenReporting.Ngo.Status, + MonitoringNgoId = x.MonitoringNgoForCitizenReporting.Id, + MonitoringNgoStatus = x.MonitoringNgoForCitizenReporting.Status + }); + } +} \ No newline at end of file diff --git a/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs b/api/src/Authorization.Policies/Specifications/GetMonitoringNgoSpecification.cs index 99819c7f4..01c1e6c24 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/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs b/api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs new file mode 100644 index 000000000..47a0442d9 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using Ardalis.SmartEnum.SystemTextJson; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +namespace Feature.CitizenReports.Guides; + +public record CitizenReportsGuideModel +{ + public required Guid Id { get; init; } + public string Title { get; init; } = string.Empty; + public string? FileName { get; init; } = string.Empty; + public string? MimeType { get; init; } = string.Empty; + public string? PresignedUrl { get; init; } = string.Empty; + public int? UrlValidityInSeconds { get; init; } + public string? WebsiteUrl { get; init; } + public string? Text { get; init; } + + [JsonConverter(typeof(SmartEnumNameConverter))] + public CitizenReportGuideType GuideType { get; init; } + + public DateTime CreatedOn { get; init; } + public string CreatedBy { get; init; } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs b/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs new file mode 100644 index 000000000..1736e0f04 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Feature.CitizenReports.Guides; + +public static class CitizenReportsGuidesFeatureInstaller +{ + public static IServiceCollection AddCitizenReportsGuidesFeature(this IServiceCollection services) + { + return services; + } +} diff --git a/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs new file mode 100644 index 000000000..657ebb54c --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs @@ -0,0 +1,103 @@ +using System.Net; +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Core.Services.FileStorage.Contracts; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +namespace Feature.CitizenReports.Guides.Create; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository repository, + IFileStorageService fileStorageService) + : Endpoint, NotFound, StatusCodeHttpResult>> +{ + public override void Configure() + { + Post("/api/election-rounds/{electionRoundId}/citizen-reports-guides"); + DontAutoTag(); + Options(x => x.WithTags("citizen-reports-guides")); + AllowFileUploads(); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound, StatusCodeHttpResult>> ExecuteAsync( + Request req, CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + CitizenReportGuideAggregate? observerGuide = null; + CitizenReportsGuideModel? observerGuideModel = null; + if (req.GuideType == CitizenReportGuideType.Document) + { + var uploadPath = $"elections/{req.ElectionRoundId}/citizen-reports-guides"; + + observerGuide = CitizenReportGuideAggregate.NewDocumentGuide(req.ElectionRoundId, + req.Title, + req.Attachment!.FileName, + uploadPath, + req.Attachment.ContentType); + + var uploadResult = await fileStorageService.UploadFileAsync(uploadPath, + fileName: observerGuide.UploadedFileName!, + req.Attachment.OpenReadStream(), + ct); + + if (uploadResult is UploadFileResult.Failed) + { + return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError); + } + + var result = uploadResult as UploadFileResult.Ok; + observerGuideModel = new CitizenReportsGuideModel + { + Title = observerGuide.Title, + FileName = observerGuide.FileName!, + PresignedUrl = result!.Url, + MimeType = observerGuide.MimeType!, + UrlValidityInSeconds = result.UrlValidityInSeconds, + Id = observerGuide.Id, + GuideType = observerGuide.GuideType + }; + } + + if (req.GuideType == CitizenReportGuideType.Website) + { + observerGuide = CitizenReportGuideAggregate.NewWebsiteGuide(req.ElectionRoundId, + req.Title, + new Uri(req.WebsiteUrl!)); + + observerGuideModel = new CitizenReportsGuideModel + { + Id = observerGuide.Id, + Title = observerGuide.Title, + WebsiteUrl = observerGuide.WebsiteUrl, + GuideType = observerGuide.GuideType + }; + } + + if (req.GuideType == CitizenReportGuideType.Text) + { + observerGuide = CitizenReportGuideAggregate.NewTextGuide(req.ElectionRoundId, + req.Title, + req.Text!); + + observerGuideModel = new CitizenReportsGuideModel + { + Id = observerGuide.Id, + Title = observerGuide.Title, + Text = observerGuide.Text, + GuideType = observerGuide.GuideType + }; + } + + await repository.AddAsync(observerGuide!, ct); + return TypedResults.Ok(observerGuideModel!); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Create/Request.cs b/api/src/Feature.CitizenReports.Guides/Create/Request.cs new file mode 100644 index 000000000..30cecb0d8 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Create/Request.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; +using Vote.Monitor.Core.Security; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +namespace Feature.CitizenReports.Guides.Create; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + public string Title { get; set; } + + public CitizenReportGuideType GuideType { get; set; } + + [FromForm] + public IFormFile? Attachment { get; set; } + + public string? WebsiteUrl { get; set; } + public string? Text { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/Create/Validator.cs b/api/src/Feature.CitizenReports.Guides/Create/Validator.cs new file mode 100644 index 000000000..bd870ecee --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Create/Validator.cs @@ -0,0 +1,28 @@ +using Vote.Monitor.Core.Validators; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +namespace Feature.CitizenReports.Guides.Create; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.Title).NotEmpty().MaximumLength(256); + + RuleFor(x => x.Attachment) + .NotEmpty()! + .FileSmallerThan(50 * 1024 * 1024) // 50 MB upload limit + .When(x => x.GuideType == CitizenReportGuideType.Document); + + RuleFor(x => x.WebsiteUrl) + .NotEmpty()! + .MaximumLength(2048) + .IsValidUri() + .When(x => x.GuideType == CitizenReportGuideType.Website); + + RuleFor(x => x.WebsiteUrl) + .NotEmpty()! + .When(x => x.GuideType == CitizenReportGuideType.Text); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs new file mode 100644 index 000000000..eb5849766 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs @@ -0,0 +1,41 @@ +using Authorization.Policies.Requirements; +using Feature.CitizenReports.Guides.Specifications; +using Microsoft.AspNetCore.Authorization; + +namespace Feature.CitizenReports.Guides.Delete; + +public class Endpoint(IAuthorizationService authorizationService, + IRepository repository) + : Endpoint> +{ + public override void Configure() + { + Delete("/api/election-rounds/{electionRoundId}/citizen-reports-guides/{id}"); + DontAutoTag(); + Options(x => x.WithTags("citizen-reports-guides")); + } + + public override async Task> ExecuteAsync(Request req, CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var specification = new GetObserverGuideByIdSpecification(req.ElectionRoundId, req.Id); + var observerGuide = await repository.FirstOrDefaultAsync(specification, ct); + + if (observerGuide == null) + { + return TypedResults.NotFound(); + } + + observerGuide.Delete(); + + await repository.UpdateAsync(observerGuide, ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Request.cs b/api/src/Feature.CitizenReports.Guides/Delete/Request.cs new file mode 100644 index 000000000..f4510bb43 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Delete/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.CitizenReports.Guides.Delete; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid Id { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Validator.cs b/api/src/Feature.CitizenReports.Guides/Delete/Validator.cs new file mode 100644 index 000000000..1a0e53e6a --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Delete/Validator.cs @@ -0,0 +1,10 @@ +namespace Feature.CitizenReports.Guides.Delete; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.Id).NotEmpty(); + } +} diff --git a/api/src/Feature.CitizenReports.Guides/EnableTesting.cs b/api/src/Feature.CitizenReports.Guides/EnableTesting.cs new file mode 100644 index 000000000..4daf42299 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/EnableTesting.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Feature.CitizenReports.Guides.UnitTests")] +[assembly: InternalsVisibleTo("Vote.Monitor.Api.IntegrationTests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj b/api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj new file mode 100644 index 000000000..77da37da3 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/api/src/Feature.CitizenReports.Guides/GlobalUsings.cs b/api/src/Feature.CitizenReports.Guides/GlobalUsings.cs new file mode 100644 index 000000000..c1d392e54 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/GlobalUsings.cs @@ -0,0 +1,8 @@ +// Global using directives + +global using FastEndpoints; +global using FluentValidation; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Http.HttpResults; +global using Vote.Monitor.Domain.Repository; +global using CitizenReportGuideAggregate = Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate.CitizenReportGuide; diff --git a/api/src/Feature.CitizenReports.Guides/List/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/List/Endpoint.cs new file mode 100644 index 000000000..712362109 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/List/Endpoint.cs @@ -0,0 +1,100 @@ +using Authorization.Policies.Requirements; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Core.Services.FileStorage.Contracts; +using Vote.Monitor.Core.Services.Security; +using Vote.Monitor.Domain; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; + +namespace Feature.CitizenReports.Guides.List; + +public class Endpoint(IAuthorizationService authorizationService, + ICurrentUserRoleProvider currentUserRoleProvider, + VoteMonitorContext context, + IFileStorageService fileStorageService) + : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/citizen-reports-guides"); + DontAutoTag(); + Options(x => x.WithTags("citizen-reports-guides")); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var isNgoAdmin = currentUserRoleProvider.IsNgoAdmin(); + if (isNgoAdmin) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + } + + // ReSharper disable once EntityFramework.NPlusOne.IncompleteDataQuery + var citizenReportGuides = await context + .CitizenReportGuides + .Where(x => x.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted) + .OrderByDescending(x=>x.CreatedOn) + .Join(context.Users, guide=>guide.CreatedBy, user => user.Id, (guide, ngoAdmin) => new + { + guide.Id, + guide.Title, + guide.FileName, + guide.UploadedFileName, + guide.MimeType, + guide.GuideType, + guide.CreatedOn, + guide.FilePath, + guide.Text, + guide.WebsiteUrl, + CreatedBy = isNgoAdmin ? ngoAdmin.FirstName + " " + ngoAdmin.LastName : "" + }) + .AsNoTracking() + .ToListAsync(ct); + + var tasks = citizenReportGuides + .Select(async guide => + { + if (guide.GuideType == ObserverGuideType.Document) + { + var presignedUrl = await fileStorageService.GetPresignedUrlAsync( + guide.FilePath!, + guide.UploadedFileName!); + + return new CitizenReportsGuideModel + { + Id = guide.Id, + Title = guide.Title, + FileName = guide.FileName, + PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + MimeType = guide.MimeType, + GuideType = guide.GuideType, + CreatedOn = guide.CreatedOn, + CreatedBy = guide.CreatedBy + }; + } + + return new CitizenReportsGuideModel + { + Id = guide.Id, + Title = guide.Title, + FileName = guide.FileName, + MimeType = guide.MimeType, + GuideType = guide.GuideType, + CreatedOn = guide.CreatedOn, + Text = guide.Text, + WebsiteUrl = guide.WebsiteUrl, + CreatedBy = guide.CreatedBy + }; + }); + + var guides = await Task.WhenAll(tasks); + + return TypedResults.Ok(new Response { Guides = guides.ToList() }); + } +} diff --git a/api/src/Feature.CitizenReports.Guides/List/Request.cs b/api/src/Feature.CitizenReports.Guides/List/Request.cs new file mode 100644 index 000000000..bf30af95a --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/List/Request.cs @@ -0,0 +1,6 @@ +namespace Feature.CitizenReports.Guides.List; + +public class Request +{ + public Guid ElectionRoundId { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/List/Response.cs b/api/src/Feature.CitizenReports.Guides/List/Response.cs new file mode 100644 index 000000000..37e38955d --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/List/Response.cs @@ -0,0 +1,6 @@ +namespace Feature.CitizenReports.Guides.List; + +public record Response +{ + public required List Guides { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/List/Validator.cs b/api/src/Feature.CitizenReports.Guides/List/Validator.cs new file mode 100644 index 000000000..3495afb71 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/List/Validator.cs @@ -0,0 +1,9 @@ +namespace Feature.CitizenReports.Guides.List; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + } +} diff --git a/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs b/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs new file mode 100644 index 000000000..a9c4df216 --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs @@ -0,0 +1,15 @@ +using Ardalis.Specification; + +namespace Feature.CitizenReports.Guides.Specifications; + +public sealed class GetObserverGuideByIdSpecification : SingleResultSpecification +{ + public GetObserverGuideByIdSpecification(Guid electionRoundId, Guid id) + { + Query + .Where(x => + x.ElectionRoundId == electionRoundId + && x.Id == id + && !x.IsDeleted); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs new file mode 100644 index 000000000..26945195e --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs @@ -0,0 +1,69 @@ +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Feature.CitizenReports.Guides.Specifications; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +namespace Feature.CitizenReports.Guides.Update; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository repository) + : Endpoint, NotFound, NoContent>> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/citizen-reports-guides/{id}"); + DontAutoTag(); + Options(x => x.WithTags("citizen-reports-guides")); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound, NoContent>> ExecuteAsync(Request req, + CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var specification = new GetObserverGuideByIdSpecification(req.ElectionRoundId, req.Id); + var guide = await repository.FirstOrDefaultAsync(specification, ct); + + if (guide is null) + { + return TypedResults.NotFound(); + } + + if (guide.GuideType == CitizenReportGuideType.Document) + { + guide.UpdateTitle(req.Title); + } + + if (guide.GuideType == CitizenReportGuideType.Text) + { + if (string.IsNullOrWhiteSpace(req.Text)) + { + ThrowError(x => x.Text, "Text is required."); + } + + guide.UpdateTextGuide(req.Title, req.Text); + } + + if (guide.GuideType == CitizenReportGuideType.Website) + { + if (string.IsNullOrWhiteSpace(req.WebsiteUrl)) + { + ThrowError(x => x.WebsiteUrl, "Website url is required."); + } + + guide.UpdateWebsiteGuide(req.Title, new Uri(req.WebsiteUrl)); + } + + await repository.UpdateAsync(guide, ct); + + return TypedResults.NoContent(); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Update/Request.cs b/api/src/Feature.CitizenReports.Guides/Update/Request.cs new file mode 100644 index 000000000..d690ee10a --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Update/Request.cs @@ -0,0 +1,10 @@ +namespace Feature.CitizenReports.Guides.Update; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid Id { get; set; } + public string Title { get; set; } + public string? Text { get; set; } + public string? WebsiteUrl { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/Update/Validator.cs b/api/src/Feature.CitizenReports.Guides/Update/Validator.cs new file mode 100644 index 000000000..79ea728ca --- /dev/null +++ b/api/src/Feature.CitizenReports.Guides/Update/Validator.cs @@ -0,0 +1,17 @@ +using Vote.Monitor.Core.Validators; + +namespace Feature.CitizenReports.Guides.Update; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.Title).NotEmpty().MaximumLength(256); + + RuleFor(x => x.WebsiteUrl) + .IsValidUri() + .MaximumLength(2048) + .When(x => !string.IsNullOrWhiteSpace(x.WebsiteUrl)); + } +} \ No newline at end of file diff --git a/api/src/Feature.ObserverGuide/Create/Endpoint.cs b/api/src/Feature.ObserverGuide/Create/Endpoint.cs index 9e42c9132..3a52f70b6 100644 --- a/api/src/Feature.ObserverGuide/Create/Endpoint.cs +++ b/api/src/Feature.ObserverGuide/Create/Endpoint.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; namespace Feature.ObserverGuide.Create; @@ -24,9 +25,11 @@ public override void Configure() Policies(PolicyNames.NgoAdminsOnly); } - public override async Task, NotFound, StatusCodeHttpResult>> ExecuteAsync(Request req, CancellationToken ct) + public override async Task, NotFound, StatusCodeHttpResult>> ExecuteAsync( + Request req, CancellationToken ct) { - var authorizationResult = await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); + var authorizationResult = + await authorizationService.AuthorizeAsync(User, new MonitoringNgoAdminRequirement(req.ElectionRoundId)); if (!authorizationResult.Succeeded) { return TypedResults.NotFound(); @@ -39,13 +42,13 @@ public override async Task, NotFound, StatusCodeH return TypedResults.NotFound(); } - ObserverGuideAggregate observerGuide; - ObserverGuideModel observerGuideModel; - if (string.IsNullOrEmpty(req.WebsiteUrl)) + ObserverGuideAggregate? observerGuide = null; + ObserverGuideModel? observerGuideModel = null; + if (req.GuideType == ObserverGuideType.Document) { var uploadPath = $"elections/{req.ElectionRoundId}/ngo/{monitoringNgo.NgoId}/observer-guides"; - observerGuide = ObserverGuideAggregate.CreateForDocument(monitoringNgo, + observerGuide = ObserverGuideAggregate.NewDocumentGuide(monitoringNgo, req.Title, req.Attachment!.FileName, uploadPath, @@ -73,23 +76,38 @@ public override async Task, NotFound, StatusCodeH GuideType = observerGuide.GuideType }; } - else + + if (req.GuideType == ObserverGuideType.Website) { - observerGuide = ObserverGuideAggregate.CreateForWebsite(monitoringNgo, + observerGuide = ObserverGuideAggregate.NewWebsiteGuide(monitoringNgo, req.Title, - req.WebsiteUrl); + new Uri(req.WebsiteUrl!)); observerGuideModel = new ObserverGuideModel { Id = observerGuide.Id, Title = observerGuide.Title, - FileName = observerGuide.FileName, WebsiteUrl = observerGuide.WebsiteUrl, - GuideType = observerGuide.GuideType + GuideType = observerGuide.GuideType + }; + } + + if (req.GuideType == ObserverGuideType.Text) + { + observerGuide = ObserverGuideAggregate.NewTextGuide(monitoringNgo, + req.Title, + req.Text!); + + observerGuideModel = new ObserverGuideModel + { + Id = observerGuide.Id, + Title = observerGuide.Title, + Text = observerGuide.Text, + GuideType = observerGuide.GuideType }; } - await repository.AddAsync(observerGuide, ct); - return TypedResults.Ok(observerGuideModel); + await repository.AddAsync(observerGuide!, ct); + return TypedResults.Ok(observerGuideModel!); } -} +} \ No newline at end of file diff --git a/api/src/Feature.ObserverGuide/Create/Request.cs b/api/src/Feature.ObserverGuide/Create/Request.cs index 2406a9231..d351ae4df 100644 --- a/api/src/Feature.ObserverGuide/Create/Request.cs +++ b/api/src/Feature.ObserverGuide/Create/Request.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Vote.Monitor.Core.Security; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; namespace Feature.ObserverGuide.Create; @@ -11,8 +12,11 @@ public class Request public Guid NgoId { get; set; } public string Title { get; set; } + public ObserverGuideType GuideType { get; set; } + [FromForm] public IFormFile? Attachment { get; set; } public string? WebsiteUrl { get; set; } + public string? Text { get; set; } } diff --git a/api/src/Feature.ObserverGuide/Create/Validator.cs b/api/src/Feature.ObserverGuide/Create/Validator.cs index 5b8d5dc8e..b4b7fe928 100644 --- a/api/src/Feature.ObserverGuide/Create/Validator.cs +++ b/api/src/Feature.ObserverGuide/Create/Validator.cs @@ -1,4 +1,7 @@ -namespace Feature.ObserverGuide.Create; +using Vote.Monitor.Core.Validators; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; + +namespace Feature.ObserverGuide.Create; public class Validator : Validator { @@ -8,12 +11,18 @@ public Validator() RuleFor(x => x.Title).NotEmpty().MaximumLength(256); RuleFor(x => x.Attachment) - .NotEmpty()! - .When(x => string.IsNullOrEmpty(x.WebsiteUrl)); // 50 MB upload limit + .NotEmpty() + .FileSmallerThan(50 * 1024 * 1024) // 50 MB upload limit + .When(x => x.GuideType == ObserverGuideType.Document); RuleFor(x => x.WebsiteUrl) - .NotEmpty()! + .NotEmpty() .MaximumLength(2048) - .When(x => x.Attachment == null); + .IsValidUri() + .When(x => x.GuideType == ObserverGuideType.Website); + + RuleFor(x => x.WebsiteUrl) + .NotEmpty() + .When(x => x.GuideType == ObserverGuideType.Text); } -} +} \ No newline at end of file diff --git a/api/src/Feature.ObserverGuide/ObserverGuideModel.cs b/api/src/Feature.ObserverGuide/ObserverGuideModel.cs index ad8e7e54c..2ec0e0432 100644 --- a/api/src/Feature.ObserverGuide/ObserverGuideModel.cs +++ b/api/src/Feature.ObserverGuide/ObserverGuideModel.cs @@ -13,9 +13,11 @@ public record ObserverGuideModel public string? PresignedUrl { get; init; } = string.Empty; public int? UrlValidityInSeconds { get; init; } public string? WebsiteUrl { get; init; } + public string? Text { get; set; } [JsonConverter(typeof(SmartEnumNameConverter))] public ObserverGuideType GuideType { get; init; } + public DateTime CreatedOn { get; init; } - public string CreatedBy{ get; init; } -} + public string CreatedBy { get; init; } +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api/Dockerfile b/api/src/Vote.Monitor.Api/Dockerfile index d311feea8..4ea334d43 100644 --- a/api/src/Vote.Monitor.Api/Dockerfile +++ b/api/src/Vote.Monitor.Api/Dockerfile @@ -45,6 +45,8 @@ COPY ["src/Feature.Notes/Feature.Notes.csproj", "src/Feature.Notes/"] COPY ["src/Vote.Monitor.Api.Feature.UserPreferences/Vote.Monitor.Api.Feature.UserPreferences.csproj", "src/Vote.Monitor.Api.Feature.UserPreferences/"] COPY ["src/Vote.Monitor.Api.Feature.PollingStation/Vote.Monitor.Api.Feature.PollingStation.csproj", "src/Vote.Monitor.Api.Feature.PollingStation/"] COPY ["src/Feature.Locations/Feature.Locations.csproj", "src/Feature.Locations/"] +COPY ["src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj", "src/Feature.CitizenReports.Guides/"] + RUN dotnet restore "src/Vote.Monitor.Api/Vote.Monitor.Api.csproj" COPY . . WORKDIR "/src/src/Vote.Monitor.Api" diff --git a/api/src/Vote.Monitor.Api/Program.cs b/api/src/Vote.Monitor.Api/Program.cs index 7bcc306aa..a482ad724 100644 --- a/api/src/Vote.Monitor.Api/Program.cs +++ b/api/src/Vote.Monitor.Api/Program.cs @@ -34,6 +34,7 @@ using Dapper; using Feature.CitizenReports; using Feature.CitizenReports.Attachments; +using Feature.CitizenReports.Guides; using Feature.CitizenReports.Notes; using Feature.DataExport; using Feature.Feedback; @@ -45,6 +46,8 @@ using Microsoft.AspNetCore.Http.Features; using Vote.Monitor.Core.Converters; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; var builder = WebApplication.CreateBuilder(args); @@ -84,41 +87,42 @@ builder.Services.AddOptions(); builder.Services.AddLogging(logging => - { - Serilog.Debugging.SelfLog.Enable(Console.WriteLine); +{ + Serilog.Debugging.SelfLog.Enable(Console.WriteLine); - var loggerConfiguration = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) - .WriteTo.Console() - .Enrich.FromLogContext() - .Enrich.WithMachineName() - .Enrich.WithEnvironmentUserName() - .WriteToSentry(builder.Configuration) - .Destructure.ToMaximumDepth(3); + var loggerConfiguration = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .WriteTo.Console() + .Enrich.FromLogContext() + .Enrich.WithMachineName() + .Enrich.WithEnvironmentUserName() + .WriteToSentry(builder.Configuration) + .Destructure.ToMaximumDepth(3); - var logger = Log.Logger = loggerConfiguration.CreateLogger(); + var logger = Log.Logger = loggerConfiguration.CreateLogger(); - logging.AddSerilog(logger); - }); + logging.AddSerilog(logger); +}); builder.Services.AddCors(options => - { - options.AddPolicy("AllowAll", - policy => - { - policy +{ + options.AddPolicy("AllowAll", + policy => + { + policy .AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); - }); - }); + }); +}); builder.Services.AddCoreServices(builder.Configuration.GetRequiredSection(CoreServicesInstaller.SectionKey)); builder.Services.AddFileStorage(builder.Configuration.GetRequiredSection(FileStorageInstaller.SectionKey)); builder.Services.AddPushNotifications(builder.Configuration.GetRequiredSection(PushNotificationsInstaller.SectionKey)); -builder.Services.AddApplicationDomain(builder.Configuration.GetSection(DomainInstaller.SectionKey), builder.Environment.IsProduction()); +builder.Services.AddApplicationDomain(builder.Configuration.GetSection(DomainInstaller.SectionKey), + builder.Environment.IsProduction()); builder.Services.AddSeeders(); builder.Services.AddIdentity(); @@ -153,6 +157,7 @@ builder.Services.AddCitizenReportsFeature(); builder.Services.AddCitizenReportsNotesFeature(); builder.Services.AddCitizenReportsAttachmentsFeature(); +builder.Services.AddCitizenReportsGuidesFeature(); builder.Services.AddLocationsFeature(builder.Configuration.GetSection(LocationsFeatureInstaller.SectionKey)); builder.Services.AddAuthorization(); @@ -177,37 +182,37 @@ app.UseSentryMiddleware() .UseFastEndpoints(x => -{ - x.Errors.UseProblemDetails(); - x.Endpoints.Configurator = ep => { - ep.PreProcessor(Order.Before); - }; + x.Errors.UseProblemDetails(); + x.Endpoints.Configurator = ep => { ep.PreProcessor(Order.Before); }; - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - - x.Serializer.Options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; -}); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + + x.Serializer.Options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + }); #region Register type handleers for Dapper + SqlMapper.AddTypeHandler(typeof(UserStatus), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(UserRole), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(NgoStatus), new SmartEnumByValueTypeHandler()); @@ -216,50 +221,56 @@ SqlMapper.AddTypeHandler(typeof(FormTemplateType), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(FormTemplateStatus), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(MonitoringNgoStatus), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(MonitoringObserverStatus), new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(MonitoringObserverStatus), + new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(RatingScale), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(FormType), new SmartEnumByValueTypeHandler()); SqlMapper.AddTypeHandler(typeof(ExportedDataStatus), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(QuickReportLocationType), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(DisplayLogicCondition), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(SubmissionFollowUpStatus), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(QuickReportFollowUpStatus), new SmartEnumByValueTypeHandler()); -SqlMapper.AddTypeHandler(typeof(CitizenReportFollowUpStatus), new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(QuickReportLocationType), + new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(DisplayLogicCondition), + new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(SubmissionFollowUpStatus), + new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(QuickReportFollowUpStatus), + new SmartEnumByValueTypeHandler()); +SqlMapper.AddTypeHandler(typeof(CitizenReportFollowUpStatus), + new SmartEnumByValueTypeHandler()); + SqlMapper.AddTypeHandler(typeof(TranslatedString), new JsonToObjectConverter()); + #endregion + app.UseSwaggerGen( -cfg => -{ - cfg.PostProcess = (document, _) => + cfg => { - var commitHash = Environment.GetEnvironmentVariable("COMMIT_HASH")?[..7] ?? "Unknown"; - - document.Info = new OpenApiInfo + cfg.PostProcess = (document, _) => { - Version = "v2.0", - Title = $"Vote Monitor API({commitHash})", - Description = "An ASP.NET Core Web API for monitoring elections.", - ExtensionData = new Dictionary - { - ["commit-hash"] = commitHash - }, - Contact = new OpenApiContact - { - Name = "CommitGlobal", - Url = "https://www.commitglobal.org/en/contact-us" - }, - License = new OpenApiLicense + var commitHash = Environment.GetEnvironmentVariable("COMMIT_HASH")?[..7] ?? "Unknown"; + + document.Info = new OpenApiInfo { - Name = "MPL-2.0 license", - Url = "https://github.com/commitglobal/votemonitor/blob/main/LICENSE" - } + Version = "v2.0", + Title = $"Vote Monitor API({commitHash})", + Description = "An ASP.NET Core Web API for monitoring elections.", + ExtensionData = new Dictionary + { + ["commit-hash"] = commitHash + }, + Contact = new OpenApiContact + { + Name = "CommitGlobal", + Url = "https://www.commitglobal.org/en/contact-us" + }, + License = new OpenApiLicense + { + Name = "MPL-2.0 license", + Url = "https://github.com/commitglobal/votemonitor/blob/main/LICENSE" + } + }; }; - }; -}, -uiConfig: cfg => -{ - cfg.DocExpansion = "list"; -}); + }, + uiConfig: cfg => { cfg.DocExpansion = "list"; }); app.UseResponseCompression(); app.MapHealthChecks("/health"); @@ -268,4 +279,4 @@ namespace Vote.Monitor.Api { public partial class Program; -} +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj index 233202bed..623b198e2 100644 --- a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj +++ b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj @@ -40,6 +40,7 @@ + diff --git a/api/src/Vote.Monitor.Core/Validators/FileSizeValidator.cs b/api/src/Vote.Monitor.Core/Validators/FileSizeValidator.cs index 20c5a990f..3c273fcec 100644 --- a/api/src/Vote.Monitor.Core/Validators/FileSizeValidator.cs +++ b/api/src/Vote.Monitor.Core/Validators/FileSizeValidator.cs @@ -15,7 +15,7 @@ public override bool IsValid(FluentValidation.ValidationContext context, IFor if (value is null) return true; if (value.Length <= MaxFileSizeInBytes) return true; context.MessageFormatter - .AppendArgument("MaxFilesize", MaxFileSizeInBytes.Megabytes()); + .AppendArgument("MaxFileSize", MaxFileSizeInBytes.Megabytes()); return false; } @@ -23,5 +23,5 @@ public override bool IsValid(FluentValidation.ValidationContext context, IFor public override string Name => "FileSizeValidator"; protected override string GetDefaultMessageTemplate(string errorCode) - => "Maximum file size is {MaxFilesize}."; -} + => "Maximum file size is {MaxFileSize}."; +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Core/Validators/FileSizeValidatorExtension.cs b/api/src/Vote.Monitor.Core/Validators/FileSizeValidatorExtension.cs index e6235f316..fbb26b343 100644 --- a/api/src/Vote.Monitor.Core/Validators/FileSizeValidatorExtension.cs +++ b/api/src/Vote.Monitor.Core/Validators/FileSizeValidatorExtension.cs @@ -7,4 +7,4 @@ public static class FileSizeValidatorExtension { public static IRuleBuilderOptions FileSmallerThan(this IRuleBuilder ruleBuilder, uint max) => ruleBuilder.SetValidator(new FileSizeValidator(max)); -} +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Core/Validators/UriValidator.cs b/api/src/Vote.Monitor.Core/Validators/UriValidator.cs new file mode 100644 index 000000000..1f870e88b --- /dev/null +++ b/api/src/Vote.Monitor.Core/Validators/UriValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation.Validators; + +namespace Vote.Monitor.Core.Validators; + +public class UriValidator : PropertyValidator +{ + public override bool IsValid(FluentValidation.ValidationContext context, string link) + { + if (string.IsNullOrWhiteSpace(link)) return false; + + //Courtesy of @Pure.Krome's comment and https://stackoverflow.com/a/25654227/563532 + return Uri.TryCreate(link, UriKind.Absolute, out var outUri) + && (outUri.Scheme == Uri.UriSchemeHttp || outUri.Scheme == Uri.UriSchemeHttps); + } + + public override string Name => "UriValidator"; + + protected override string GetDefaultMessageTemplate(string errorCode) + => "Provided string is not a valid URI"; +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Core/Validators/UriValidatorExtension.cs b/api/src/Vote.Monitor.Core/Validators/UriValidatorExtension.cs new file mode 100644 index 000000000..ac295de83 --- /dev/null +++ b/api/src/Vote.Monitor.Core/Validators/UriValidatorExtension.cs @@ -0,0 +1,9 @@ +using FluentValidation; + +namespace Vote.Monitor.Core.Validators; + +public static class UriValidatorExtension +{ + public static IRuleBuilderOptions IsValidUri(this IRuleBuilder ruleBuilder) + => ruleBuilder.SetValidator(new UriValidator()); +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs b/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs new file mode 100644 index 000000000..bae1bb13a --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs @@ -0,0 +1,99 @@ +namespace Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; + +public class CitizenReportGuide : AuditableBaseEntity, IAggregateRoot +{ + public Guid ElectionRoundId { get; private set; } + public virtual ElectionRound ElectionRound { get; private set; } + public string Title { get; private set; } + public string? FileName { get; private set; } + public string? UploadedFileName { get; private set; } + public string? FilePath { get; private set; } + public string? MimeType { get; private set; } + public string? WebsiteUrl { get; private set; } + public string? Text { get; private set; } + public CitizenReportGuideType GuideType { get; private set; } + public bool IsDeleted { get; private set; } + + private CitizenReportGuide(Guid electionRoundId, + string title, + string fileName, + string filePath, + string mimeType) : base(Guid.NewGuid()) + { + ElectionRoundId = electionRoundId; + Title = title; + FileName = fileName; + FilePath = filePath; + MimeType = mimeType; + IsDeleted = false; + + var extension = FileName.Split('.').Last(); + var uploadedFileName = $"{Id}.{extension}"; + UploadedFileName = uploadedFileName; + GuideType = CitizenReportGuideType.Document; + } + + private CitizenReportGuide(Guid electionRoundId, + string title, + Uri websiteUrl) : base(Guid.NewGuid()) + { + ElectionRoundId = electionRoundId; + Title = title; + WebsiteUrl = websiteUrl.ToString(); + IsDeleted = false; + GuideType = CitizenReportGuideType.Website; + } + + private CitizenReportGuide(Guid electionRoundId, + string title, + string text) : base(Guid.NewGuid()) + { + ElectionRoundId = electionRoundId; + Title = title; + Text = text; + IsDeleted = false; + GuideType = CitizenReportGuideType.Text; + } + + public static CitizenReportGuide NewDocumentGuide(Guid electionRoundId, + string title, + string fileName, + string filePath, + string mimeType) => new(electionRoundId, title, fileName, filePath, mimeType); + + public static CitizenReportGuide NewWebsiteGuide(Guid electionRoundId, + string title, + Uri websiteUrl) => new(electionRoundId, title, websiteUrl); + + public static CitizenReportGuide NewTextGuide(Guid electionRoundId, + string title, + string text) => new(electionRoundId, title, text); + + public void Delete() + { + IsDeleted = true; + } + + public void UpdateTitle(string title) + { + Title = title; + } + + public void UpdateWebsiteGuide(string title, Uri websiteUrl) + { + Title = title; + WebsiteUrl = websiteUrl.ToString(); + } + + public void UpdateTextGuide(string title, string text) + { + Title = title; + Text = text; + } + +#pragma warning disable CS8618 // Required by Entity Framework + internal CitizenReportGuide() + { + } +#pragma warning restore CS8618 +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs b/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs new file mode 100644 index 000000000..859ca5f01 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs @@ -0,0 +1,27 @@ +using Ardalis.SmartEnum; + +namespace Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +public sealed class CitizenReportGuideType : SmartEnum +{ + public static readonly CitizenReportGuideType Website = new(nameof(Website), nameof(Website)); + public static readonly CitizenReportGuideType Document = new(nameof(Document), nameof(Document)); + public static readonly CitizenReportGuideType Text = new(nameof(Text), nameof(Text)); + + /// 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 CitizenReportGuideType result) + { + return TryFromValue(value, out result); + } + + private CitizenReportGuideType(string name, string value) : base(name, value) + { + } +} diff --git a/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuide.cs b/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuide.cs index 9b151f220..1f02c7e92 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuide.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuide.cs @@ -12,6 +12,7 @@ public class ObserverGuide : AuditableBaseEntity, IAggregateRoot public string? FilePath { get; private set; } public string? MimeType { get; private set; } public string? WebsiteUrl { get; private set; } + public string? Text { get; private set; } public ObserverGuideType GuideType { get; private set; } public bool IsDeleted { get; private set; } @@ -34,25 +35,44 @@ private ObserverGuide(MonitoringNgo monitoringNgo, UploadedFileName = uploadedFileName; GuideType = ObserverGuideType.Document; } + private ObserverGuide(MonitoringNgo monitoringNgo, string title, - string websiteUrl) : base(Guid.NewGuid()) + Uri websiteUrl) : base(Guid.NewGuid()) { MonitoringNgo = monitoringNgo; MonitoringNgoId = monitoringNgo.Id; Title = title; - WebsiteUrl = websiteUrl; + WebsiteUrl = websiteUrl.ToString(); IsDeleted = false; GuideType = ObserverGuideType.Website; } - public static ObserverGuide CreateForDocument(MonitoringNgo monitoringNgo, + + private ObserverGuide(MonitoringNgo monitoringNgo, + string title, + string text) : base(Guid.NewGuid()) + { + MonitoringNgo = monitoringNgo; + MonitoringNgoId = monitoringNgo.Id; + Title = title; + Text = text; + IsDeleted = false; + GuideType = ObserverGuideType.Text; + } + + public static ObserverGuide NewDocumentGuide(MonitoringNgo monitoringNgo, string title, string fileName, string filePath, string mimeType) => new(monitoringNgo, title, fileName, filePath, mimeType); - public static ObserverGuide CreateForWebsite(MonitoringNgo monitoringNgo, + + public static ObserverGuide NewWebsiteGuide(MonitoringNgo monitoringNgo, string title, - string websiteUrl) => new(monitoringNgo, title, websiteUrl); + Uri websiteUrl) => new(monitoringNgo, title, websiteUrl); + + public static ObserverGuide NewTextGuide(MonitoringNgo monitoringNgo, + string title, + string text) => new(monitoringNgo, title, text); public void Delete() { @@ -64,10 +84,21 @@ public void UpdateTitle(string title) Title = title; } + public void UpdateWebsiteGuide(string title, Uri websiteUrl) + { + Title = title; + WebsiteUrl = websiteUrl.ToString(); + } + + public void UpdateTextGuide(string title, string text) + { + Title = title; + Text = text; + } + #pragma warning disable CS8618 // Required by Entity Framework internal ObserverGuide() { } #pragma warning restore CS8618 - -} +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuideType.cs b/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuideType.cs index 438843614..669e870ee 100644 --- a/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuideType.cs +++ b/api/src/Vote.Monitor.Domain/Entities/ObserverGuideAggregate/ObserverGuideType.cs @@ -5,6 +5,7 @@ public sealed class ObserverGuideType : SmartEnum { public static readonly ObserverGuideType Website = new(nameof(Website), nameof(Website)); public static readonly ObserverGuideType Document = new(nameof(Document), nameof(Document)); + public static readonly ObserverGuideType Text = new(nameof(Text), nameof(Text)); /// Gets an item associated with the specified value. Parses SmartEnum when used as query params /// this issue diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index fec2c66a5..85f3c35d1 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -4,6 +4,7 @@ using Vote.Monitor.Domain.Entities.AttachmentAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; using Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate; using Vote.Monitor.Domain.Entities.ExportedDataAggregate; using Vote.Monitor.Domain.Entities.FeedbackAggregate; @@ -73,6 +74,7 @@ public VoteMonitorContext(DbContextOptions options, public DbSet CitizenReportNotes { get; set; } public DbSet CitizenReportAttachments { get; set; } public DbSet MonitoringObserverNotification { get; set; } + public DbSet CitizenReportGuides { get; set; } public DbSet Locations { get; set; } diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj b/api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj new file mode 100644 index 000000000..bfcfa685b --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs new file mode 100644 index 000000000..6f09f6583 --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using ObserverGuideAggregate = Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide; diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs new file mode 100644 index 000000000..b5d82d4e3 --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs @@ -0,0 +1,158 @@ +using FluentValidation.TestHelper; +using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.TestUtils.Fakes; + +namespace Feature.CitizenReports.Guides.UnitTests.Validators; + +public class CreateValidatorTests +{ + private readonly Create.Validator _validator = new(); + + [Fact] + public void Should_Have_Error_When_ElectionRoundId_Is_Empty() + { + // Arrange + var model = new Create.Request { ElectionRoundId = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Should_Have_Error_When_Title_Is_Empty() + { + // Arrange + var model = new Create.Request { Title = string.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Title); + } + + [Fact] + public void Should_Have_Error_When_Title_Exceeds_Maximum_Length() + { + // Arrange + var model = new Create.Request { Title = new string('A', 257) }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Title); + } + + [Fact] + public void Should_Have_Error_When_Attachment_Is_Empty_For_Document_Type() + { + // Arrange + var model = new Create.Request { GuideType = CitizenReportGuideType.Document, Attachment = null }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Attachment); + } + + [Fact] + public void Should_Have_Error_When_Attachment_Exceeds_File_Size_Limit_For_Document_Type() + { + // Arrange + var model = new Create.Request + { + GuideType = CitizenReportGuideType.Document, + Attachment = FakeFormFile.New().HavingLength(51 * 1024 * 1024).Please() // 51 MB file + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Attachment); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Is_Empty_For_Website_Type() + { + // Arrange + var model = new Create.Request { GuideType = CitizenReportGuideType.Website, WebsiteUrl = string.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Exceeds_Maximum_Length_For_Website_Type() + { + // Arrange + var model = new Create.Request + { + GuideType = CitizenReportGuideType.Website, + WebsiteUrl = new string('A', 2049) + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Is_Invalid_For_Website_Type() + { + // Arrange + var model = new Create.Request + { + GuideType = CitizenReportGuideType.Website, + WebsiteUrl = "invalid-url" + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Is_Empty_For_Text_Type() + { + // Arrange + var model = new Create.Request { GuideType = CitizenReportGuideType.Text, WebsiteUrl = string.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Not_Have_Error_When_Valid_Request() + { + // Arrange + var model = new Create.Request + { + ElectionRoundId = Guid.NewGuid(), + Title = "Valid Title", + GuideType = CitizenReportGuideType.Document, + Attachment = FakeFormFile.New().Please() + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } +} \ No newline at end of file diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs new file mode 100644 index 000000000..d612084ed --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs @@ -0,0 +1,37 @@ +using FluentValidation.TestHelper; + +namespace Feature.CitizenReports.Guides.UnitTests.Validators; + +public class DeleteValidatorTests +{ + private readonly Delete.Validator _validator = new(); + + [Fact] + public void Should_Have_Error_When_ElectionRoundId_Is_Empty() + { + var model = new Delete.Request { ElectionRoundId = Guid.Empty }; + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Should_Have_Error_When_Id_Is_Empty() + { + var model = new Delete.Request { Id = Guid.Empty }; + var result = _validator.TestValidate(model); + result.ShouldHaveValidationErrorFor(x => x.Id); + } + + [Fact] + public void Should_Not_Have_Error_When_Valid_Request() + { + var model = new Delete.Request + { + ElectionRoundId = Guid.NewGuid(), + Id = Guid.NewGuid(), + }; + + var result = _validator.TestValidate(model); + result.ShouldNotHaveAnyValidationErrors(); + } +} \ No newline at end of file diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs new file mode 100644 index 000000000..577cc4550 --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs @@ -0,0 +1,38 @@ +using FluentValidation.TestHelper; + +namespace Feature.CitizenReports.Guides.UnitTests.Validators; + +public class ListValidatorTests +{ + private readonly List.Validator _validator = new(); + + [Fact] + public void Should_Have_Error_When_ElectionRoundId_Is_Empty() + { + // Arrange + var model = new List.Request { ElectionRoundId = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + + [Fact] + public void Should_Not_Have_Error_When_Valid_Request() + { + // Arrange + var model = new List.Request + { + ElectionRoundId = Guid.NewGuid() + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } +} \ No newline at end of file diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs new file mode 100644 index 000000000..53e2fa214 --- /dev/null +++ b/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs @@ -0,0 +1,106 @@ +using Xunit; +using FluentValidation.TestHelper; +using Vote.Monitor.TestUtils; + +namespace Feature.CitizenReports.Guides.UnitTests.Validators; + +public class UpdateValidatorTests +{ + private readonly Update.Validator _validator = new(); + + [Fact] + public void Should_Have_Error_When_ElectionRoundId_Is_Empty() + { + // Arrange + var model = new Update.Request { ElectionRoundId = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Theory] + [MemberData(nameof(TestData.EmptyStringsTestCases), MemberType = typeof(TestData))] + public void Should_Have_Error_When_Title_Is_Empty(string emptyTitle) + { + // Arrange + var model = new Update.Request { Title = emptyTitle }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Title); + } + + [Fact] + public void Should_Have_Error_When_Title_Exceeds_Maximum_Length() + { + // Arrange + var model = new Update.Request { Title = new string('A', 257) }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Title); + } + + [Theory] + [MemberData(nameof(TestData.EmptyStringsTestCases), MemberType = typeof(TestData))] + public void Should_Not_Have_Error_When_WebsiteUrl_Is_Empty_Or_Whitespace(string emptyUrl) + { + // Arrange + var model = new Update.Request { WebsiteUrl = emptyUrl }; + + // Act + var result3 = _validator.TestValidate(model); + result3.ShouldNotHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Exceeds_Maximum_Length() + { + // Arrange + var model = new Update.Request { WebsiteUrl = new string('A', 2049) }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Have_Error_When_WebsiteUrl_Is_Invalid() + { + // Arrange + var model = new Update.Request { WebsiteUrl = "invalid-url" }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + } + + [Fact] + public void Should_Not_Have_Error_When_Valid_Request() + { + // Arrange + var model = new Update.Request + { + ElectionRoundId = Guid.NewGuid(), + Title = "Valid Title", + WebsiteUrl = "https://validurl.com" + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } +} \ No newline at end of file From 8ba9f08c45de358ac3d7fb83ff9d450a2cc1112d Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Mon, 23 Sep 2024 17:18:26 +0300 Subject: [PATCH 2/7] rename api feature --- api/Vote.Monitor.sln | 4 +- .../CitizenGuideModel.cs} | 10 +- .../CitizenGuidesFeatureInstaller.cs | 11 + .../Feature.Citizen.Guides/Create/Endpoint.cs | 103 + .../Create/Request.cs | 6 +- .../Create/Validator.cs | 10 +- .../Feature.Citizen.Guides/Delete/Endpoint.cs | 36 + .../Delete/Request.cs | 2 +- .../Delete/Validator.cs | 2 +- .../EnableTesting.cs | 2 +- .../Feature.Citizen.Guides.csproj} | 0 .../GlobalUsings.cs | 2 +- .../List/Endpoint.cs | 24 +- .../List/Request.cs | 2 +- .../Feature.Citizen.Guides/List/Response.cs | 6 + .../List/Validator.cs | 2 +- .../GetCitizenGuideByIdSpecification.cs | 15 + .../Update/Endpoint.cs | 24 +- .../Update/Request.cs | 2 +- .../Update/Validator.cs | 2 +- .../CitizenReportsGuidesFeatureInstaller.cs | 11 - .../Create/Endpoint.cs | 103 - .../Delete/Endpoint.cs | 41 - .../List/Response.cs | 6 - .../GetObserverGuideByIdSpecification.cs | 15 - api/src/Vote.Monitor.Api/Dockerfile | 2 +- api/src/Vote.Monitor.Api/Program.cs | 8 +- .../Vote.Monitor.Api/Vote.Monitor.Api.csproj | 2 +- .../CitizenGuide.cs} | 26 +- .../CitizenGuideAggregate/CitizenGuideType.cs | 27 + .../CitizenReportGuideType.cs | 27 - .../CitizenReportsGuideConfiguration.cs | 43 + .../ObserverGuideConfiguration.cs | 5 +- ...3141550_AddCitizenReportsGuide.Designer.cs | 6421 +++++++++++++++++ .../20240923141550_AddCitizenReportsGuide.cs | 68 + .../VoteMonitorContextModelSnapshot.cs | 71 + .../Vote.Monitor.Domain/VoteMonitorContext.cs | 4 +- .../Feature.Citizen.Guides.UnitTests.csproj} | 2 +- .../GlobalUsings.cs | 6 + .../Validators/CreateValidatorTests.cs | 20 +- .../Validators/DeleteValidatorTests.cs | 4 +- .../Validators/ListValidatorTests.cs | 4 +- .../Validators/UpdateValidatorTests.cs | 6 +- .../GlobalUsings.cs | 1 - .../Endpoints/CreateEndpointTests.cs | 35 +- 45 files changed, 6914 insertions(+), 309 deletions(-) rename api/src/{Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs => Feature.Citizen.Guides/CitizenGuideModel.cs} (67%) create mode 100644 api/src/Feature.Citizen.Guides/CitizenGuidesFeatureInstaller.cs create mode 100644 api/src/Feature.Citizen.Guides/Create/Endpoint.cs rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Create/Request.cs (70%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Create/Validator.cs (63%) create mode 100644 api/src/Feature.Citizen.Guides/Delete/Endpoint.cs rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Delete/Request.cs (67%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Delete/Validator.cs (77%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/EnableTesting.cs (69%) rename api/src/{Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj => Feature.Citizen.Guides/Feature.Citizen.Guides.csproj} (100%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/GlobalUsings.cs (65%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/List/Endpoint.cs (83%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/List/Request.cs (59%) create mode 100644 api/src/Feature.Citizen.Guides/List/Response.cs rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/List/Validator.cs (73%) create mode 100644 api/src/Feature.Citizen.Guides/Specifications/GetCitizenGuideByIdSpecification.cs rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Update/Endpoint.cs (63%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Update/Request.cs (81%) rename api/src/{Feature.CitizenReports.Guides => Feature.Citizen.Guides}/Update/Validator.cs (89%) delete mode 100644 api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs delete mode 100644 api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs delete mode 100644 api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs delete mode 100644 api/src/Feature.CitizenReports.Guides/List/Response.cs delete mode 100644 api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs rename api/src/Vote.Monitor.Domain/Entities/{CitizenReportGuideAggregate/CitizenReportGuide.cs => CitizenGuideAggregate/CitizenGuide.cs} (74%) create mode 100644 api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuideType.cs delete mode 100644 api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs create mode 100644 api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.Designer.cs create mode 100644 api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.cs rename api/tests/{Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj => Feature.Citizen.Guides.UnitTests/Feature.Citizen.Guides.UnitTests.csproj} (92%) create mode 100644 api/tests/Feature.Citizen.Guides.UnitTests/GlobalUsings.cs rename api/tests/{Feature.CitizenReports.Guides.UnitTests => Feature.Citizen.Guides.UnitTests}/Validators/CreateValidatorTests.cs (82%) rename api/tests/{Feature.CitizenReports.Guides.UnitTests => Feature.Citizen.Guides.UnitTests}/Validators/DeleteValidatorTests.cs (90%) rename api/tests/{Feature.CitizenReports.Guides.UnitTests => Feature.Citizen.Guides.UnitTests}/Validators/ListValidatorTests.cs (89%) rename api/tests/{Feature.CitizenReports.Guides.UnitTests => Feature.Citizen.Guides.UnitTests}/Validators/UpdateValidatorTests.cs (95%) delete mode 100644 api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs diff --git a/api/Vote.Monitor.sln b/api/Vote.Monitor.sln index 01585e85f..13be6c8e4 100644 --- a/api/Vote.Monitor.sln +++ b/api/Vote.Monitor.sln @@ -150,9 +150,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations", "src\Fe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Locations.UnitTests", "tests\Feature.Locations.UnitTests\Feature.Locations.UnitTests.csproj", "{6DC3922B-5AC8-4968-AE5C-557315576720}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Guides", "src\Feature.CitizenReports.Guides\Feature.CitizenReports.Guides.csproj", "{A4842D5C-A8B6-4260-9071-A2431889EA24}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Citizen.Guides", "src\Feature.Citizen.Guides\Feature.Citizen.Guides.csproj", "{A4842D5C-A8B6-4260-9071-A2431889EA24}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.CitizenReports.Guides.UnitTests", "tests\Feature.CitizenReports.Guides.UnitTests\Feature.CitizenReports.Guides.UnitTests.csproj", "{4EFDD417-A568-4F39-BE44-0D97003EC42E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Feature.Citizen.Guides.UnitTests", "tests\Feature.Citizen.Guides.UnitTests\Feature.Citizen.Guides.UnitTests.csproj", "{4EFDD417-A568-4F39-BE44-0D97003EC42E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs b/api/src/Feature.Citizen.Guides/CitizenGuideModel.cs similarity index 67% rename from api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs rename to api/src/Feature.Citizen.Guides/CitizenGuideModel.cs index 47a0442d9..0b762f053 100644 --- a/api/src/Feature.CitizenReports.Guides/CitizenReportsGuideModel.cs +++ b/api/src/Feature.Citizen.Guides/CitizenGuideModel.cs @@ -1,10 +1,10 @@ using System.Text.Json.Serialization; using Ardalis.SmartEnum.SystemTextJson; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -namespace Feature.CitizenReports.Guides; +namespace Feature.Citizen.Guides; -public record CitizenReportsGuideModel +public record CitizenGuideModel { public required Guid Id { get; init; } public string Title { get; init; } = string.Empty; @@ -15,8 +15,8 @@ public record CitizenReportsGuideModel public string? WebsiteUrl { get; init; } public string? Text { get; init; } - [JsonConverter(typeof(SmartEnumNameConverter))] - public CitizenReportGuideType GuideType { get; init; } + [JsonConverter(typeof(SmartEnumNameConverter))] + public CitizenGuideType GuideType { get; init; } public DateTime CreatedOn { get; init; } public string CreatedBy { get; init; } diff --git a/api/src/Feature.Citizen.Guides/CitizenGuidesFeatureInstaller.cs b/api/src/Feature.Citizen.Guides/CitizenGuidesFeatureInstaller.cs new file mode 100644 index 000000000..0eff34241 --- /dev/null +++ b/api/src/Feature.Citizen.Guides/CitizenGuidesFeatureInstaller.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Feature.Citizen.Guides; + +public static class CitizenGuidesFeatureInstaller +{ + public static IServiceCollection AddCitizenGuidesFeature(this IServiceCollection services) + { + return services; + } +} diff --git a/api/src/Feature.Citizen.Guides/Create/Endpoint.cs b/api/src/Feature.Citizen.Guides/Create/Endpoint.cs new file mode 100644 index 000000000..d479c17f3 --- /dev/null +++ b/api/src/Feature.Citizen.Guides/Create/Endpoint.cs @@ -0,0 +1,103 @@ +using System.Net; +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Core.Services.FileStorage.Contracts; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; + +namespace Feature.Citizen.Guides.Create; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository repository, + IFileStorageService fileStorageService) + : Endpoint, NotFound, StatusCodeHttpResult>> +{ + public override void Configure() + { + Post("/api/election-rounds/{electionRoundId}/citizen-guides"); + DontAutoTag(); + Options(x => x.WithTags("citizen-guides")); + AllowFileUploads(); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound, StatusCodeHttpResult>> ExecuteAsync( + Request req, CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + CitizenGuideAggregate? citizenGuide = null; + CitizenGuideModel? citizenGuideModel = null; + if (req.GuideType == CitizenGuideType.Document) + { + var uploadPath = $"elections/{req.ElectionRoundId}/citizen-guides"; + + citizenGuide = CitizenGuide.NewDocumentGuide(req.ElectionRoundId, + req.Title, + req.Attachment!.FileName, + uploadPath, + req.Attachment.ContentType); + + var uploadResult = await fileStorageService.UploadFileAsync(uploadPath, + fileName: citizenGuide.UploadedFileName!, + req.Attachment.OpenReadStream(), + ct); + + if (uploadResult is UploadFileResult.Failed) + { + return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError); + } + + var result = uploadResult as UploadFileResult.Ok; + citizenGuideModel = new CitizenGuideModel + { + Title = citizenGuide.Title, + FileName = citizenGuide.FileName!, + PresignedUrl = result!.Url, + MimeType = citizenGuide.MimeType!, + UrlValidityInSeconds = result.UrlValidityInSeconds, + Id = citizenGuide.Id, + GuideType = citizenGuide.GuideType + }; + } + + if (req.GuideType == CitizenGuideType.Website) + { + citizenGuide = CitizenGuide.NewWebsiteGuide(req.ElectionRoundId, + req.Title, + new Uri(req.WebsiteUrl!)); + + citizenGuideModel = new CitizenGuideModel + { + Id = citizenGuide.Id, + Title = citizenGuide.Title, + WebsiteUrl = citizenGuide.WebsiteUrl, + GuideType = citizenGuide.GuideType + }; + } + + if (req.GuideType == CitizenGuideType.Text) + { + citizenGuide = CitizenGuide.NewTextGuide(req.ElectionRoundId, + req.Title, + req.Text!); + + citizenGuideModel = new CitizenGuideModel + { + Id = citizenGuide.Id, + Title = citizenGuide.Title, + Text = citizenGuide.Text, + GuideType = citizenGuide.GuideType + }; + } + + await repository.AddAsync(citizenGuide!, ct); + return TypedResults.Ok(citizenGuideModel!); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Create/Request.cs b/api/src/Feature.Citizen.Guides/Create/Request.cs similarity index 70% rename from api/src/Feature.CitizenReports.Guides/Create/Request.cs rename to api/src/Feature.Citizen.Guides/Create/Request.cs index 30cecb0d8..29fba5be7 100644 --- a/api/src/Feature.CitizenReports.Guides/Create/Request.cs +++ b/api/src/Feature.Citizen.Guides/Create/Request.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Vote.Monitor.Core.Security; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -namespace Feature.CitizenReports.Guides.Create; +namespace Feature.Citizen.Guides.Create; public class Request { @@ -12,7 +12,7 @@ public class Request public Guid NgoId { get; set; } public string Title { get; set; } - public CitizenReportGuideType GuideType { get; set; } + public CitizenGuideType GuideType { get; set; } [FromForm] public IFormFile? Attachment { get; set; } diff --git a/api/src/Feature.CitizenReports.Guides/Create/Validator.cs b/api/src/Feature.Citizen.Guides/Create/Validator.cs similarity index 63% rename from api/src/Feature.CitizenReports.Guides/Create/Validator.cs rename to api/src/Feature.Citizen.Guides/Create/Validator.cs index bd870ecee..dfb561f0c 100644 --- a/api/src/Feature.CitizenReports.Guides/Create/Validator.cs +++ b/api/src/Feature.Citizen.Guides/Create/Validator.cs @@ -1,7 +1,7 @@ using Vote.Monitor.Core.Validators; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -namespace Feature.CitizenReports.Guides.Create; +namespace Feature.Citizen.Guides.Create; public class Validator : Validator { @@ -13,16 +13,16 @@ public Validator() RuleFor(x => x.Attachment) .NotEmpty()! .FileSmallerThan(50 * 1024 * 1024) // 50 MB upload limit - .When(x => x.GuideType == CitizenReportGuideType.Document); + .When(x => x.GuideType == CitizenGuideType.Document); RuleFor(x => x.WebsiteUrl) .NotEmpty()! .MaximumLength(2048) .IsValidUri() - .When(x => x.GuideType == CitizenReportGuideType.Website); + .When(x => x.GuideType == CitizenGuideType.Website); RuleFor(x => x.WebsiteUrl) .NotEmpty()! - .When(x => x.GuideType == CitizenReportGuideType.Text); + .When(x => x.GuideType == CitizenGuideType.Text); } } \ No newline at end of file diff --git a/api/src/Feature.Citizen.Guides/Delete/Endpoint.cs b/api/src/Feature.Citizen.Guides/Delete/Endpoint.cs new file mode 100644 index 000000000..9e63dd823 --- /dev/null +++ b/api/src/Feature.Citizen.Guides/Delete/Endpoint.cs @@ -0,0 +1,36 @@ +using Authorization.Policies.Requirements; +using Feature.Citizen.Guides.Specifications; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Domain; + +namespace Feature.Citizen.Guides.Delete; + +public class Endpoint( + IAuthorizationService authorizationService, + VoteMonitorContext context) + : Endpoint> +{ + public override void Configure() + { + Delete("/api/election-rounds/{electionRoundId}/citizen-guides/{id}"); + DontAutoTag(); + Options(x => x.WithTags("citizen-guides")); + } + + public override async Task> ExecuteAsync(Request req, CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + await context + .CitizenGuides + .ExecuteUpdateAsync(x => x.SetProperty(g => g.IsDeleted, true), ct); + + return TypedResults.NoContent(); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Request.cs b/api/src/Feature.Citizen.Guides/Delete/Request.cs similarity index 67% rename from api/src/Feature.CitizenReports.Guides/Delete/Request.cs rename to api/src/Feature.Citizen.Guides/Delete/Request.cs index f4510bb43..8e61d1418 100644 --- a/api/src/Feature.CitizenReports.Guides/Delete/Request.cs +++ b/api/src/Feature.Citizen.Guides/Delete/Request.cs @@ -1,4 +1,4 @@ -namespace Feature.CitizenReports.Guides.Delete; +namespace Feature.Citizen.Guides.Delete; public class Request { diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Validator.cs b/api/src/Feature.Citizen.Guides/Delete/Validator.cs similarity index 77% rename from api/src/Feature.CitizenReports.Guides/Delete/Validator.cs rename to api/src/Feature.Citizen.Guides/Delete/Validator.cs index 1a0e53e6a..bc461bd4a 100644 --- a/api/src/Feature.CitizenReports.Guides/Delete/Validator.cs +++ b/api/src/Feature.Citizen.Guides/Delete/Validator.cs @@ -1,4 +1,4 @@ -namespace Feature.CitizenReports.Guides.Delete; +namespace Feature.Citizen.Guides.Delete; public class Validator : Validator { diff --git a/api/src/Feature.CitizenReports.Guides/EnableTesting.cs b/api/src/Feature.Citizen.Guides/EnableTesting.cs similarity index 69% rename from api/src/Feature.CitizenReports.Guides/EnableTesting.cs rename to api/src/Feature.Citizen.Guides/EnableTesting.cs index 4daf42299..bdfbc5eb9 100644 --- a/api/src/Feature.CitizenReports.Guides/EnableTesting.cs +++ b/api/src/Feature.Citizen.Guides/EnableTesting.cs @@ -1,5 +1,5 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Feature.CitizenReports.Guides.UnitTests")] +[assembly: InternalsVisibleTo("Feature.Citizen.Guides.UnitTests")] [assembly: InternalsVisibleTo("Vote.Monitor.Api.IntegrationTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj b/api/src/Feature.Citizen.Guides/Feature.Citizen.Guides.csproj similarity index 100% rename from api/src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj rename to api/src/Feature.Citizen.Guides/Feature.Citizen.Guides.csproj diff --git a/api/src/Feature.CitizenReports.Guides/GlobalUsings.cs b/api/src/Feature.Citizen.Guides/GlobalUsings.cs similarity index 65% rename from api/src/Feature.CitizenReports.Guides/GlobalUsings.cs rename to api/src/Feature.Citizen.Guides/GlobalUsings.cs index c1d392e54..13ce63ed0 100644 --- a/api/src/Feature.CitizenReports.Guides/GlobalUsings.cs +++ b/api/src/Feature.Citizen.Guides/GlobalUsings.cs @@ -5,4 +5,4 @@ global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Http.HttpResults; global using Vote.Monitor.Domain.Repository; -global using CitizenReportGuideAggregate = Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate.CitizenReportGuide; +global using CitizenGuideAggregate = Vote.Monitor.Domain.Entities.CitizenGuideAggregate.CitizenGuide; diff --git a/api/src/Feature.CitizenReports.Guides/List/Endpoint.cs b/api/src/Feature.Citizen.Guides/List/Endpoint.cs similarity index 83% rename from api/src/Feature.CitizenReports.Guides/List/Endpoint.cs rename to api/src/Feature.Citizen.Guides/List/Endpoint.cs index 712362109..48fc0e4fa 100644 --- a/api/src/Feature.CitizenReports.Guides/List/Endpoint.cs +++ b/api/src/Feature.Citizen.Guides/List/Endpoint.cs @@ -4,9 +4,9 @@ using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Core.Services.Security; using Vote.Monitor.Domain; -using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -namespace Feature.CitizenReports.Guides.List; +namespace Feature.Citizen.Guides.List; public class Endpoint(IAuthorizationService authorizationService, ICurrentUserRoleProvider currentUserRoleProvider, @@ -16,9 +16,9 @@ public class Endpoint(IAuthorizationService authorizationService, { public override void Configure() { - Get("/api/election-rounds/{electionRoundId}/citizen-reports-guides"); + Get("/api/election-rounds/{electionRoundId}/citizen-guides"); DontAutoTag(); - Options(x => x.WithTags("citizen-reports-guides")); + Options(x => x.WithTags("citizen-guides")); } public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) @@ -35,8 +35,8 @@ public override async Task, NotFound>> ExecuteAsync(Request } // ReSharper disable once EntityFramework.NPlusOne.IncompleteDataQuery - var citizenReportGuides = await context - .CitizenReportGuides + var guides = await context + .CitizenGuides .Where(x => x.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted) .OrderByDescending(x=>x.CreatedOn) .Join(context.Users, guide=>guide.CreatedBy, user => user.Id, (guide, ngoAdmin) => new @@ -56,16 +56,16 @@ public override async Task, NotFound>> ExecuteAsync(Request .AsNoTracking() .ToListAsync(ct); - var tasks = citizenReportGuides + var tasks = guides .Select(async guide => { - if (guide.GuideType == ObserverGuideType.Document) + if (guide.GuideType == CitizenGuideType.Document) { var presignedUrl = await fileStorageService.GetPresignedUrlAsync( guide.FilePath!, guide.UploadedFileName!); - return new CitizenReportsGuideModel + return new CitizenGuideModel { Id = guide.Id, Title = guide.Title, @@ -79,7 +79,7 @@ public override async Task, NotFound>> ExecuteAsync(Request }; } - return new CitizenReportsGuideModel + return new CitizenGuideModel { Id = guide.Id, Title = guide.Title, @@ -93,8 +93,8 @@ public override async Task, NotFound>> ExecuteAsync(Request }; }); - var guides = await Task.WhenAll(tasks); + var mappedGuides = await Task.WhenAll(tasks); - return TypedResults.Ok(new Response { Guides = guides.ToList() }); + return TypedResults.Ok(new Response { Guides = mappedGuides.ToList() }); } } diff --git a/api/src/Feature.CitizenReports.Guides/List/Request.cs b/api/src/Feature.Citizen.Guides/List/Request.cs similarity index 59% rename from api/src/Feature.CitizenReports.Guides/List/Request.cs rename to api/src/Feature.Citizen.Guides/List/Request.cs index bf30af95a..e1a2dac79 100644 --- a/api/src/Feature.CitizenReports.Guides/List/Request.cs +++ b/api/src/Feature.Citizen.Guides/List/Request.cs @@ -1,4 +1,4 @@ -namespace Feature.CitizenReports.Guides.List; +namespace Feature.Citizen.Guides.List; public class Request { diff --git a/api/src/Feature.Citizen.Guides/List/Response.cs b/api/src/Feature.Citizen.Guides/List/Response.cs new file mode 100644 index 000000000..337ca1cfd --- /dev/null +++ b/api/src/Feature.Citizen.Guides/List/Response.cs @@ -0,0 +1,6 @@ +namespace Feature.Citizen.Guides.List; + +public record Response +{ + public required List Guides { get; set; } +} diff --git a/api/src/Feature.CitizenReports.Guides/List/Validator.cs b/api/src/Feature.Citizen.Guides/List/Validator.cs similarity index 73% rename from api/src/Feature.CitizenReports.Guides/List/Validator.cs rename to api/src/Feature.Citizen.Guides/List/Validator.cs index 3495afb71..59bb91add 100644 --- a/api/src/Feature.CitizenReports.Guides/List/Validator.cs +++ b/api/src/Feature.Citizen.Guides/List/Validator.cs @@ -1,4 +1,4 @@ -namespace Feature.CitizenReports.Guides.List; +namespace Feature.Citizen.Guides.List; public class Validator : Validator { diff --git a/api/src/Feature.Citizen.Guides/Specifications/GetCitizenGuideByIdSpecification.cs b/api/src/Feature.Citizen.Guides/Specifications/GetCitizenGuideByIdSpecification.cs new file mode 100644 index 000000000..c09724862 --- /dev/null +++ b/api/src/Feature.Citizen.Guides/Specifications/GetCitizenGuideByIdSpecification.cs @@ -0,0 +1,15 @@ +using Ardalis.Specification; + +namespace Feature.Citizen.Guides.Specifications; + +public sealed class GetCitizenGuideByIdSpecification : SingleResultSpecification +{ + public GetCitizenGuideByIdSpecification(Guid electionRoundId, Guid id) + { + Query + .Where(x => + x.ElectionRoundId == electionRoundId + && x.Id == id + && !x.IsDeleted); + } +} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs b/api/src/Feature.Citizen.Guides/Update/Endpoint.cs similarity index 63% rename from api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs rename to api/src/Feature.Citizen.Guides/Update/Endpoint.cs index 26945195e..3d16bcab8 100644 --- a/api/src/Feature.CitizenReports.Guides/Update/Endpoint.cs +++ b/api/src/Feature.Citizen.Guides/Update/Endpoint.cs @@ -1,25 +1,25 @@ using Authorization.Policies; using Authorization.Policies.Requirements; -using Feature.CitizenReports.Guides.Specifications; +using Feature.Citizen.Guides.Specifications; using Microsoft.AspNetCore.Authorization; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -namespace Feature.CitizenReports.Guides.Update; +namespace Feature.Citizen.Guides.Update; public class Endpoint( IAuthorizationService authorizationService, - IRepository repository) - : Endpoint, NotFound, NoContent>> + IRepository repository) + : Endpoint, NotFound, NoContent>> { public override void Configure() { - Put("/api/election-rounds/{electionRoundId}/citizen-reports-guides/{id}"); + Put("/api/election-rounds/{electionRoundId}/citizen-guides/{id}"); DontAutoTag(); - Options(x => x.WithTags("citizen-reports-guides")); + Options(x => x.WithTags("citizen-guides")); Policies(PolicyNames.NgoAdminsOnly); } - public override async Task, NotFound, NoContent>> ExecuteAsync(Request req, + public override async Task, NotFound, NoContent>> ExecuteAsync(Request req, CancellationToken ct) { var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); @@ -29,7 +29,7 @@ public override async Task, NotFound, NoCon return TypedResults.NotFound(); } - var specification = new GetObserverGuideByIdSpecification(req.ElectionRoundId, req.Id); + var specification = new GetCitizenGuideByIdSpecification(req.ElectionRoundId, req.Id); var guide = await repository.FirstOrDefaultAsync(specification, ct); if (guide is null) @@ -37,12 +37,12 @@ public override async Task, NotFound, NoCon return TypedResults.NotFound(); } - if (guide.GuideType == CitizenReportGuideType.Document) + if (guide.GuideType == CitizenGuideType.Document) { guide.UpdateTitle(req.Title); } - if (guide.GuideType == CitizenReportGuideType.Text) + if (guide.GuideType == CitizenGuideType.Text) { if (string.IsNullOrWhiteSpace(req.Text)) { @@ -52,7 +52,7 @@ public override async Task, NotFound, NoCon guide.UpdateTextGuide(req.Title, req.Text); } - if (guide.GuideType == CitizenReportGuideType.Website) + if (guide.GuideType == CitizenGuideType.Website) { if (string.IsNullOrWhiteSpace(req.WebsiteUrl)) { diff --git a/api/src/Feature.CitizenReports.Guides/Update/Request.cs b/api/src/Feature.Citizen.Guides/Update/Request.cs similarity index 81% rename from api/src/Feature.CitizenReports.Guides/Update/Request.cs rename to api/src/Feature.Citizen.Guides/Update/Request.cs index d690ee10a..daf7a61a9 100644 --- a/api/src/Feature.CitizenReports.Guides/Update/Request.cs +++ b/api/src/Feature.Citizen.Guides/Update/Request.cs @@ -1,4 +1,4 @@ -namespace Feature.CitizenReports.Guides.Update; +namespace Feature.Citizen.Guides.Update; public class Request { diff --git a/api/src/Feature.CitizenReports.Guides/Update/Validator.cs b/api/src/Feature.Citizen.Guides/Update/Validator.cs similarity index 89% rename from api/src/Feature.CitizenReports.Guides/Update/Validator.cs rename to api/src/Feature.Citizen.Guides/Update/Validator.cs index 79ea728ca..4339d66bb 100644 --- a/api/src/Feature.CitizenReports.Guides/Update/Validator.cs +++ b/api/src/Feature.Citizen.Guides/Update/Validator.cs @@ -1,6 +1,6 @@ using Vote.Monitor.Core.Validators; -namespace Feature.CitizenReports.Guides.Update; +namespace Feature.Citizen.Guides.Update; public class Validator : Validator { diff --git a/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs b/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs deleted file mode 100644 index 1736e0f04..000000000 --- a/api/src/Feature.CitizenReports.Guides/CitizenReportsGuidesFeatureInstaller.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Feature.CitizenReports.Guides; - -public static class CitizenReportsGuidesFeatureInstaller -{ - public static IServiceCollection AddCitizenReportsGuidesFeature(this IServiceCollection services) - { - return services; - } -} diff --git a/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs deleted file mode 100644 index 657ebb54c..000000000 --- a/api/src/Feature.CitizenReports.Guides/Create/Endpoint.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Net; -using Authorization.Policies; -using Authorization.Policies.Requirements; -using Microsoft.AspNetCore.Authorization; -using Vote.Monitor.Core.Services.FileStorage.Contracts; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; - -namespace Feature.CitizenReports.Guides.Create; - -public class Endpoint( - IAuthorizationService authorizationService, - IRepository repository, - IFileStorageService fileStorageService) - : Endpoint, NotFound, StatusCodeHttpResult>> -{ - public override void Configure() - { - Post("/api/election-rounds/{electionRoundId}/citizen-reports-guides"); - DontAutoTag(); - Options(x => x.WithTags("citizen-reports-guides")); - AllowFileUploads(); - Policies(PolicyNames.NgoAdminsOnly); - } - - public override async Task, NotFound, StatusCodeHttpResult>> ExecuteAsync( - Request req, CancellationToken ct) - { - var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); - var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); - if (!authorizationResult.Succeeded) - { - return TypedResults.NotFound(); - } - - CitizenReportGuideAggregate? observerGuide = null; - CitizenReportsGuideModel? observerGuideModel = null; - if (req.GuideType == CitizenReportGuideType.Document) - { - var uploadPath = $"elections/{req.ElectionRoundId}/citizen-reports-guides"; - - observerGuide = CitizenReportGuideAggregate.NewDocumentGuide(req.ElectionRoundId, - req.Title, - req.Attachment!.FileName, - uploadPath, - req.Attachment.ContentType); - - var uploadResult = await fileStorageService.UploadFileAsync(uploadPath, - fileName: observerGuide.UploadedFileName!, - req.Attachment.OpenReadStream(), - ct); - - if (uploadResult is UploadFileResult.Failed) - { - return TypedResults.StatusCode((int)HttpStatusCode.InternalServerError); - } - - var result = uploadResult as UploadFileResult.Ok; - observerGuideModel = new CitizenReportsGuideModel - { - Title = observerGuide.Title, - FileName = observerGuide.FileName!, - PresignedUrl = result!.Url, - MimeType = observerGuide.MimeType!, - UrlValidityInSeconds = result.UrlValidityInSeconds, - Id = observerGuide.Id, - GuideType = observerGuide.GuideType - }; - } - - if (req.GuideType == CitizenReportGuideType.Website) - { - observerGuide = CitizenReportGuideAggregate.NewWebsiteGuide(req.ElectionRoundId, - req.Title, - new Uri(req.WebsiteUrl!)); - - observerGuideModel = new CitizenReportsGuideModel - { - Id = observerGuide.Id, - Title = observerGuide.Title, - WebsiteUrl = observerGuide.WebsiteUrl, - GuideType = observerGuide.GuideType - }; - } - - if (req.GuideType == CitizenReportGuideType.Text) - { - observerGuide = CitizenReportGuideAggregate.NewTextGuide(req.ElectionRoundId, - req.Title, - req.Text!); - - observerGuideModel = new CitizenReportsGuideModel - { - Id = observerGuide.Id, - Title = observerGuide.Title, - Text = observerGuide.Text, - GuideType = observerGuide.GuideType - }; - } - - await repository.AddAsync(observerGuide!, ct); - return TypedResults.Ok(observerGuideModel!); - } -} \ No newline at end of file diff --git a/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs b/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs deleted file mode 100644 index eb5849766..000000000 --- a/api/src/Feature.CitizenReports.Guides/Delete/Endpoint.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Authorization.Policies.Requirements; -using Feature.CitizenReports.Guides.Specifications; -using Microsoft.AspNetCore.Authorization; - -namespace Feature.CitizenReports.Guides.Delete; - -public class Endpoint(IAuthorizationService authorizationService, - IRepository repository) - : Endpoint> -{ - public override void Configure() - { - Delete("/api/election-rounds/{electionRoundId}/citizen-reports-guides/{id}"); - DontAutoTag(); - Options(x => x.WithTags("citizen-reports-guides")); - } - - public override async Task> ExecuteAsync(Request req, CancellationToken ct) - { - var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); - var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); - if (!authorizationResult.Succeeded) - { - return TypedResults.NotFound(); - } - - var specification = new GetObserverGuideByIdSpecification(req.ElectionRoundId, req.Id); - var observerGuide = await repository.FirstOrDefaultAsync(specification, ct); - - if (observerGuide == null) - { - return TypedResults.NotFound(); - } - - observerGuide.Delete(); - - await repository.UpdateAsync(observerGuide, ct); - - return TypedResults.NoContent(); - } -} diff --git a/api/src/Feature.CitizenReports.Guides/List/Response.cs b/api/src/Feature.CitizenReports.Guides/List/Response.cs deleted file mode 100644 index 37e38955d..000000000 --- a/api/src/Feature.CitizenReports.Guides/List/Response.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Feature.CitizenReports.Guides.List; - -public record Response -{ - public required List Guides { get; set; } -} diff --git a/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs b/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs deleted file mode 100644 index a9c4df216..000000000 --- a/api/src/Feature.CitizenReports.Guides/Specifications/GetObserverGuideByIdSpecification.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Ardalis.Specification; - -namespace Feature.CitizenReports.Guides.Specifications; - -public sealed class GetObserverGuideByIdSpecification : SingleResultSpecification -{ - public GetObserverGuideByIdSpecification(Guid electionRoundId, Guid id) - { - Query - .Where(x => - x.ElectionRoundId == electionRoundId - && x.Id == id - && !x.IsDeleted); - } -} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Api/Dockerfile b/api/src/Vote.Monitor.Api/Dockerfile index 4ea334d43..75b2a9b1a 100644 --- a/api/src/Vote.Monitor.Api/Dockerfile +++ b/api/src/Vote.Monitor.Api/Dockerfile @@ -45,7 +45,7 @@ COPY ["src/Feature.Notes/Feature.Notes.csproj", "src/Feature.Notes/"] COPY ["src/Vote.Monitor.Api.Feature.UserPreferences/Vote.Monitor.Api.Feature.UserPreferences.csproj", "src/Vote.Monitor.Api.Feature.UserPreferences/"] COPY ["src/Vote.Monitor.Api.Feature.PollingStation/Vote.Monitor.Api.Feature.PollingStation.csproj", "src/Vote.Monitor.Api.Feature.PollingStation/"] COPY ["src/Feature.Locations/Feature.Locations.csproj", "src/Feature.Locations/"] -COPY ["src/Feature.CitizenReports.Guides/Feature.CitizenReports.Guides.csproj", "src/Feature.CitizenReports.Guides/"] +COPY ["src/Feature.Citizen.Guides/Feature.Citizen.Guides.csproj", "src/Feature.Citizen.Guides/"] RUN dotnet restore "src/Vote.Monitor.Api/Vote.Monitor.Api.csproj" COPY . . diff --git a/api/src/Vote.Monitor.Api/Program.cs b/api/src/Vote.Monitor.Api/Program.cs index a482ad724..2594d2813 100644 --- a/api/src/Vote.Monitor.Api/Program.cs +++ b/api/src/Vote.Monitor.Api/Program.cs @@ -34,7 +34,7 @@ using Dapper; using Feature.CitizenReports; using Feature.CitizenReports.Attachments; -using Feature.CitizenReports.Guides; +using Feature.Citizen.Guides; using Feature.CitizenReports.Notes; using Feature.DataExport; using Feature.Feedback; @@ -45,8 +45,8 @@ using Vote.Monitor.Domain.Entities.FormSubmissionAggregate; using Microsoft.AspNetCore.Http.Features; using Vote.Monitor.Core.Converters; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; var builder = WebApplication.CreateBuilder(args); @@ -157,7 +157,7 @@ builder.Services.AddCitizenReportsFeature(); builder.Services.AddCitizenReportsNotesFeature(); builder.Services.AddCitizenReportsAttachmentsFeature(); -builder.Services.AddCitizenReportsGuidesFeature(); +builder.Services.AddCitizenGuidesFeature(); builder.Services.AddLocationsFeature(builder.Configuration.GetSection(LocationsFeatureInstaller.SectionKey)); builder.Services.AddAuthorization(); @@ -206,7 +206,7 @@ x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); - x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); + x.Serializer.Options.Converters.Add(new SmartEnumValueConverter()); x.Serializer.Options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); diff --git a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj index 623b198e2..16d5bac90 100644 --- a/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj +++ b/api/src/Vote.Monitor.Api/Vote.Monitor.Api.csproj @@ -40,7 +40,7 @@ - + diff --git a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs b/api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuide.cs similarity index 74% rename from api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs rename to api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuide.cs index bae1bb13a..7ace72490 100644 --- a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuide.cs +++ b/api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuide.cs @@ -1,6 +1,6 @@ -namespace Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; +namespace Vote.Monitor.Domain.Entities.CitizenGuideAggregate; -public class CitizenReportGuide : AuditableBaseEntity, IAggregateRoot +public class CitizenGuide : AuditableBaseEntity, IAggregateRoot { public Guid ElectionRoundId { get; private set; } public virtual ElectionRound ElectionRound { get; private set; } @@ -11,10 +11,10 @@ public class CitizenReportGuide : AuditableBaseEntity, IAggregateRoot public string? MimeType { get; private set; } public string? WebsiteUrl { get; private set; } public string? Text { get; private set; } - public CitizenReportGuideType GuideType { get; private set; } + public CitizenGuideType GuideType { get; private set; } public bool IsDeleted { get; private set; } - private CitizenReportGuide(Guid electionRoundId, + private CitizenGuide(Guid electionRoundId, string title, string fileName, string filePath, @@ -30,10 +30,10 @@ private CitizenReportGuide(Guid electionRoundId, var extension = FileName.Split('.').Last(); var uploadedFileName = $"{Id}.{extension}"; UploadedFileName = uploadedFileName; - GuideType = CitizenReportGuideType.Document; + GuideType = CitizenGuideType.Document; } - private CitizenReportGuide(Guid electionRoundId, + private CitizenGuide(Guid electionRoundId, string title, Uri websiteUrl) : base(Guid.NewGuid()) { @@ -41,10 +41,10 @@ private CitizenReportGuide(Guid electionRoundId, Title = title; WebsiteUrl = websiteUrl.ToString(); IsDeleted = false; - GuideType = CitizenReportGuideType.Website; + GuideType = CitizenGuideType.Website; } - private CitizenReportGuide(Guid electionRoundId, + private CitizenGuide(Guid electionRoundId, string title, string text) : base(Guid.NewGuid()) { @@ -52,20 +52,20 @@ private CitizenReportGuide(Guid electionRoundId, Title = title; Text = text; IsDeleted = false; - GuideType = CitizenReportGuideType.Text; + GuideType = CitizenGuideType.Text; } - public static CitizenReportGuide NewDocumentGuide(Guid electionRoundId, + public static CitizenGuide NewDocumentGuide(Guid electionRoundId, string title, string fileName, string filePath, string mimeType) => new(electionRoundId, title, fileName, filePath, mimeType); - public static CitizenReportGuide NewWebsiteGuide(Guid electionRoundId, + public static CitizenGuide NewWebsiteGuide(Guid electionRoundId, string title, Uri websiteUrl) => new(electionRoundId, title, websiteUrl); - public static CitizenReportGuide NewTextGuide(Guid electionRoundId, + public static CitizenGuide NewTextGuide(Guid electionRoundId, string title, string text) => new(electionRoundId, title, text); @@ -92,7 +92,7 @@ public void UpdateTextGuide(string title, string text) } #pragma warning disable CS8618 // Required by Entity Framework - internal CitizenReportGuide() + internal CitizenGuide() { } #pragma warning restore CS8618 diff --git a/api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuideType.cs b/api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuideType.cs new file mode 100644 index 000000000..374ef37f5 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Entities/CitizenGuideAggregate/CitizenGuideType.cs @@ -0,0 +1,27 @@ +using Ardalis.SmartEnum; + +namespace Vote.Monitor.Domain.Entities.CitizenGuideAggregate; +public sealed class CitizenGuideType : SmartEnum +{ + public static readonly CitizenGuideType Website = new(nameof(Website), nameof(Website)); + public static readonly CitizenGuideType Document = new(nameof(Document), nameof(Document)); + public static readonly CitizenGuideType Text = new(nameof(Text), nameof(Text)); + + /// 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 CitizenGuideType result) + { + return TryFromValue(value, out result); + } + + private CitizenGuideType(string name, string value) : base(name, value) + { + } +} diff --git a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs b/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs deleted file mode 100644 index 859ca5f01..000000000 --- a/api/src/Vote.Monitor.Domain/Entities/CitizenReportGuideAggregate/CitizenReportGuideType.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ardalis.SmartEnum; - -namespace Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; -public sealed class CitizenReportGuideType : SmartEnum -{ - public static readonly CitizenReportGuideType Website = new(nameof(Website), nameof(Website)); - public static readonly CitizenReportGuideType Document = new(nameof(Document), nameof(Document)); - public static readonly CitizenReportGuideType Text = new(nameof(Text), nameof(Text)); - - /// 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 CitizenReportGuideType result) - { - return TryFromValue(value, out result); - } - - private CitizenReportGuideType(string name, string value) : base(name, value) - { - } -} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs new file mode 100644 index 000000000..ea032c227 --- /dev/null +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; + +namespace Vote.Monitor.Domain.EntitiesConfiguration; + +internal class CitizenReportsGuideConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).IsRequired(); + + builder.Property(x => x.FileName) + .HasMaxLength(256); + + builder.Property(x => x.UploadedFileName) + .HasMaxLength(256); + + builder.Property(x => x.FilePath) + .HasMaxLength(256); + + builder.Property(x => x.MimeType) + .HasMaxLength(256); + + builder.Property(x => x.IsDeleted); + + builder.Property(x => x.Title) + .HasMaxLength(256) + .IsRequired(); + + builder + .Property(x => x.WebsiteUrl) + .HasMaxLength(2048); + + builder + .Property(x => x.Text); + + builder.HasOne(x => x.ElectionRound) + .WithMany() + .HasForeignKey(x => x.ElectionRoundId); + } +} diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ObserverGuideConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ObserverGuideConfiguration.cs index c05dcc95a..ca3e1a031 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ObserverGuideConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/ObserverGuideConfiguration.cs @@ -32,8 +32,11 @@ public void Configure(EntityTypeBuilder builder) .Property(x => x.WebsiteUrl) .HasMaxLength(2048); + builder + .Property(x => x.Text); + builder.HasOne(x => x.MonitoringNgo) .WithMany() .HasForeignKey(x => x.MonitoringNgoId); } -} +} \ No newline at end of file diff --git a/api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.Designer.cs b/api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.Designer.cs new file mode 100644 index 000000000..a52b0d78b --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.Designer.cs @@ -0,0 +1,6421 @@ +// +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("20240923141550_AddCitizenReportsGuide")] + partial class AddCitizenReportsGuide + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .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") + .HasColumnType("text"); + + b.Property("FilePath") + .HasColumnType("text"); + + 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") + .HasColumnType("text"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("UploadedFileName") + .HasColumnType("text"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + + 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.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("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("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("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("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.FormTemplate", 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("FormTemplateType") + .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.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() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + 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("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("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() + .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("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") + .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("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.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.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.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.ElectionRoundAggregate.ElectionRound", b => + { + b.Navigation("MonitoringNgos"); + }); + + modelBuilder.Entity("Vote.Monitor.Domain.Entities.MonitoringNgoAggregate.MonitoringNgo", b => + { + 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/20240923141550_AddCitizenReportsGuide.cs b/api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.cs new file mode 100644 index 000000000..df8d189ca --- /dev/null +++ b/api/src/Vote.Monitor.Domain/Migrations/20240923141550_AddCitizenReportsGuide.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vote.Monitor.Domain.Migrations +{ + /// + public partial class AddCitizenReportsGuide : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Text", + table: "ObserversGuides", + type: "text", + nullable: true); + + migrationBuilder.CreateTable( + name: "CitizenGuides", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ElectionRoundId = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + FileName = table.Column(type: "text", nullable: true), + UploadedFileName = table.Column(type: "text", nullable: true), + FilePath = table.Column(type: "text", nullable: true), + MimeType = table.Column(type: "text", nullable: true), + WebsiteUrl = table.Column(type: "text", nullable: true), + Text = table.Column(type: "text", nullable: true), + GuideType = table.Column(type: "text", nullable: false), + IsDeleted = table.Column(type: "boolean", 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_CitizenGuides", x => x.Id); + table.ForeignKey( + name: "FK_CitizenGuides_ElectionRounds_ElectionRoundId", + column: x => x.ElectionRoundId, + principalTable: "ElectionRounds", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CitizenGuides_ElectionRoundId", + table: "CitizenGuides", + column: "ElectionRoundId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CitizenGuides"); + + migrationBuilder.DropColumn( + name: "Text", + table: "ObserversGuides"); + } + } +} diff --git a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs index d7aeb8eb9..e869ed826 100644 --- a/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs +++ b/api/src/Vote.Monitor.Domain/Migrations/VoteMonitorContextModelSnapshot.cs @@ -380,6 +380,63 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") + .HasColumnType("text"); + + b.Property("FilePath") + .HasColumnType("text"); + + 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") + .HasColumnType("text"); + + b.Property("Text") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("UploadedFileName") + .HasColumnType("text"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ElectionRoundId"); + + b.ToTable("CitizenGuides"); + }); + modelBuilder.Entity("Vote.Monitor.Domain.Entities.CitizenReportAggregate.CitizenReport", b => { b.Property("Id") @@ -5395,6 +5452,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("MonitoringNgoId") .HasColumnType("uuid"); + b.Property("Text") + .HasColumnType("text"); + b.Property("Title") .IsRequired() .HasMaxLength(256) @@ -5852,6 +5912,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.CitizenReportAggregate.CitizenReport", b => { b.HasOne("Vote.Monitor.Domain.Entities.ElectionRoundAggregate.ElectionRound", "ElectionRound") diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index 85f3c35d1..73fe17618 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -2,9 +2,9 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Vote.Monitor.Domain.Constants; using Vote.Monitor.Domain.Entities.AttachmentAggregate; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAggregate; using Vote.Monitor.Domain.Entities.CitizenReportAttachmentAggregate; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; using Vote.Monitor.Domain.Entities.CitizenReportNoteAggregate; using Vote.Monitor.Domain.Entities.ExportedDataAggregate; using Vote.Monitor.Domain.Entities.FeedbackAggregate; @@ -74,7 +74,7 @@ public VoteMonitorContext(DbContextOptions options, public DbSet CitizenReportNotes { get; set; } public DbSet CitizenReportAttachments { get; set; } public DbSet MonitoringObserverNotification { get; set; } - public DbSet CitizenReportGuides { get; set; } + public DbSet CitizenGuides { get; set; } public DbSet Locations { get; set; } diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj b/api/tests/Feature.Citizen.Guides.UnitTests/Feature.Citizen.Guides.UnitTests.csproj similarity index 92% rename from api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj rename to api/tests/Feature.Citizen.Guides.UnitTests/Feature.Citizen.Guides.UnitTests.csproj index bfcfa685b..785c060ba 100644 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/Feature.CitizenReports.Guides.UnitTests.csproj +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Feature.Citizen.Guides.UnitTests.csproj @@ -26,7 +26,7 @@ - + diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/GlobalUsings.cs b/api/tests/Feature.Citizen.Guides.UnitTests/GlobalUsings.cs new file mode 100644 index 000000000..f8185a276 --- /dev/null +++ b/api/tests/Feature.Citizen.Guides.UnitTests/GlobalUsings.cs @@ -0,0 +1,6 @@ +// Global using directives + +global using FluentValidation.TestHelper; +global using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; +global using Vote.Monitor.TestUtils; +global using Vote.Monitor.TestUtils.Fakes; \ No newline at end of file diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs similarity index 82% rename from api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs rename to api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs index b5d82d4e3..a9d8eb6a0 100644 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/CreateValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs @@ -1,8 +1,4 @@ -using FluentValidation.TestHelper; -using Vote.Monitor.Domain.Entities.CitizenReportGuideAggregate; -using Vote.Monitor.TestUtils.Fakes; - -namespace Feature.CitizenReports.Guides.UnitTests.Validators; +namespace Feature.Citizen.Guides.UnitTests.Validators; public class CreateValidatorTests { @@ -51,7 +47,7 @@ public void Should_Have_Error_When_Title_Exceeds_Maximum_Length() public void Should_Have_Error_When_Attachment_Is_Empty_For_Document_Type() { // Arrange - var model = new Create.Request { GuideType = CitizenReportGuideType.Document, Attachment = null }; + var model = new Create.Request { GuideType = CitizenGuideType.Document, Attachment = null }; // Act var result = _validator.TestValidate(model); @@ -66,7 +62,7 @@ public void Should_Have_Error_When_Attachment_Exceeds_File_Size_Limit_For_Docume // Arrange var model = new Create.Request { - GuideType = CitizenReportGuideType.Document, + GuideType = CitizenGuideType.Document, Attachment = FakeFormFile.New().HavingLength(51 * 1024 * 1024).Please() // 51 MB file }; @@ -81,7 +77,7 @@ public void Should_Have_Error_When_Attachment_Exceeds_File_Size_Limit_For_Docume public void Should_Have_Error_When_WebsiteUrl_Is_Empty_For_Website_Type() { // Arrange - var model = new Create.Request { GuideType = CitizenReportGuideType.Website, WebsiteUrl = string.Empty }; + var model = new Create.Request { GuideType = CitizenGuideType.Website, WebsiteUrl = string.Empty }; // Act var result = _validator.TestValidate(model); @@ -96,7 +92,7 @@ public void Should_Have_Error_When_WebsiteUrl_Exceeds_Maximum_Length_For_Website // Arrange var model = new Create.Request { - GuideType = CitizenReportGuideType.Website, + GuideType = CitizenGuideType.Website, WebsiteUrl = new string('A', 2049) }; @@ -113,7 +109,7 @@ public void Should_Have_Error_When_WebsiteUrl_Is_Invalid_For_Website_Type() // Arrange var model = new Create.Request { - GuideType = CitizenReportGuideType.Website, + GuideType = CitizenGuideType.Website, WebsiteUrl = "invalid-url" }; @@ -128,7 +124,7 @@ public void Should_Have_Error_When_WebsiteUrl_Is_Invalid_For_Website_Type() public void Should_Have_Error_When_WebsiteUrl_Is_Empty_For_Text_Type() { // Arrange - var model = new Create.Request { GuideType = CitizenReportGuideType.Text, WebsiteUrl = string.Empty }; + var model = new Create.Request { GuideType = CitizenGuideType.Text, WebsiteUrl = string.Empty }; // Act var result = _validator.TestValidate(model); @@ -145,7 +141,7 @@ public void Should_Not_Have_Error_When_Valid_Request() { ElectionRoundId = Guid.NewGuid(), Title = "Valid Title", - GuideType = CitizenReportGuideType.Document, + GuideType = CitizenGuideType.Document, Attachment = FakeFormFile.New().Please() }; diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs similarity index 90% rename from api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs rename to api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs index d612084ed..d2f1be59b 100644 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/DeleteValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/DeleteValidatorTests.cs @@ -1,6 +1,4 @@ -using FluentValidation.TestHelper; - -namespace Feature.CitizenReports.Guides.UnitTests.Validators; +namespace Feature.Citizen.Guides.UnitTests.Validators; public class DeleteValidatorTests { diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/ListValidatorTests.cs similarity index 89% rename from api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs rename to api/tests/Feature.Citizen.Guides.UnitTests/Validators/ListValidatorTests.cs index 577cc4550..85700408b 100644 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/ListValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/ListValidatorTests.cs @@ -1,6 +1,4 @@ -using FluentValidation.TestHelper; - -namespace Feature.CitizenReports.Guides.UnitTests.Validators; +namespace Feature.Citizen.Guides.UnitTests.Validators; public class ListValidatorTests { diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs similarity index 95% rename from api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs rename to api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs index 53e2fa214..719cb65a8 100644 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/Validators/UpdateValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs @@ -1,8 +1,4 @@ -using Xunit; -using FluentValidation.TestHelper; -using Vote.Monitor.TestUtils; - -namespace Feature.CitizenReports.Guides.UnitTests.Validators; +namespace Feature.Citizen.Guides.UnitTests.Validators; public class UpdateValidatorTests { diff --git a/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs b/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs deleted file mode 100644 index 6f09f6583..000000000 --- a/api/tests/Feature.CitizenReports.Guides.UnitTests/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using ObserverGuideAggregate = Vote.Monitor.Domain.Entities.ObserverGuideAggregate.ObserverGuide; diff --git a/api/tests/Feature.ObserverGuide.UnitTests/Endpoints/CreateEndpointTests.cs b/api/tests/Feature.ObserverGuide.UnitTests/Endpoints/CreateEndpointTests.cs index 3e3c59c61..dabb3bd8b 100644 --- a/api/tests/Feature.ObserverGuide.UnitTests/Endpoints/CreateEndpointTests.cs +++ b/api/tests/Feature.ObserverGuide.UnitTests/Endpoints/CreateEndpointTests.cs @@ -12,6 +12,7 @@ using NSubstitute.ReturnsExtensions; using Vote.Monitor.Core.Services.FileStorage.Contracts; using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; +using Vote.Monitor.Domain.Entities.ObserverGuideAggregate; using Vote.Monitor.Domain.Repository; using Vote.Monitor.TestUtils.Fakes.Aggregates; @@ -45,7 +46,8 @@ public async Task ShouldReturnOkWithObserverGuideModel_WhenUploadSucceeds() var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); var fakeMonitoringNgo = new MonitoringNgoAggregateFaker().Generate(); - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) + _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Any>()) .Returns(AuthorizationResult.Success()); _monitoringNgoRepository @@ -57,7 +59,8 @@ public async Task ShouldReturnOkWithObserverGuideModel_WhenUploadSucceeds() var stream = new MemoryStream(bytes); var url = "url"; var urlValidityInSeconds = 60; - _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) .Returns(new UploadFileResult.Ok(url, fileName, urlValidityInSeconds)); // Act @@ -73,7 +76,8 @@ public async Task ShouldReturnOkWithObserverGuideModel_WhenUploadSucceeds() { ElectionRoundId = fakeElectionRound.Id, Title = observerGuideTitle, - Attachment = formFile + Attachment = formFile, + GuideType = ObserverGuideType.Document }; var result = await _endpoint.ExecuteAsync(request, default); @@ -81,7 +85,7 @@ public async Task ShouldReturnOkWithObserverGuideModel_WhenUploadSucceeds() await _repository .Received(1) .AddAsync(Arg.Is(x => x.Title == observerGuideTitle - && x.MonitoringNgoId == fakeMonitoringNgo.Id)); + && x.MonitoringNgoId == fakeMonitoringNgo.Id)); var model = result.Result.As>(); model.Value!.PresignedUrl.Should().Be(url); @@ -96,7 +100,8 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); var fakeMonitoringNgo = new MonitoringNgoAggregateFaker().Generate(); - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) + _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Any>()) .Returns(AuthorizationResult.Failed()); _monitoringNgoRepository @@ -108,7 +113,8 @@ public async Task ShouldReturnNotFound_WhenNotAuthorised() var stream = new MemoryStream(bytes); var url = "url"; var urlValidityInSeconds = 60; - _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) .Returns(new UploadFileResult.Ok(url, fileName, urlValidityInSeconds)); // Act @@ -147,7 +153,8 @@ public async Task ShouldReturnNotFound_WhenMonitoringNgoNotFound() var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); var fakeMonitoringNgo = new MonitoringNgoAggregateFaker().Generate(); - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) + _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Any>()) .Returns(AuthorizationResult.Success()); _monitoringNgoRepository @@ -159,7 +166,8 @@ public async Task ShouldReturnNotFound_WhenMonitoringNgoNotFound() var stream = new MemoryStream(bytes); var url = "url"; var urlValidityInSeconds = 60; - _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) .Returns(new UploadFileResult.Ok(url, fileName, urlValidityInSeconds)); // Act @@ -198,7 +206,8 @@ public async Task ShouldReturnInternalServerError_WhenUploadFails() var fakeElectionRound = new ElectionRoundAggregateFaker().Generate(); var fakeMonitoringNgo = new MonitoringNgoAggregateFaker().Generate(); - _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), Arg.Any>()) + _authorizationService.AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Any>()) .Returns(AuthorizationResult.Success()); _monitoringNgoRepository @@ -209,7 +218,8 @@ public async Task ShouldReturnInternalServerError_WhenUploadFails() var fileName = "file.txt"; var bytes = Encoding.UTF8.GetBytes("Test content"); var stream = new MemoryStream(bytes); - _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _fileStorageService.UploadFileAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) .Returns(new UploadFileResult.Failed("error message")); // Act @@ -225,7 +235,8 @@ public async Task ShouldReturnInternalServerError_WhenUploadFails() { ElectionRoundId = fakeElectionRound.Id, Title = observerGuideTitle, - Attachment = formFile + Attachment = formFile, + GuideType = ObserverGuideType.Document }; var result = await _endpoint.ExecuteAsync(request, default); @@ -243,4 +254,4 @@ await _repository var model = result.Result.As(); model.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError); } -} +} \ No newline at end of file From d5d2efee0d063667f4f55dc2ed702464060efef2 Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Wed, 25 Sep 2024 11:06:08 +0300 Subject: [PATCH 3/7] refactor guides --- .../PoliciesInstaller.cs | 1 + .../Create/Validator.cs | 2 +- .../GetById/Endpoint.cs | 103 +++++++ .../Feature.Citizen.Guides/GetById/Request.cs | 7 + .../GetById}/Validator.cs | 3 +- .../Feature.Citizen.Guides/List/Endpoint.cs | 19 +- .../Update/Validator.cs | 1 + .../Feature.Locations/FetchLevels/Endpoint.cs | 127 -------- .../FetchLevels/LevelNode.cs | 13 - .../Feature.Locations/FetchLevels/Request.cs | 5 - .../Feature.Locations/FetchLevels/Response.cs | 8 - .../Feature.ObserverGuide/Create/Validator.cs | 2 +- .../Entities/LocationAggregate/Location.cs | 8 +- ...ration.cs => CitizenGuideConfiguration.cs} | 2 +- .../Vote.Monitor.Domain/VoteMonitorContext.cs | 1 + .../Validators/CreateValidatorTests.cs | 6 +- .../Validators/GetByIdValidatorTests.cs | 50 +++ .../Validators/UpdateValidatorTests.cs | 14 + web/package.json | 2 + web/pnpm-lock.yaml | 136 +++++++++ web/src/components/ui/DataTable/DataTable.tsx | 39 ++- web/src/components/ui/file-uploader.tsx | 260 ++++++++++++++++ web/src/components/ui/scroll-area.tsx | 46 +++ .../CitizenGuides/AddGuideDialog.tsx | 50 +++ .../components/CitizenGuides/AddGuideForm.tsx | 271 +++++++++++++++++ .../components/CitizenGuides/AddTextGuide.tsx | 73 +++++ .../CitizenGuides/EditGuideDialog.tsx | 53 ++++ .../CitizenGuides/EditGuideForm.tsx | 225 ++++++++++++++ .../CitizenGuides/EditTextGuide.tsx | 75 +++++ .../CitizenGuides/GuidesDashboard.tsx | 286 ++++++++++++++++++ .../components/Dashboard/Dashboard.tsx | 32 +- .../ObserversGuides/ConfirmDeleteDialog.tsx | 50 --- .../EditObserversGuideDialog.tsx | 104 ------- .../ObserversGuides/ObserversGuides.tsx | 188 ------------ .../UploadObserversGuideDialog.tsx | 147 --------- .../hooks/citizen-guides-hooks.ts | 50 +++ .../hooks/election-event-hooks.ts | 24 -- .../hooks/observer-guides-hooks.ts | 51 ++++ .../features/election-event/models/guide.ts | 24 ++ .../election-event/models/observer-guide.ts | 13 - .../forms/components/EditForm/EditForm.tsx | 1 - .../EditMonitoringObserver.tsx | 12 +- web/src/hooks/use-callback-ref.ts | 27 ++ web/src/hooks/use-controllable-state.ts | 67 ++++ web/src/lib/utils.ts | 20 +- web/src/locales/en.json | 13 +- web/src/routeTree.gen.ts | 44 +++ .../routes/citizen-guides/edit.$guideId.tsx | 27 ++ web/src/routes/citizen-guides/new.tsx | 19 ++ web/src/routes/election-event/$tab.tsx | 1 + .../routes/observer-guides/edit.$guideId.tsx | 27 ++ web/src/routes/observer-guides/new.tsx | 19 ++ 52 files changed, 2126 insertions(+), 722 deletions(-) create mode 100644 api/src/Feature.Citizen.Guides/GetById/Endpoint.cs create mode 100644 api/src/Feature.Citizen.Guides/GetById/Request.cs rename api/src/{Feature.Locations/FetchLevels => Feature.Citizen.Guides/GetById}/Validator.cs (61%) delete mode 100644 api/src/Feature.Locations/FetchLevels/Endpoint.cs delete mode 100644 api/src/Feature.Locations/FetchLevels/LevelNode.cs delete mode 100644 api/src/Feature.Locations/FetchLevels/Request.cs delete mode 100644 api/src/Feature.Locations/FetchLevels/Response.cs rename api/src/Vote.Monitor.Domain/EntitiesConfiguration/{CitizenReportsGuideConfiguration.cs => CitizenGuideConfiguration.cs} (92%) create mode 100644 api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs create mode 100644 web/src/components/ui/file-uploader.tsx create mode 100644 web/src/components/ui/scroll-area.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/AddGuideDialog.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/AddGuideForm.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/AddTextGuide.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/EditGuideDialog.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/EditGuideForm.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/EditTextGuide.tsx create mode 100644 web/src/features/election-event/components/CitizenGuides/GuidesDashboard.tsx delete mode 100644 web/src/features/election-event/components/ObserversGuides/ConfirmDeleteDialog.tsx delete mode 100644 web/src/features/election-event/components/ObserversGuides/EditObserversGuideDialog.tsx delete mode 100644 web/src/features/election-event/components/ObserversGuides/ObserversGuides.tsx delete mode 100644 web/src/features/election-event/components/ObserversGuides/UploadObserversGuideDialog.tsx create mode 100644 web/src/features/election-event/hooks/citizen-guides-hooks.ts create mode 100644 web/src/features/election-event/hooks/observer-guides-hooks.ts create mode 100644 web/src/features/election-event/models/guide.ts delete mode 100644 web/src/features/election-event/models/observer-guide.ts create mode 100644 web/src/hooks/use-callback-ref.ts create mode 100644 web/src/hooks/use-controllable-state.ts create mode 100644 web/src/routes/citizen-guides/edit.$guideId.tsx create mode 100644 web/src/routes/citizen-guides/new.tsx create mode 100644 web/src/routes/observer-guides/edit.$guideId.tsx create mode 100644 web/src/routes/observer-guides/new.tsx diff --git a/api/src/Authorization.Policies/PoliciesInstaller.cs b/api/src/Authorization.Policies/PoliciesInstaller.cs index d187d9141..7eb696ce6 100644 --- a/api/src/Authorization.Policies/PoliciesInstaller.cs +++ b/api/src/Authorization.Policies/PoliciesInstaller.cs @@ -14,6 +14,7 @@ public static IServiceCollection AddAuthorizationPolicies(this IServiceCollectio services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddAuthorization(options => { diff --git a/api/src/Feature.Citizen.Guides/Create/Validator.cs b/api/src/Feature.Citizen.Guides/Create/Validator.cs index dfb561f0c..2d1e8d827 100644 --- a/api/src/Feature.Citizen.Guides/Create/Validator.cs +++ b/api/src/Feature.Citizen.Guides/Create/Validator.cs @@ -21,7 +21,7 @@ public Validator() .IsValidUri() .When(x => x.GuideType == CitizenGuideType.Website); - RuleFor(x => x.WebsiteUrl) + RuleFor(x => x.Text) .NotEmpty()! .When(x => x.GuideType == CitizenGuideType.Text); } diff --git a/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs new file mode 100644 index 000000000..9be3e4d00 --- /dev/null +++ b/api/src/Feature.Citizen.Guides/GetById/Endpoint.cs @@ -0,0 +1,103 @@ +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Vote.Monitor.Core.Services.FileStorage.Contracts; +using Vote.Monitor.Domain; +using Vote.Monitor.Domain.Entities.CitizenGuideAggregate; + +namespace Feature.Citizen.Guides.GetById; + +public class Endpoint( + IAuthorizationService authorizationService, + VoteMonitorContext context, + IFileStorageService fileStorageService) + : Endpoint, NotFound>> +{ + public override void Configure() + { + Get("/api/election-rounds/{electionRoundId}/citizen-guides/{id}"); + DontAutoTag(); + Options(x => x.WithTags("citizen-guides")); + Policies(PolicyNames.NgoAdminsOnly); + } + + public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + // ReSharper disable once EntityFramework.NPlusOne.IncompleteDataQuery + var guide = await context + .CitizenGuides + .Where(x => x.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted && x.Id == req.Id) + .OrderByDescending(x => x.CreatedOn) + .Join(context.NgoAdmins, guide => guide.CreatedBy, user => user.Id, (guide, ngoAdmin) => new + { + guide.Id, + guide.Title, + guide.FileName, + guide.UploadedFileName, + guide.MimeType, + guide.GuideType, + guide.CreatedOn, + guide.FilePath, + guide.Text, + guide.WebsiteUrl, + UserId = ngoAdmin.ApplicationUserId + }) + .Join(context.Users, x => x.UserId, user => user.Id, (guide, user) => new + { + guide.Id, + guide.Title, + guide.FileName, + guide.UploadedFileName, + guide.MimeType, + guide.GuideType, + guide.CreatedOn, + guide.FilePath, + guide.Text, + guide.WebsiteUrl, + CreatedBy = user.FirstName + " " + user.LastName + }) + .AsNoTracking() + .FirstOrDefaultAsync(ct); + + if (guide is null) + { + return TypedResults.NotFound(); + } + + var citizenGuideModel = new CitizenGuideModel + { + Id = guide.Id, + Title = guide.Title, + FileName = guide.FileName, + MimeType = guide.MimeType, + GuideType = guide.GuideType, + CreatedOn = guide.CreatedOn, + Text = guide.Text, + WebsiteUrl = guide.WebsiteUrl, + CreatedBy = guide.CreatedBy + }; + + if (guide.GuideType == CitizenGuideType.Document) + { + var presignedUrl = await fileStorageService.GetPresignedUrlAsync( + guide.FilePath!, + guide.UploadedFileName!); + + return TypedResults.Ok(citizenGuideModel with + { + PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty, + UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0, + }); + } + + return TypedResults.Ok(citizenGuideModel); + } +} \ No newline at end of file diff --git a/api/src/Feature.Citizen.Guides/GetById/Request.cs b/api/src/Feature.Citizen.Guides/GetById/Request.cs new file mode 100644 index 000000000..4ccf8165c --- /dev/null +++ b/api/src/Feature.Citizen.Guides/GetById/Request.cs @@ -0,0 +1,7 @@ +namespace Feature.Citizen.Guides.GetById; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + public Guid Id { get; set; } +} diff --git a/api/src/Feature.Locations/FetchLevels/Validator.cs b/api/src/Feature.Citizen.Guides/GetById/Validator.cs similarity index 61% rename from api/src/Feature.Locations/FetchLevels/Validator.cs rename to api/src/Feature.Citizen.Guides/GetById/Validator.cs index 165b90f22..f510ac3eb 100644 --- a/api/src/Feature.Locations/FetchLevels/Validator.cs +++ b/api/src/Feature.Citizen.Guides/GetById/Validator.cs @@ -1,9 +1,10 @@ -namespace Feature.Locations.FetchLevels; +namespace Feature.Citizen.Guides.GetById; public class Validator : Validator { public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.Id).NotEmpty(); } } diff --git a/api/src/Feature.Citizen.Guides/List/Endpoint.cs b/api/src/Feature.Citizen.Guides/List/Endpoint.cs index 48fc0e4fa..fa4fc3672 100644 --- a/api/src/Feature.Citizen.Guides/List/Endpoint.cs +++ b/api/src/Feature.Citizen.Guides/List/Endpoint.cs @@ -19,6 +19,7 @@ public override void Configure() Get("/api/election-rounds/{electionRoundId}/citizen-guides"); DontAutoTag(); Options(x => x.WithTags("citizen-guides")); + AllowAnonymous(); } public override async Task, NotFound>> ExecuteAsync(Request req, CancellationToken ct) @@ -39,7 +40,7 @@ public override async Task, NotFound>> ExecuteAsync(Request .CitizenGuides .Where(x => x.ElectionRoundId == req.ElectionRoundId && !x.IsDeleted) .OrderByDescending(x=>x.CreatedOn) - .Join(context.Users, guide=>guide.CreatedBy, user => user.Id, (guide, ngoAdmin) => new + .Join(context.NgoAdmins, guide => guide.CreatedBy, user => user.Id, (guide, ngoAdmin) => new { guide.Id, guide.Title, @@ -51,7 +52,21 @@ public override async Task, NotFound>> ExecuteAsync(Request guide.FilePath, guide.Text, guide.WebsiteUrl, - CreatedBy = isNgoAdmin ? ngoAdmin.FirstName + " " + ngoAdmin.LastName : "" + UserId = ngoAdmin.ApplicationUserId + }) + .Join(context.Users, x => x.UserId, user => user.Id, (guide, user) => new + { + guide.Id, + guide.Title, + guide.FileName, + guide.UploadedFileName, + guide.MimeType, + guide.GuideType, + guide.CreatedOn, + guide.FilePath, + guide.Text, + guide.WebsiteUrl, + CreatedBy = isNgoAdmin ? user.FirstName + " " + user.LastName : "" }) .AsNoTracking() .ToListAsync(ct); diff --git a/api/src/Feature.Citizen.Guides/Update/Validator.cs b/api/src/Feature.Citizen.Guides/Update/Validator.cs index 4339d66bb..1fd40b764 100644 --- a/api/src/Feature.Citizen.Guides/Update/Validator.cs +++ b/api/src/Feature.Citizen.Guides/Update/Validator.cs @@ -7,6 +7,7 @@ public class Validator : Validator public Validator() { RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.Id).NotEmpty(); RuleFor(x => x.Title).NotEmpty().MaximumLength(256); RuleFor(x => x.WebsiteUrl) diff --git a/api/src/Feature.Locations/FetchLevels/Endpoint.cs b/api/src/Feature.Locations/FetchLevels/Endpoint.cs deleted file mode 100644 index f64c646a9..000000000 --- a/api/src/Feature.Locations/FetchLevels/Endpoint.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using Vote.Monitor.Core.Extensions; - -namespace Feature.Locations.FetchLevels; -public class Endpoint(VoteMonitorContext context, IMemoryCache memoryCache) : Endpoint, NotFound>> -{ - public override void Configure() - { - Get("/api/election-rounds/{electionRoundId}/locations:fetchLevels"); - DontAutoTag(); - Options(x => x.WithTags("locations")); - Description(x => x.Accepts()); - Summary(s => - { - s.Summary = "Gets all levels for all locations"; - s.Description = "Gets all locations and a cache key for the data"; - }); - } - - public override async Task, NotFound>> ExecuteAsync(Request request, CancellationToken ct) - { - var electionRound = await context.ElectionRounds - .Where(x => x.Id == request.ElectionRoundId) - .Select(x => new { x.LocationsVersion, x.Id }) - .FirstOrDefaultAsync(ct); - - if (electionRound is null) - { - return TypedResults.NotFound(); - } - - var cacheKey = $"election-rounds/{request.ElectionRoundId}/location-nodes/{electionRound.LocationsVersion}"; - - var cachedResponse = await memoryCache.GetOrCreateAsync(cacheKey, async (e) => - { - var locations = await context.Locations - .Where(x => x.ElectionRoundId == request.ElectionRoundId) - .Select(x => new - { - x.Level1, - x.Level2, - x.Level3, - x.Level4, - x.Level5, - }) - .Distinct() - .ToListAsync(cancellationToken: ct); - - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - - Dictionary cache = new(); - int id = 0; - - foreach (var ps in locations) - { - var parentNode = cache.GetOrCreate(BuildKey(ps.Level1), () => new LevelNode - { - Id = ++id, - Name = ps.Level1, - Depth = 1 - }); - - if (!string.IsNullOrWhiteSpace(ps.Level2)) - { - var level2Key = BuildKey(ps.Level1, ps.Level2); - parentNode = cache.GetOrCreate(level2Key, () => new LevelNode - { - Id = ++id, - Name = ps.Level2, - ParentId = parentNode.Id, - Depth = 2 - }); - } - - if (!string.IsNullOrWhiteSpace(ps.Level3)) - { - var level3Key = BuildKey(ps.Level1, ps.Level2, ps.Level3); - parentNode = cache.GetOrCreate(level3Key, () => new LevelNode - { - Id = ++id, - Name = ps.Level3, - ParentId = parentNode.Id, - Depth = 3 - }); - } - - if (!string.IsNullOrWhiteSpace(ps.Level4)) - { - var level4Key = BuildKey(ps.Level1, ps.Level2, ps.Level3, ps.Level4); - parentNode = cache.GetOrCreate(level4Key, () => new LevelNode - { - Id = ++id, - Name = ps.Level4, - ParentId = parentNode.Id, - Depth = 4 - }); - } - - if (!string.IsNullOrWhiteSpace(ps.Level5)) - { - var level5Key = BuildKey(ps.Level1, ps.Level2, ps.Level3, ps.Level4, ps.Level5); - parentNode = cache.GetOrCreate(level5Key, () => new LevelNode - { - Id = ++id, - Name = ps.Level5, - ParentId = parentNode.Id, - Depth = 5 - }); - } - } - - return new Response - { - ElectionRoundId = electionRound.Id, - Version = electionRound.LocationsVersion.ToString(), - Nodes = [.. cache.Values] - }; - }); - - return TypedResults.Ok(cachedResponse!); - } - - private static string BuildKey(params string[] keyParts) - { - return string.Join("-", keyParts); - } -} diff --git a/api/src/Feature.Locations/FetchLevels/LevelNode.cs b/api/src/Feature.Locations/FetchLevels/LevelNode.cs deleted file mode 100644 index 90e7fc6ec..000000000 --- a/api/src/Feature.Locations/FetchLevels/LevelNode.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Feature.Locations.FetchLevels; - -public class LevelNode -{ - public int Id { get; set; } - public string Name { get; set; } - public int Depth { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public int? ParentId { get; set; } -} diff --git a/api/src/Feature.Locations/FetchLevels/Request.cs b/api/src/Feature.Locations/FetchLevels/Request.cs deleted file mode 100644 index 61ed8b680..000000000 --- a/api/src/Feature.Locations/FetchLevels/Request.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Feature.Locations.FetchLevels; -public class Request -{ - public Guid ElectionRoundId { get; set; } -} diff --git a/api/src/Feature.Locations/FetchLevels/Response.cs b/api/src/Feature.Locations/FetchLevels/Response.cs deleted file mode 100644 index ba32f8958..000000000 --- a/api/src/Feature.Locations/FetchLevels/Response.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Feature.Locations.FetchLevels; - -public class Response -{ - public Guid ElectionRoundId { get; set; } - public string Version { get; set; } - public List Nodes { get; set; } = []; -} diff --git a/api/src/Feature.ObserverGuide/Create/Validator.cs b/api/src/Feature.ObserverGuide/Create/Validator.cs index b4b7fe928..4baf683eb 100644 --- a/api/src/Feature.ObserverGuide/Create/Validator.cs +++ b/api/src/Feature.ObserverGuide/Create/Validator.cs @@ -21,7 +21,7 @@ public Validator() .IsValidUri() .When(x => x.GuideType == ObserverGuideType.Website); - RuleFor(x => x.WebsiteUrl) + RuleFor(x => x.Text) .NotEmpty() .When(x => x.GuideType == ObserverGuideType.Text); } diff --git a/api/src/Vote.Monitor.Domain/Entities/LocationAggregate/Location.cs b/api/src/Vote.Monitor.Domain/Entities/LocationAggregate/Location.cs index c16fc43d4..b2fb0e211 100644 --- a/api/src/Vote.Monitor.Domain/Entities/LocationAggregate/Location.cs +++ b/api/src/Vote.Monitor.Domain/Entities/LocationAggregate/Location.cs @@ -31,13 +31,13 @@ public static Location Create(ElectionRound electionRound, DateTime createdOn, Guid userId) { - var pollingStation = new Location(electionRound, level1, level2, level3, level4, level5, displayOrder, + var location = new Location(electionRound, level1, level2, level3, level4, level5, displayOrder, tags); - pollingStation.CreatedOn = createdOn; - pollingStation.CreatedBy = userId; + location.CreatedOn = createdOn; + location.CreatedBy = userId; - return pollingStation; + return location; } public ElectionRound ElectionRound { get; private set; } diff --git a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenGuideConfiguration.cs similarity index 92% rename from api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs rename to api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenGuideConfiguration.cs index ea032c227..d77452656 100644 --- a/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenReportsGuideConfiguration.cs +++ b/api/src/Vote.Monitor.Domain/EntitiesConfiguration/CitizenGuideConfiguration.cs @@ -4,7 +4,7 @@ namespace Vote.Monitor.Domain.EntitiesConfiguration; -internal class CitizenReportsGuideConfiguration : IEntityTypeConfiguration +internal class CitizenGuideConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { diff --git a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs index 73fe17618..dd44a746a 100644 --- a/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs +++ b/api/src/Vote.Monitor.Domain/VoteMonitorContext.cs @@ -148,6 +148,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.ApplyConfiguration(new CitizenReportConfiguration()); builder.ApplyConfiguration(new CitizenReportNoteConfiguration()); builder.ApplyConfiguration(new CitizenReportAttachmentConfiguration()); + builder.ApplyConfiguration(new CitizenGuideConfiguration()); builder.ApplyConfiguration(new LocationConfiguration()); } diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs index a9d8eb6a0..f33cdf6eb 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/CreateValidatorTests.cs @@ -121,16 +121,16 @@ public void Should_Have_Error_When_WebsiteUrl_Is_Invalid_For_Website_Type() } [Fact] - public void Should_Have_Error_When_WebsiteUrl_Is_Empty_For_Text_Type() + public void Should_Have_Error_When_Text_Is_Empty_For_Text_Type() { // Arrange - var model = new Create.Request { GuideType = CitizenGuideType.Text, WebsiteUrl = string.Empty }; + var model = new Create.Request { GuideType = CitizenGuideType.Text, Text = string.Empty }; // Act var result = _validator.TestValidate(model); // Assert - result.ShouldHaveValidationErrorFor(x => x.WebsiteUrl); + result.ShouldHaveValidationErrorFor(x => x.Text); } [Fact] diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs new file mode 100644 index 000000000..9cd5f773c --- /dev/null +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/GetByIdValidatorTests.cs @@ -0,0 +1,50 @@ +namespace Feature.Citizen.Guides.UnitTests.Validators; + +public class GetByIdValidatorTests +{ + private readonly GetById.Validator _validator = new(); + + [Fact] + public void Should_Have_Error_When_ElectionRoundId_Is_Empty() + { + // Arrange + var model = new GetById.Request { ElectionRoundId = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Should_Have_Error_When_Id_Is_Empty() + { + // Arrange + var model = new GetById.Request { Id = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Id); + } + + + [Fact] + public void Should_Not_Have_Error_When_Valid_Request() + { + // Arrange + var model = new GetById.Request + { + ElectionRoundId = Guid.NewGuid(), + Id = Guid.NewGuid(), + }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } +} \ No newline at end of file diff --git a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs index 719cb65a8..15c0d1e83 100644 --- a/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs +++ b/api/tests/Feature.Citizen.Guides.UnitTests/Validators/UpdateValidatorTests.cs @@ -16,6 +16,19 @@ public void Should_Have_Error_When_ElectionRoundId_Is_Empty() // Assert result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); } + + [Fact] + public void Should_Have_Error_When_Id_Is_Empty() + { + // Arrange + var model = new Update.Request { Id = Guid.Empty }; + + // Act + var result = _validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Id); + } [Theory] [MemberData(nameof(TestData.EmptyStringsTestCases), MemberType = typeof(TestData))] @@ -89,6 +102,7 @@ public void Should_Not_Have_Error_When_Valid_Request() var model = new Update.Request { ElectionRoundId = Guid.NewGuid(), + Id = Guid.NewGuid(), Title = "Valid Title", WebsiteUrl = "https://validurl.com" }; diff --git a/web/package.json b/web/package.json index 7b4cc91f9..be2a44b65 100644 --- a/web/package.json +++ b/web/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", @@ -76,6 +77,7 @@ "react-chartjs-2": "^5.2.0", "react-day-picker": "^8.10.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-easy-sort": "^1.6.0", "react-hook-form": "^7.45.2", "react-i18next": "^14.1.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ad9dc4618..900dd93cd 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-scroll-area': + specifier: ^1.1.0 + version: 1.1.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-select': specifier: ^2.0.0 version: 2.0.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -176,6 +179,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-dropzone: + specifier: ^14.2.3 + version: 14.2.3(react@18.2.0) react-easy-sort: specifier: ^1.6.0 version: 1.6.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1568,6 +1574,9 @@ packages: '@radix-ui/number@1.0.1': resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} + '@radix-ui/number@1.1.0': + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + '@radix-ui/primitive@1.0.1': resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} @@ -1666,6 +1675,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.0.5': resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} peerDependencies: @@ -1688,6 +1706,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.0.4': resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} peerDependencies: @@ -1875,6 +1902,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.0': + resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@1.0.3': resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -1940,6 +1980,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-scroll-area@1.1.0': + resolution: {integrity: sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@1.2.2': resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} peerDependencies: @@ -2155,6 +2208,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-previous@1.0.1': resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} peerDependencies: @@ -3609,6 +3671,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + attr-accept@2.2.2: + resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==} + engines: {node: '>=4'} + autoprefixer@10.4.14: resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} engines: {node: ^10 || ^12 || >=14} @@ -4533,6 +4599,10 @@ packages: file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + file-selector@0.6.0: + resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==} + engines: {node: '>= 12'} + file-system-cache@2.3.0: resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} @@ -6212,6 +6282,12 @@ packages: peerDependencies: react: ^18.2.0 + react-dropzone@14.2.3: + resolution: {integrity: sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==} + engines: {node: '>= 10.13'} + peerDependencies: + react: '>= 16.8 || 18.0.0' + react-easy-sort@1.6.0: resolution: {integrity: sha512-zd9Nn90wVlZPEwJrpqElN87sf9GZnFR1StfjgNQVbSpR5QTSzCHjEYK6REuwq49Ip+76KOMSln9tg/ST2KLelg==} engines: {node: '>=16'} @@ -8951,6 +9027,8 @@ snapshots: dependencies: '@babel/runtime': 7.24.1 + '@radix-ui/number@1.1.0': {} + '@radix-ui/primitive@1.0.1': dependencies: '@babel/runtime': 7.24.1 @@ -9049,6 +9127,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.17 + '@radix-ui/react-context@1.1.0(@types/react@18.2.17)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.17 + '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -9079,6 +9163,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.17 + '@radix-ui/react-direction@1.1.0(@types/react@18.2.17)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.17 + '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -9292,6 +9382,16 @@ snapshots: '@types/react': 18.2.17 '@types/react-dom': 18.2.7 + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.17)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.17)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.17 + '@types/react-dom': 18.2.7 + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -9359,6 +9459,23 @@ snapshots: '@types/react': 18.2.17 '@types/react-dom': 18.2.7 + '@radix-ui/react-scroll-area@1.1.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.17)(react@18.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@18.2.17)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.2.17)(react@18.2.0) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.17)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.17)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.17 + '@types/react-dom': 18.2.7 + '@radix-ui/react-select@1.2.2(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -9617,6 +9734,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.17 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.17)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.17 + '@radix-ui/react-use-previous@1.0.1(@types/react@18.2.17)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -11721,6 +11844,8 @@ snapshots: asynckit@0.4.0: {} + attr-accept@2.2.2: {} + autoprefixer@10.4.14(postcss@8.4.27): dependencies: browserslist: 4.23.0 @@ -12905,6 +13030,10 @@ snapshots: file-saver@2.0.5: {} + file-selector@0.6.0: + dependencies: + tslib: 2.6.2 + file-system-cache@2.3.0: dependencies: fs-extra: 11.1.1 @@ -14630,6 +14759,13 @@ snapshots: react: 18.2.0 scheduler: 0.23.0 + react-dropzone@14.2.3(react@18.2.0): + dependencies: + attr-accept: 2.2.2 + file-selector: 0.6.0 + prop-types: 15.8.1 + react: 18.2.0 + react-easy-sort@1.6.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: array-move: 3.0.1 diff --git a/web/src/components/ui/DataTable/DataTable.tsx b/web/src/components/ui/DataTable/DataTable.tsx index b2efeb507..6c58c734b 100644 --- a/web/src/components/ui/DataTable/DataTable.tsx +++ b/web/src/components/ui/DataTable/DataTable.tsx @@ -35,6 +35,20 @@ declare module '@tanstack/react-table' { } } +function isArrayResult(result: PageResponse | TData[]): result is TData[] { + return Array.isArray(result); // data will be an array for TData[] +} + +function isEmpty(result?: PageResponse | TData[]): boolean | undefined{ + if(result === undefined) return undefined; + + if(isArrayResult(result)){ + return result.length === 0; + + } + + return result.isEmpty; +} export interface DataTableProps { /** * Tanstack table column definitions. @@ -44,7 +58,9 @@ export interface DataTableProps) => UseQueryResult, Error>; + useQuery: ( + params: DataTableParameters + ) => UseQueryResult, Error> | UseQueryResult; /** * Externalize pagination state to the parent component. @@ -145,7 +161,7 @@ export function DataTable( [sorting, setSorting] = [sortingExt, setSortingExt]; } - const { data, isFetching, isSuccess } = useQuery({ + const { data: result, isFetching, isSuccess } = useQuery({ pageNumber: pagination.pageIndex + 1, pageSize: pagination.pageSize, sortColumnName: sorting[0]?.id || 'id', @@ -155,18 +171,22 @@ export function DataTable( useEffect(() => { if (isSuccess && onDataFetchingSucceed) { - onDataFetchingSucceed(data.pageSize, data.currentPage, data.totalCount); + if (isArrayResult(result)) { + onDataFetchingSucceed(result.length, 1, result.length); + } else { + onDataFetchingSucceed(result.pageSize, result.currentPage, result.totalCount); + } } - }, [isSuccess, queryParams]); + }, [isSuccess, queryParams, result]); const table = useReactTable({ - data: data?.items || [], + data: !!result ? (isArrayResult(result) ? result : result.items ?? []) : [], columns, manualPagination: true, manualSorting: true, enableSorting: true, enableFilters: true, - pageCount: data ? Math.ceil(data.totalCount / data.pageSize) : 0, + pageCount: result ? (isArrayResult(result) ? result.length : Math.ceil(result.totalCount / result.pageSize)) : 0, columnResizeMode: 'onChange', enableColumnResizing: true, getCoreRowModel: getCoreRowModel(), @@ -188,8 +208,8 @@ export function DataTable( return (
- {data?.isEmpty ? ( -
+ {isEmpty(result) ? ( +

{emptyTitle ?? 'No data'}

@@ -256,7 +276,8 @@ export function DataTable(
- + + {!!result && !isArrayResult(result) && } )}
diff --git a/web/src/components/ui/file-uploader.tsx b/web/src/components/ui/file-uploader.tsx new file mode 100644 index 000000000..e1e324ce9 --- /dev/null +++ b/web/src/components/ui/file-uploader.tsx @@ -0,0 +1,260 @@ +import { ArrowUpTrayIcon, DocumentTextIcon, XMarkIcon } from "@heroicons/react/24/solid" +import * as React from "react" +import Dropzone, { + type DropzoneProps, + type FileRejection, +} from "react-dropzone" + +import { Button } from "@/components/ui/button" +import { useControllableState } from "@/hooks/use-controllable-state" +import { cn, formatBytes } from "@/lib/utils" +import { toast } from "./use-toast" + +interface FileUploaderProps extends React.HTMLAttributes { + /** + * Value of the uploader. + * @type File[] + * @default undefined + * @example value={files} + */ + value?: File[] + + /** + * Function to be called when the value changes. + * @type (files: File[]) => void + * @default undefined + * @example onValueChange={(files) => setFiles(files)} + */ + onValueChange?: (files: File[]) => void + + /** + * Accepted file types for the uploader. + * @type { [key: string]: string[]} + * @default + * ```ts + * { "image/*": [] } + * ``` + * @example accept={["image/png", "image/jpeg"]} + */ + accept?: DropzoneProps["accept"] + + /** + * Maximum file size for the uploader. + * @type number | undefined + * @default 1024 * 1024 * 2 // 2MB + * @example maxSize={1024 * 1024 * 2} // 2MB + */ + maxSize?: DropzoneProps["maxSize"] + + /** + * Maximum number of files for the uploader. + * @type number | undefined + * @default 1 + * @example maxFileCount={4} + */ + maxFileCount?: DropzoneProps["maxFiles"] + + /** + * Whether the uploader should accept multiple files. + * @type boolean + * @default false + * @example multiple + */ + multiple?: boolean + + /** + * Whether the uploader is disabled. + * @type boolean + * @default false + * @example disabled + */ + disabled?: boolean +} + +export function FileUploader(props: FileUploaderProps) { + const { + value: valueProp, + onValueChange, + accept = { + "image/*": [], + }, + maxSize = 1024 * 1024 * 2, + maxFileCount = 1, + multiple = false, + disabled = false, + className, + ...dropzoneProps + } = props + + const [files, setFiles] = useControllableState({ + prop: valueProp, + onChange: onValueChange, + }) + + const onDrop = React.useCallback( + (acceptedFiles: File[], rejectedFiles: FileRejection[]) => { + if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) { + toast({ + title:"Cannot upload more than 1 file at a time", + variant: 'destructive' + }) + return + } + + if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) { + toast({title:`Cannot upload more than ${maxFileCount} files`, variant: 'destructive'}) + return + } + + const newFiles = acceptedFiles.map((file) => + Object.assign(file, { + preview: URL.createObjectURL(file), + }) + ) + + const updatedFiles = files ? [...files, ...newFiles] : newFiles + + setFiles(updatedFiles) + + if (rejectedFiles.length > 0) { + rejectedFiles.forEach(({ file }) => { + toast({title: `File ${file.name} was rejected`, variant: 'destructive'}) + }) + } + }, + + [files, maxFileCount, multiple, setFiles] + ) + + function onRemove(index: number) { + if (!files) return + const newFiles = files.filter((_, i) => i !== index) + setFiles(newFiles) + onValueChange?.(newFiles) + } + + const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount + + return ( +
+ 1 || multiple} + disabled={isDisabled} + > + {({ getRootProps, getInputProps, isDragActive }) => ( +
+ + {isDragActive ? ( +
+
+
+

+ Drop the files here +

+
+ ) : ( +
+
+
+
+

+ Drag {`'n'`} drop files here, or click to select files +

+

+ You can upload + {maxFileCount > 1 + ? ` ${maxFileCount === Infinity ? "multiple" : maxFileCount} + files (up to ${formatBytes(maxSize)} each)` + : ` a file with ${formatBytes(maxSize)}`} +

+
+
+ )} +
+ )} +
+ {files?.length ? ( +
+ {files?.map((file, index) => ( + onRemove(index)} + /> + ))} +
+ ) : null} +
+ ) +} + +interface FileCardProps { + file: File + onRemove: () => void +} + +function FileCard({ file, onRemove }: FileCardProps) { + return ( +
+
+
+
+

+ {file.name} +

+

+ {formatBytes(file.size)} +

+
+
+
+
+ +
+
+ ) +} + +interface FilePreviewProps { + file: File & { preview: string } +} + +function FilePreview({ file }: FilePreviewProps) { + return ( +