Skip to content

Commit f3cee10

Browse files
authored
allow multiple form submission (#995)
* Add AllowMultipleFormSubmission flag * Add created at timestamp for form submissions * Rework notes * Rework attachments * Rework queries * fix selects * Fix selects * Add missing endpoints * Add missing endpoints * Update appsettings.Development.json * Update appsettings.Development.json * Update Endpoint.cs
1 parent 8986d33 commit f3cee10

File tree

99 files changed

+42122
-472
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+42122
-472
lines changed

api/Directory.Packages.props

Lines changed: 90 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,92 @@
11
<Project>
2-
<PropertyGroup>
3-
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
<ImplicitUsings>enable</ImplicitUsings>
5-
<Nullable>enable</Nullable>
6-
</PropertyGroup>
7-
<ItemGroup>
8-
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.300"/>
9-
<PackageVersion Include="AWSSDK.S3" Version="3.7.307.15"/>
10-
<PackageVersion Include="AWSSDK.SimpleEmail" Version="3.7.300.74"/>
11-
<PackageVersion Include="AWSSDK.Core" Version="3.7.303.14" />
12-
<PackageVersion Include="Ardalis.SmartEnum" Version="8.0.0"/>
13-
<PackageVersion Include="Ardalis.SmartEnum.Dapper" Version="7.0.0"/>
14-
<PackageVersion Include="Ardalis.SmartEnum.EFCore" Version="8.0.0"/>
15-
<PackageVersion Include="Ardalis.SmartEnum.SystemTextJson" Version="8.0.0"/>
16-
<PackageVersion Include="Ardalis.Specification" Version="8.0.0"/>
17-
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0"/>
18-
<PackageVersion Include="AspNetCore.HealthChecks.Hangfire" Version="8.0.1"/>
19-
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.1"/>
20-
<PackageVersion Include="CsvHelper" Version="31.0.3"/>
21-
<PackageVersion Include="Dapper" Version="2.1.35"/>
22-
<PackageVersion Include="EFCore.BulkExtensions.PostgreSql" Version="8.0.4"/>
23-
<PackageVersion Include="FastEndpoints" Version="5.24.0"/>
24-
<PackageVersion Include="FastEndpoints.Attributes" Version="5.24.0"/>
25-
<PackageVersion Include="FastEndpoints.Security" Version="5.24.0"/>
26-
<PackageVersion Include="FastEndpoints.Swagger" Version="5.24.0"/>
27-
<PackageVersion Include="FirebaseAdmin" Version="2.4.1"/>
28-
<PackageVersion Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0"/>
29-
<PackageVersion Include="Hangfire" Version="1.8.12"/>
30-
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.12"/>
31-
<PackageVersion Include="Hangfire.Core" Version="1.8.12"/>
32-
<PackageVersion Include="Hangfire.Dashboard.Basic.Authentication" Version="7.0.1"/>
33-
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.8"/>
34-
<PackageVersion Include="HtmlSanitizer" Version="8.1.870"/>
35-
<PackageVersion Include="Humanizer" Version="2.14.1"/>
36-
<PackageVersion Include="MailKit" Version="4.5.0"/>
37-
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.4"/>
38-
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8"/>
39-
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.10"/>
40-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8"/>
41-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10"/>
42-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8"/>
43-
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
44-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2"/>
45-
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5"/>
46-
<PackageVersion Include="NPOI" Version="2.7.0"/>
47-
<PackageVersion Include="Npgsql" Version="8.0.4"/>
48-
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8"/>
49-
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.8.0"/>
50-
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1"/>
51-
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1"/>
52-
<PackageVersion Include="OpenTelemetry.Instrumentation.Process" Version="0.5.0-beta.5"/>
53-
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0"/>
54-
<PackageVersion Include="PolyJson" Version="1.4.0"/>
55-
<PackageVersion Include="Refit" Version="8.0.0"/>
56-
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0"/>
57-
<PackageVersion Include="Sentry" Version="4.12.1"/>
58-
<PackageVersion Include="Sentry.AspNetCore" Version="4.4.0"/>
59-
<PackageVersion Include="Sentry.OpenTelemetry" Version="4.12.1"/>
60-
<PackageVersion Include="Sentry.Serilog" Version="4.12.1"/>
61-
<PackageVersion Include="Serilog" Version="4.0.2"/>
62-
<PackageVersion Include="Serilog.Enrichers.Environment" Version="3.0.1"/>
63-
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0"/>
64-
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4"/>
65-
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
66-
67-
<PackageVersion Include="Bogus" Version="35.5.0"/>
68-
<PackageVersion Include="FastEndpoints.Testing" Version="5.24.0"/>
69-
<PackageVersion Include="FluentAssertions" Version="6.12.0"/>
70-
<PackageVersion Include="FluentAssertions.Analyzers" Version="0.31.0"/>
71-
<PackageVersion Include="FluentAssertions.Json" Version="6.1.0"/>
72-
<PackageVersion Include="FluentValidation" Version="11.9.0"/>
73-
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10"/>
74-
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8"/>
75-
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10"/>
76-
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2"/>
77-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0"/>
78-
<PackageVersion Include="NSubstitute" Version="5.1.0"/>
79-
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17"/>
80-
<PackageVersion Include="NUnit.Analyzers" Version="4.3.0"/>
81-
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0"/>
82-
<PackageVersion Include="Respawn" Version="6.2.1"/>
83-
<PackageVersion Include="Serilog.Sinks.NUnit" Version="1.0.3"/>
84-
<PackageVersion Include="Testcontainers.MsSql" Version="4.0.0"/>
85-
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.0.0"/>
86-
<PackageVersion Include="Xunit.Priority" Version="1.1.6"/>
87-
<PackageVersion Include="coverlet.collector" Version="6.0.2"/>
88-
<PackageVersion Include="nunit" Version="4.2.2"/>
89-
<PackageVersion Include="xunit" Version="2.7.1"/>
90-
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8"/>
91-
</ItemGroup>
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.300" />
9+
<PackageVersion Include="AWSSDK.S3" Version="3.7.307.15" />
10+
<PackageVersion Include="AWSSDK.SimpleEmail" Version="3.7.300.74" />
11+
<PackageVersion Include="AWSSDK.Core" Version="3.7.303.14" />
12+
<PackageVersion Include="Ardalis.SmartEnum" Version="8.0.0" />
13+
<PackageVersion Include="Ardalis.SmartEnum.Dapper" Version="7.0.0" />
14+
<PackageVersion Include="Ardalis.SmartEnum.EFCore" Version="8.0.0" />
15+
<PackageVersion Include="Ardalis.SmartEnum.SystemTextJson" Version="8.0.0" />
16+
<PackageVersion Include="Ardalis.Specification" Version="8.0.0" />
17+
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
18+
<PackageVersion Include="AspNetCore.HealthChecks.Hangfire" Version="8.0.1" />
19+
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="8.0.1" />
20+
<PackageVersion Include="CsvHelper" Version="31.0.3" />
21+
<PackageVersion Include="Dapper" Version="2.1.35" />
22+
<PackageVersion Include="EFCore.BulkExtensions.PostgreSql" Version="8.0.4" />
23+
<PackageVersion Include="FastEndpoints" Version="5.24.0" />
24+
<PackageVersion Include="FastEndpoints.Attributes" Version="5.24.0" />
25+
<PackageVersion Include="FastEndpoints.Security" Version="5.24.0" />
26+
<PackageVersion Include="FastEndpoints.Swagger" Version="5.24.0" />
27+
<PackageVersion Include="FirebaseAdmin" Version="2.4.1" />
28+
<PackageVersion Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" />
29+
<PackageVersion Include="Hangfire" Version="1.8.12" />
30+
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.12" />
31+
<PackageVersion Include="Hangfire.Core" Version="1.8.12" />
32+
<PackageVersion Include="Hangfire.Dashboard.Basic.Authentication" Version="7.0.1" />
33+
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.8" />
34+
<PackageVersion Include="HtmlSanitizer" Version="8.1.870" />
35+
<PackageVersion Include="Humanizer" Version="2.14.1" />
36+
<PackageVersion Include="MailKit" Version="4.5.0" />
37+
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.4" />
38+
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
39+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
40+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8" />
41+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
42+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8" />
43+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
44+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
45+
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
46+
<PackageVersion Include="Minio" Version="6.0.3" />
47+
<PackageVersion Include="NPOI" Version="2.7.0" />
48+
<PackageVersion Include="Npgsql" Version="8.0.4" />
49+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
50+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.8.0" />
51+
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
52+
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
53+
<PackageVersion Include="OpenTelemetry.Instrumentation.Process" Version="0.5.0-beta.5" />
54+
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" />
55+
<PackageVersion Include="PolyJson" Version="1.4.0" />
56+
<PackageVersion Include="Refit" Version="8.0.0" />
57+
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
58+
<PackageVersion Include="Sentry" Version="4.12.1" />
59+
<PackageVersion Include="Sentry.AspNetCore" Version="4.4.0" />
60+
<PackageVersion Include="Sentry.OpenTelemetry" Version="4.12.1" />
61+
<PackageVersion Include="Sentry.Serilog" Version="4.12.1" />
62+
<PackageVersion Include="Serilog" Version="4.0.2" />
63+
<PackageVersion Include="Serilog.Enrichers.Environment" Version="3.0.1" />
64+
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
65+
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
66+
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
67+
<PackageVersion Include="Bogus" Version="35.5.0" />
68+
<PackageVersion Include="FastEndpoints.Testing" Version="5.24.0" />
69+
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
70+
<PackageVersion Include="FluentAssertions.Analyzers" Version="0.31.0" />
71+
<PackageVersion Include="FluentAssertions.Json" Version="6.1.0" />
72+
<PackageVersion Include="FluentValidation" Version="11.9.0" />
73+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" />
74+
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
75+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
76+
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
77+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
78+
<PackageVersion Include="NSubstitute" Version="5.1.0" />
79+
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
80+
<PackageVersion Include="NUnit.Analyzers" Version="4.3.0" />
81+
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
82+
<PackageVersion Include="Respawn" Version="6.2.1" />
83+
<PackageVersion Include="Serilog.Sinks.NUnit" Version="1.0.3" />
84+
<PackageVersion Include="Testcontainers.MsSql" Version="4.0.0" />
85+
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.0.0" />
86+
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
87+
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
88+
<PackageVersion Include="nunit" Version="4.2.2" />
89+
<PackageVersion Include="xunit" Version="2.7.1" />
90+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8" />
91+
</ItemGroup>
9292
</Project>

api/src/Feature.Attachments/AttachmentModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace Feature.Attachments;
22

3+
[Obsolete("Will be removed in future version")]
4+
35
public record AttachmentModel
46
{
57
public required Guid Id { get; init; }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Feature.Attachments;
2+
3+
public record AttachmentModelV2
4+
{
5+
public required Guid Id { get; init; }
6+
public required Guid ElectionRoundId { get; init; }
7+
public Guid SubmissionId { get; init; }
8+
public required Guid QuestionId { get; init; }
9+
public required string FileName { get; init; } = string.Empty;
10+
public required string MimeType { get; init; } = string.Empty;
11+
public required string PresignedUrl { get; init; } = string.Empty;
12+
public required int UrlValidityInSeconds { get; init; }
13+
}
Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,47 @@
11
using Authorization.Policies.Requirements;
2+
using Feature.Attachments.Specifications;
23
using Microsoft.AspNetCore.Authorization;
34

45
namespace Feature.Attachments.Delete;
56

6-
public class Endpoint : Endpoint<Request, Results<NoContent, NotFound, BadRequest<ProblemDetails>>>
7+
public class Endpoint(
8+
IAuthorizationService authorizationService,
9+
IRepository<AttachmentAggregate> repository)
10+
: Endpoint<Request, Results<NoContent, NotFound, BadRequest<ProblemDetails>>>
711
{
8-
private readonly IAuthorizationService _authorizationService;
9-
private readonly IRepository<AttachmentAggregate> _repository;
10-
11-
public Endpoint(IAuthorizationService authorizationService,
12-
IRepository<AttachmentAggregate> repository)
13-
{
14-
_repository = repository;
15-
_authorizationService = authorizationService;
16-
}
17-
1812
public override void Configure()
1913
{
2014
Delete("/api/election-rounds/{electionRoundId}/attachments/{id}");
2115
DontAutoTag();
2216
Options(x => x.WithTags("attachments", "mobile"));
23-
Summary(s => {
17+
Summary(s =>
18+
{
2419
s.Summary = "Deletes an attachment";
2520
});
2621
}
2722

28-
public override async Task<Results<NoContent, NotFound, BadRequest<ProblemDetails>>> ExecuteAsync(Request req, CancellationToken ct)
23+
public override async Task<Results<NoContent, NotFound, BadRequest<ProblemDetails>>> ExecuteAsync(Request req,
24+
CancellationToken ct)
2925
{
30-
var authorizationResult = await _authorizationService.AuthorizeAsync(User, new MonitoringObserverRequirement(req.ElectionRoundId));
26+
var authorizationResult =
27+
await authorizationService.AuthorizeAsync(User, new MonitoringObserverRequirement(req.ElectionRoundId));
3128
if (!authorizationResult.Succeeded)
3229
{
3330
return TypedResults.NotFound();
3431
}
3532

36-
var attachment = await _repository.GetByIdAsync(req.Id, ct);
33+
var specification = new GetAttachmentByIdSpecification(req.ElectionRoundId, req.ObserverId, req.Id);
34+
var attachment = await repository.FirstOrDefaultAsync(specification, ct);
35+
3736
if (attachment is null)
3837
{
3938
return TypedResults.NotFound();
4039
}
4140

4241
attachment.Delete();
4342

44-
await _repository.UpdateAsync(attachment, ct);
45-
43+
await repository.UpdateAsync(attachment, ct);
44+
4645
return TypedResults.NoContent();
4746
}
4847
}

api/src/Feature.Attachments/Get/Endpoint.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Authorization.Policies.Requirements;
2+
using Feature.Attachments.Specifications;
23
using Microsoft.AspNetCore.Authorization;
34
using Vote.Monitor.Core.Services.FileStorage.Contracts;
45

@@ -29,7 +30,9 @@ public override async Task<Results<Ok<AttachmentModel>, BadRequest<ProblemDetail
2930
return TypedResults.NotFound();
3031
}
3132

32-
var attachment = await repository.GetByIdAsync(req.Id, ct);
33+
var specification = new GetAttachmentByIdSpecification(req.ElectionRoundId, req.ObserverId, req.Id);
34+
var attachment = await repository.FirstOrDefaultAsync(specification, ct);
35+
3336
if (attachment is null)
3437
{
3538
return TypedResults.NotFound();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Authorization.Policies.Requirements;
2+
using Feature.Attachments.Specifications;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Vote.Monitor.Core.Services.FileStorage.Contracts;
5+
6+
namespace Feature.Attachments.GetV2;
7+
8+
public class Endpoint(
9+
IAuthorizationService authorizationService,
10+
IReadRepository<AttachmentAggregate> repository,
11+
IFileStorageService fileStorageService)
12+
: Endpoint<Request, Results<Ok<AttachmentModelV2>, BadRequest<ProblemDetails>, NotFound>>
13+
{
14+
public override void Configure()
15+
{
16+
Get("/api/election-rounds/{electionRoundId}/form-submissions/{submissionId}/attachments/{id}");
17+
DontAutoTag();
18+
Options(x => x.WithTags("attachments", "mobile"));
19+
Summary(s => {
20+
s.Summary = "Gets an attachment";
21+
s.Description = "Gets an attachment with freshly generated presigned url";
22+
});
23+
}
24+
25+
public override async Task<Results<Ok<AttachmentModelV2>, BadRequest<ProblemDetails>, NotFound>> ExecuteAsync(Request req, CancellationToken ct)
26+
{
27+
var authorizationResult = await authorizationService.AuthorizeAsync(User, new MonitoringObserverRequirement(req.ElectionRoundId));
28+
if (!authorizationResult.Succeeded)
29+
{
30+
return TypedResults.NotFound();
31+
}
32+
33+
var specification = new GetAttachmentByIdSpecification(req.ElectionRoundId, req.ObserverId, req.Id);
34+
var attachment = await repository.FirstOrDefaultAsync(specification, ct);
35+
36+
if (attachment is null)
37+
{
38+
return TypedResults.NotFound();
39+
}
40+
41+
var presignedUrl = await fileStorageService.GetPresignedUrlAsync(attachment.FilePath, attachment.UploadedFileName);
42+
43+
return TypedResults.Ok(new AttachmentModelV2
44+
{
45+
Id = attachment.Id,
46+
ElectionRoundId = req.ElectionRoundId,
47+
FileName = attachment.FileName,
48+
PresignedUrl = (presignedUrl as GetPresignedUrlResult.Ok)?.Url ?? string.Empty,
49+
MimeType = attachment.MimeType,
50+
UrlValidityInSeconds = (presignedUrl as GetPresignedUrlResult.Ok)?.UrlValidityInSeconds ?? 0,
51+
SubmissionId = attachment.SubmissionId,
52+
QuestionId = attachment.QuestionId
53+
});
54+
}
55+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Vote.Monitor.Core.Security;
2+
3+
namespace Feature.Attachments.GetV2;
4+
5+
public class Request
6+
{
7+
public Guid ElectionRoundId { get; set; }
8+
9+
public Guid SubmissionId { get; set; }
10+
11+
[FromClaim(ApplicationClaimTypes.UserId)]
12+
public Guid ObserverId { get; set; }
13+
14+
public Guid Id { get; set; }
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Feature.Attachments.GetV2;
2+
3+
public class Validator : Validator<Request>
4+
{
5+
public Validator()
6+
{
7+
RuleFor(x => x.ElectionRoundId).NotEmpty();
8+
RuleFor(x => x.SubmissionId).NotEmpty();
9+
RuleFor(x => x.ObserverId).NotEmpty();
10+
RuleFor(x => x.Id).NotEmpty();
11+
}
12+
}

0 commit comments

Comments
 (0)