diff --git a/src/ProjectOrigin.WalletSystem.IntegrationTests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt b/src/ProjectOrigin.WalletSystem.IntegrationTests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
index 969fbbcb..7dde4053 100644
--- a/src/ProjectOrigin.WalletSystem.IntegrationTests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
+++ b/src/ProjectOrigin.WalletSystem.IntegrationTests/ApiTests/ApiTests.open_api_specification_not_changed.verified.txt
@@ -13,7 +13,7 @@
"summary": "Gets all certificates in the wallet that are available for use.",
"parameters": [
{
- "name": "start",
+ "name": "Start",
"in": "query",
"description": "The start of the time range in Unix time in seconds.",
"schema": {
@@ -22,7 +22,7 @@
}
},
{
- "name": "end",
+ "name": "End",
"in": "query",
"description": "The end of the time range in Unix time in seconds.",
"schema": {
@@ -31,7 +31,7 @@
}
},
{
- "name": "type",
+ "name": "Type",
"in": "query",
"description": "Filter the type of certificates to return.",
"schema": {
@@ -39,7 +39,7 @@
}
},
{
- "name": "limit",
+ "name": "Limit",
"in": "query",
"description": "The number of items to return.",
"schema": {
@@ -48,7 +48,7 @@
}
},
{
- "name": "skip",
+ "name": "Skip",
"in": "query",
"description": "The number of items to skip.",
"schema": {
@@ -83,15 +83,15 @@
"summary": "Returns aggregates certificates that are available to use, based on the specified time zone and time range.",
"parameters": [
{
- "name": "timeAggregate",
+ "name": "TimeAggregate",
"in": "query",
- "description": "The size of each bucket in the aggregation",
+ "description": "The size of each bucket in the aggregation.",
"schema": {
"$ref": "#/components/schemas/TimeAggregate"
}
},
{
- "name": "timeZone",
+ "name": "TimeZone",
"in": "query",
"description": "The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.",
"schema": {
@@ -99,7 +99,7 @@
}
},
{
- "name": "start",
+ "name": "Start",
"in": "query",
"description": "The start of the time range in Unix time in seconds.",
"schema": {
@@ -108,7 +108,7 @@
}
},
{
- "name": "end",
+ "name": "End",
"in": "query",
"description": "The end of the time range in Unix time in seconds.",
"schema": {
@@ -117,7 +117,7 @@
}
},
{
- "name": "type",
+ "name": "Type",
"in": "query",
"description": "Filter the type of certificates to return.",
"schema": {
@@ -125,7 +125,7 @@
}
},
{
- "name": "limit",
+ "name": "Limit",
"in": "query",
"description": "The number of items to return.",
"schema": {
@@ -134,7 +134,7 @@
}
},
{
- "name": "skip",
+ "name": "Skip",
"in": "query",
"description": "The number of items to skip.",
"schema": {
@@ -172,7 +172,7 @@
"summary": "Gets all claims in the wallet",
"parameters": [
{
- "name": "start",
+ "name": "Start",
"in": "query",
"description": "The start of the time range in Unix time in seconds.",
"schema": {
@@ -181,7 +181,7 @@
}
},
{
- "name": "end",
+ "name": "End",
"in": "query",
"description": "The end of the time range in Unix time in seconds.",
"schema": {
@@ -190,7 +190,7 @@
}
},
{
- "name": "limit",
+ "name": "Limit",
"in": "query",
"description": "The number of items to return.",
"schema": {
@@ -199,7 +199,7 @@
}
},
{
- "name": "skip",
+ "name": "Skip",
"in": "query",
"description": "The number of items to skip.",
"schema": {
@@ -275,7 +275,7 @@
"summary": "Returns a list of aggregates claims for the authenticated user based on the specified time zone and time range.",
"parameters": [
{
- "name": "timeAggregate",
+ "name": "TimeAggregate",
"in": "query",
"description": "The size of each bucket in the aggregation",
"schema": {
@@ -283,7 +283,7 @@
}
},
{
- "name": "timeZone",
+ "name": "TimeZone",
"in": "query",
"description": "The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.",
"schema": {
@@ -291,7 +291,7 @@
}
},
{
- "name": "start",
+ "name": "Start",
"in": "query",
"description": "The start of the time range in Unix time in seconds.",
"schema": {
@@ -300,7 +300,7 @@
}
},
{
- "name": "end",
+ "name": "End",
"in": "query",
"description": "The end of the time range in Unix time in seconds.",
"schema": {
@@ -309,7 +309,7 @@
}
},
{
- "name": "limit",
+ "name": "Limit",
"in": "query",
"description": "The number of items to return.",
"schema": {
@@ -318,7 +318,7 @@
}
},
{
- "name": "skip",
+ "name": "Skip",
"in": "query",
"description": "The number of items to skip.",
"schema": {
diff --git a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/CertificatesControllerTests.cs b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/CertificatesControllerTests.cs
index 06baa14c..356688c4 100644
--- a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/CertificatesControllerTests.cs
+++ b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/CertificatesControllerTests.cs
@@ -34,12 +34,7 @@ public async Task Verify_Unauthorized()
var controller = new CertificatesController();
// Act
- var result = await controller.GetCertificates(
- _unitOfWork,
- null,
- null,
- null,
- null);
+ var result = await controller.GetCertificates(_unitOfWork, new GetCertificatesQueryParameters());
// Assert
result.Result.Should().BeOfType();
@@ -88,12 +83,14 @@ public async Task Test_AggregateCertificates(string timezone, long[] values, Cer
// Act
var result = await controller.AggregateCertificates(
_unitOfWork,
- TimeAggregate.Day,
- timezone,
- queryStartDate.ToUnixTimeSeconds(),
- queryEndDate.ToUnixTimeSeconds(),
- type,
- null);
+ new AggregateCertificatesQueryParameters
+ {
+ TimeAggregate = TimeAggregate.Day,
+ TimeZone = timezone,
+ Start = queryStartDate.ToUnixTimeSeconds(),
+ End = queryEndDate.ToUnixTimeSeconds(),
+ Type = type
+ });
// Assert
result.Value.Should().NotBeNull();
@@ -116,12 +113,11 @@ public async Task AggregateCertificates_Invalid_TimeZone()
// Act
var result = await controller.AggregateCertificates(
_unitOfWork,
- TimeAggregate.Day,
- "invalid-time-zone",
- null,
- null,
- null,
- null);
+ new AggregateCertificatesQueryParameters
+ {
+ TimeAggregate = TimeAggregate.Day,
+ TimeZone = "invalid-time-zone",
+ });
// Assert
result.Result.Should().BeOfType();
diff --git a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/ClaimsControllerTests.cs b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/ClaimsControllerTests.cs
index 28edff29..68a7a7a9 100644
--- a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/ClaimsControllerTests.cs
+++ b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/ClaimsControllerTests.cs
@@ -40,9 +40,7 @@ public async Task Verify_Unauthorized()
// Act
var result = await controller.GetClaims(
_unitOfWork,
- null,
- null,
- null);
+ new GetClaimsQueryParameters());
// Assert
result.Result.Should().BeOfType();
@@ -93,11 +91,13 @@ public async Task Test_AggregateClaims(string timezone, long[] values)
// Act
var result = await controller.AggregateClaims(
_unitOfWork,
- TimeAggregate.Day,
- timezone,
- queryStartDate.ToUnixTimeSeconds(),
- queryEndDate.ToUnixTimeSeconds(),
- null);
+ new AggregateClaimsQueryParameters
+ {
+ TimeAggregate = TimeAggregate.Day,
+ TimeZone = timezone,
+ Start = queryStartDate.ToUnixTimeSeconds(),
+ End = queryEndDate.ToUnixTimeSeconds()
+ });
// Assert
result.Value.Should().NotBeNull();
@@ -120,11 +120,11 @@ public async Task AggregateClaims_Invalid_TimeZone()
// Act
var result = await controller.AggregateClaims(
_unitOfWork,
- TimeAggregate.Day,
- "invalid-time-zone",
- null,
- null,
- null);
+ new AggregateClaimsQueryParameters
+ {
+ TimeAggregate = TimeAggregate.Day,
+ TimeZone = "invalid-time-zone"
+ });
// Assert
result.Result.Should().BeOfType();
diff --git a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/WalletControllerTests.cs b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/WalletControllerTests.cs
index 918b7a13..9d4345e6 100644
--- a/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/WalletControllerTests.cs
+++ b/src/ProjectOrigin.WalletSystem.IntegrationTests/REST/WalletControllerTests.cs
@@ -139,7 +139,7 @@ public async Task Verify_Valid()
}
[Fact]
- public async Task Verify_OnlySingleAllowedPerSubject()
+ public async Task Verify_MultipleWalletsAllowedPerSubject()
{
// Arrange
var subject = _fixture.Create();
@@ -154,7 +154,8 @@ public async Task Verify_OnlySingleAllowedPerSubject()
_options,
new CreateWalletRequest());
- firstCreateResult.Result.Should().BeOfType();
+ var firstResponse = firstCreateResult.Result.Should().BeOfType()
+ .Which.Value.Should().BeOfType().Which;
// Act
var secondCreateResult = await controller.CreateWallet(
@@ -164,8 +165,9 @@ public async Task Verify_OnlySingleAllowedPerSubject()
new CreateWalletRequest());
// Assert
- secondCreateResult.Result.Should().BeOfType()
- .Which.Value.Should().Be("Wallet already exists.");
+ var secondResponse = secondCreateResult.Result.Should().BeOfType()
+ .Which.Value.Should().BeOfType().Which;
+ firstResponse.WalletId.Should().NotBe(secondResponse.WalletId);
}
[Fact]
diff --git a/src/ProjectOrigin.WalletSystem.IntegrationTests/Repositories/CertificateRepositoryTests.cs b/src/ProjectOrigin.WalletSystem.IntegrationTests/Repositories/CertificateRepositoryTests.cs
index 275d4f06..b7f18cdc 100644
--- a/src/ProjectOrigin.WalletSystem.IntegrationTests/Repositories/CertificateRepositoryTests.cs
+++ b/src/ProjectOrigin.WalletSystem.IntegrationTests/Repositories/CertificateRepositoryTests.cs
@@ -417,22 +417,8 @@ public async Task GetAvailableSlice()
{
var registry = _fixture.Create();
var wallet1 = await CreateWallet(_fixture.Create());
- var endpointPosition = 1;
- var endpoint = await CreateWalletEndpoint(wallet1);
- var certificate = await CreateCertificate(registry);
- var slice = new WalletSlice
- {
- Id = Guid.NewGuid(),
- WalletEndpointId = endpoint.Id,
- WalletEndpointPosition = endpointPosition,
- RegistryName = registry,
- CertificateId = certificate.Id,
- Quantity = _fixture.Create(),
- RandomR = _fixture.Create(),
- State = WalletSliceState.Available
- };
- await _certRepository.InsertWalletSlice(slice);
+ var slice = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet1), 1);
var sliceDb = await _certRepository.GetOwnersAvailableSlices(registry, slice.CertificateId, wallet1.Owner);
@@ -440,6 +426,25 @@ public async Task GetAvailableSlice()
sliceDb.Should().ContainEquivalentOf(slice);
}
+ [Fact]
+ public async Task QueryAvailableCertificates_MultipleWallets()
+ {
+ var registry = _fixture.Create();
+ var subject = _fixture.Create();
+ var wallet1 = await CreateWallet(subject);
+ var wallet2 = await CreateWallet(subject);
+ var slice1 = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet1), 1);
+ var slice2 = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet2), 1);
+
+ var result = await _certRepository.QueryAvailableCertificates(new CertificatesFilter
+ {
+ Owner = subject
+ });
+
+ result.Count.Should().Be(2);
+ result.Items.Sum(x => x.Quantity).Should().Be(slice1.Quantity + slice2.Quantity);
+ }
+
[Fact]
public async Task GetAvailableSlice_WhenSliceStateOtherThanAvailable_ExpectNull()
{
@@ -513,25 +518,12 @@ public async Task ReserveSlice()
{
// Arrange
var registry = _fixture.Create();
- var certificate = await CreateCertificate(registry);
var owner = _fixture.Create();
var wallet = await CreateWallet(owner);
- var endpoint = await CreateWalletEndpoint(wallet);
- var slice = new WalletSlice
- {
- Id = Guid.NewGuid(),
- WalletEndpointId = endpoint.Id,
- WalletEndpointPosition = 1,
- RegistryName = registry,
- CertificateId = certificate.Id,
- Quantity = 150,
- RandomR = _fixture.Create(),
- State = WalletSliceState.Available
- };
- await _certRepository.InsertWalletSlice(slice);
+ var slice = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet), 1, quantity: 150);
// Act
- var reservedSlices = await _certRepository.ReserveQuantity(owner, certificate.RegistryName, certificate.Id, 100);
+ var reservedSlices = await _certRepository.ReserveQuantity(owner, slice.RegistryName, slice.CertificateId, 100);
// Assert
reservedSlices.Should().ContainEquivalentOf(slice);
@@ -557,25 +549,13 @@ public async Task ReserveSlice_LessThan_ThrowsException()
{
// Arrange
var registry = _fixture.Create();
- var certificate = await CreateCertificate(registry);
var owner = _fixture.Create();
var wallet = await CreateWallet(owner);
var endpoint = await CreateWalletEndpoint(wallet);
- var slice = new WalletSlice
- {
- Id = Guid.NewGuid(),
- WalletEndpointId = endpoint.Id,
- WalletEndpointPosition = 1,
- RegistryName = registry,
- CertificateId = certificate.Id,
- Quantity = 150,
- RandomR = _fixture.Create(),
- State = WalletSliceState.Available
- };
- await _certRepository.InsertWalletSlice(slice);
+ var slice = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet), 1, quantity: 150);
// Act
- var act = () => _certRepository.ReserveQuantity(owner, certificate.RegistryName, certificate.Id, 200);
+ var act = () => _certRepository.ReserveQuantity(owner, slice.RegistryName, slice.CertificateId, 200);
// Assert
await act.Should().ThrowAsync().WithMessage("Owner has less to reserve than available");
@@ -586,22 +566,10 @@ public async Task ReserveSlice_ToBe_ThrowsException()
{
// Arrange
var registry = _fixture.Create();
- var certificate = await CreateCertificate(registry);
var owner = _fixture.Create();
var wallet = await CreateWallet(owner);
- var endpoint = await CreateWalletEndpoint(wallet);
- var slice = new WalletSlice
- {
- Id = Guid.NewGuid(),
- WalletEndpointId = endpoint.Id,
- WalletEndpointPosition = 1,
- RegistryName = registry,
- CertificateId = certificate.Id,
- Quantity = 150,
- RandomR = _fixture.Create(),
- State = WalletSliceState.Available
- };
- await _certRepository.InsertWalletSlice(slice);
+
+ var slice = await CreateAndInsertCertificateWithSlice(registry, await CreateWalletEndpoint(wallet), 1, quantity: 150);
await _certRepository.InsertWalletSlice(slice with
{
Id = Guid.NewGuid(),
@@ -611,7 +579,7 @@ await _certRepository.InsertWalletSlice(slice with
});
// Act
- var act = () => _certRepository.ReserveQuantity(owner, certificate.RegistryName, certificate.Id, 200);
+ var act = () => _certRepository.ReserveQuantity(owner, slice.RegistryName, slice.CertificateId, 200);
// Assert
await act.Should().ThrowAsync().WithMessage("Owner has enough quantity, but it is not yet available to reserve");
@@ -748,4 +716,30 @@ private async Task CreateCertificatesAndSlices(Wallet wallet, int numberOfCertif
await _certRepository.InsertWalletSlice(slice);
}
}
+
+ private async Task CreateAndInsertCertificateWithSlice(
+ string registry,
+ WalletEndpoint endpoint,
+ int endpointPosition,
+ int? quantity = null)
+ {
+ quantity = quantity ?? _fixture.Create();
+
+ var certificate = await CreateCertificate(registry);
+ var slice = new WalletSlice
+ {
+ Id = Guid.NewGuid(),
+ WalletEndpointId = endpoint.Id,
+ WalletEndpointPosition = endpointPosition,
+ RegistryName = registry,
+ CertificateId = certificate.Id,
+ Quantity = quantity.Value,
+ RandomR = _fixture.Create(),
+ State = WalletSliceState.Available
+ };
+
+ await _certRepository.InsertWalletSlice(slice);
+
+ return slice;
+ }
}
diff --git a/src/ProjectOrigin.WalletSystem.Server/Database/Postgres/Scripts/v2/v2-0005.sql b/src/ProjectOrigin.WalletSystem.Server/Database/Postgres/Scripts/v2/v2-0005.sql
new file mode 100644
index 00000000..523b5abd
--- /dev/null
+++ b/src/ProjectOrigin.WalletSystem.Server/Database/Postgres/Scripts/v2/v2-0005.sql
@@ -0,0 +1,2 @@
+-- Modify the wallets table to remove the unique constraint on the owner column
+ALTER TABLE wallets DROP CONSTRAINT wallets_owner_key1;
diff --git a/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/CertificatesController.cs b/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/CertificatesController.cs
index 19969fb2..b53e7ebf 100644
--- a/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/CertificatesController.cs
+++ b/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/CertificatesController.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -18,12 +18,6 @@ public class CertificatesController : ControllerBase
///
/// Gets all certificates in the wallet that are available for use.
///
- ///
- /// The start of the time range in Unix time in seconds.
- /// The end of the time range in Unix time in seconds.
- /// Filter the type of certificates to return.
- /// The number of items to skip.
- /// The number of items to return.
/// Returns the aggregated claims.
/// If the user is not authenticated.
[HttpGet]
@@ -33,22 +27,18 @@ public class CertificatesController : ControllerBase
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
public async Task>> GetCertificates(
[FromServices] IUnitOfWork unitOfWork,
- [FromQuery] long? start,
- [FromQuery] long? end,
- [FromQuery] CertificateType? type,
- [FromQuery] int? limit,
- [FromQuery] int skip = 0)
+ [FromQuery] GetCertificatesQueryParameters param)
{
if (!User.TryGetSubject(out var subject)) return Unauthorized();
var certificates = await unitOfWork.CertificateRepository.QueryAvailableCertificates(new CertificatesFilter
{
Owner = subject,
- Start = start != null ? DateTimeOffset.FromUnixTimeSeconds(start.Value) : null,
- End = end != null ? DateTimeOffset.FromUnixTimeSeconds(end.Value) : null,
- Type = type != null ? (GranularCertificateType)type.Value : null,
- Skip = skip,
- Limit = limit ?? int.MaxValue
+ Start = param.Start != null ? DateTimeOffset.FromUnixTimeSeconds(param.Start.Value) : null,
+ End = param.End != null ? DateTimeOffset.FromUnixTimeSeconds(param.End.Value) : null,
+ Type = param.Type != null ? (GranularCertificateType)param.Type.Value : null,
+ Skip = param.Skip,
+ Limit = param.Limit ?? int.MaxValue
});
return certificates.ToResultList(c => c.MapToV1());
@@ -57,14 +47,6 @@ public async Task>> GetCertificates
///
/// Returns aggregates certificates that are available to use, based on the specified time zone and time range.
///
- ///
- /// The size of each bucket in the aggregation
- /// The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.
- /// The start of the time range in Unix time in seconds.
- /// The end of the time range in Unix time in seconds.
- /// Filter the type of certificates to return.
- /// The number of items to skip.
- /// The number of items to return.
/// Returns the aggregated claims.
/// If the time zone is invalid.
/// If the user is not authenticated.
@@ -76,26 +58,22 @@ public async Task>> GetCertificates
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
public async Task>> AggregateCertificates(
[FromServices] IUnitOfWork unitOfWork,
- [FromQuery] TimeAggregate timeAggregate,
- [FromQuery] string timeZone,
- [FromQuery] long? start,
- [FromQuery] long? end,
- [FromQuery] CertificateType? type,
- [FromQuery] int? limit,
- [FromQuery] int skip = 0)
+ [FromQuery] AggregateCertificatesQueryParameters parameters)
{
if (!User.TryGetSubject(out var subject)) return Unauthorized();
- if (!timeZone.TryParseTimeZone(out var timeZoneInfo)) return BadRequest("Invalid time zone");
+ if (!parameters.TimeZone.TryParseTimeZone(out var timeZoneInfo)) return BadRequest("Invalid time zone");
var certificates = await unitOfWork.CertificateRepository.QueryAggregatedAvailableCertificates(new CertificatesFilter
{
Owner = subject,
- Start = start != null ? DateTimeOffset.FromUnixTimeSeconds(start.Value) : null,
- End = end != null ? DateTimeOffset.FromUnixTimeSeconds(end.Value) : null,
- Type = type != null ? (GranularCertificateType)type.Value : null,
- Skip = skip,
- Limit = limit ?? int.MaxValue
- }, (Models.TimeAggregate)timeAggregate, timeZone);
+ Start = parameters.Start != null ? DateTimeOffset.FromUnixTimeSeconds(parameters.Start.Value) : null,
+ End = parameters.End != null ? DateTimeOffset.FromUnixTimeSeconds(parameters.End.Value) : null,
+ Type = parameters.Type != null ? (GranularCertificateType)parameters.Type.Value : null,
+ Skip = parameters.Skip,
+ Limit = parameters.Limit ?? int.MaxValue
+ },
+ (Models.TimeAggregate)parameters.TimeAggregate,
+ parameters.TimeZone);
return certificates.ToResultList(c => new AggregatedCertificates
{
@@ -108,6 +86,73 @@ public async Task>> AggregateCer
}
#region Records
+public record GetCertificatesQueryParameters
+{
+ ///
+ /// The start of the time range in Unix time in seconds.
+ ///
+ public long? Start { get; init; }
+
+ ///
+ /// The end of the time range in Unix time in seconds.
+ ///
+ public long? End { get; init; }
+
+ ///
+ /// Filter the type of certificates to return.
+ ///
+ public CertificateType? Type { get; init; }
+
+ ///
+ /// The number of items to return.
+ ///
+ public int? Limit { get; init; }
+
+ ///
+ /// The number of items to skip.
+ ///
+ [DefaultValue(0)]
+ public int Skip { get; init; }
+}
+
+public record AggregateCertificatesQueryParameters
+{
+ ///
+ /// The size of each bucket in the aggregation.
+ ///
+ public required TimeAggregate TimeAggregate { get; init; }
+
+ ///
+ /// The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.
+ ///
+ public required string TimeZone { get; init; }
+
+ ///
+ ///The start of the time range in Unix time in seconds.
+ ///
+ public long? Start { get; init; }
+
+ ///
+ /// The end of the time range in Unix time in seconds.
+ ///
+ public long? End { get; init; }
+
+ ///
+ /// Filter the type of certificates to return.
+ ///
+ public CertificateType? Type { get; init; }
+
+ ///
+ /// The number of items to return.
+ ///
+ public int? Limit { get; init; }
+
+ ///
+ /// The number of items to skip.
+ ///
+ [DefaultValue(0)]
+ public int Skip { get; init; }
+}
///
/// A certificate that is available to use in the wallet.
diff --git a/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/ClaimsController.cs b/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/ClaimsController.cs
index c356da44..65742067 100644
--- a/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/ClaimsController.cs
+++ b/src/ProjectOrigin.WalletSystem.Server/Services/REST/v1/ClaimsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using MassTransit;
@@ -20,11 +21,6 @@ public class ClaimsController : ControllerBase
///
/// Gets all claims in the wallet
///
- ///
- /// The start of the time range in Unix time in seconds.
- /// The end of the time range in Unix time in seconds.
- /// The number of items to skip.
- /// The number of items to return.
/// Returns all the indiviual claims.
/// If the user is not authenticated.
[HttpGet]
@@ -34,20 +30,17 @@ public class ClaimsController : ControllerBase
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
public async Task>> GetClaims(
[FromServices] IUnitOfWork unitOfWork,
- [FromQuery] long? start,
- [FromQuery] long? end,
- [FromQuery] int? limit,
- [FromQuery] int skip = 0)
+ [FromQuery] GetClaimsQueryParameters param)
{
if (!User.TryGetSubject(out var subject)) return Unauthorized();
var claims = await unitOfWork.ClaimRepository.QueryClaims(new ClaimFilter
{
Owner = subject,
- Start = start != null ? DateTimeOffset.FromUnixTimeSeconds(start.Value) : null,
- End = end != null ? DateTimeOffset.FromUnixTimeSeconds(end.Value) : null,
- Skip = skip,
- Limit = limit ?? int.MaxValue,
+ Start = param.Start != null ? DateTimeOffset.FromUnixTimeSeconds(param.Start.Value) : null,
+ End = param.End != null ? DateTimeOffset.FromUnixTimeSeconds(param.End.Value) : null,
+ Skip = param.Skip,
+ Limit = param.Limit ?? int.MaxValue,
});
return claims.ToResultList(c => c.MapToV1());
@@ -56,13 +49,6 @@ public async Task>> GetClaims(
///
/// Returns a list of aggregates claims for the authenticated user based on the specified time zone and time range.
///
- ///
- /// The size of each bucket in the aggregation
- /// The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.
- /// The start of the time range in Unix time in seconds.
- /// The end of the time range in Unix time in seconds.
- /// The number of items to skip.
- /// The number of items to return.
/// Returns the aggregated claims.
/// If the time zone is invalid.
/// If the user is not authenticated.
@@ -74,24 +60,19 @@ public async Task>> GetClaims(
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
public async Task>> AggregateClaims(
[FromServices] IUnitOfWork unitOfWork,
- [FromQuery] TimeAggregate timeAggregate,
- [FromQuery] string timeZone,
- [FromQuery] long? start,
- [FromQuery] long? end,
- [FromQuery] int? limit,
- [FromQuery] int skip = 0)
+ [FromQuery] AggregateClaimsQueryParameters param)
{
if (!User.TryGetSubject(out var subject)) return Unauthorized();
- if (!timeZone.TryParseTimeZone(out var timeZoneInfo)) return BadRequest("Invalid time zone");
+ if (!param.TimeZone.TryParseTimeZone(out var timeZoneInfo)) return BadRequest("Invalid time zone");
var result = await unitOfWork.ClaimRepository.QueryAggregatedClaims(new ClaimFilter
{
Owner = subject,
- Start = start != null ? DateTimeOffset.FromUnixTimeSeconds(start.Value) : null,
- End = end != null ? DateTimeOffset.FromUnixTimeSeconds(end.Value) : null,
- Skip = skip,
- Limit = limit ?? int.MaxValue
- }, (Models.TimeAggregate)timeAggregate, timeZone);
+ Start = param.Start != null ? DateTimeOffset.FromUnixTimeSeconds(param.Start.Value) : null,
+ End = param.End != null ? DateTimeOffset.FromUnixTimeSeconds(param.End.Value) : null,
+ Skip = param.Skip,
+ Limit = param.Limit ?? int.MaxValue
+ }, (Models.TimeAggregate)param.TimeAggregate, param.TimeZone);
return result.ToResultList(c => new AggregatedClaims()
{
@@ -142,6 +123,64 @@ [FromBody] ClaimRequest request
#region Records
+public record GetClaimsQueryParameters
+{
+ ///
+ /// The start of the time range in Unix time in seconds.
+ ///
+ public long? Start { get; init; }
+
+ ///
+ /// The end of the time range in Unix time in seconds.
+ ///
+ public long? End { get; init; }
+
+ ///
+ /// The number of items to return.
+ ///
+ public int? Limit { get; init; }
+
+ ///
+ /// The number of items to skip.
+ ///
+ [DefaultValue(0)]
+ public int Skip { get; init; }
+}
+
+public record AggregateClaimsQueryParameters
+{
+ ///
+ /// The size of each bucket in the aggregation
+ ///
+ public required TimeAggregate TimeAggregate { get; init; }
+
+ ///
+ /// The time zone. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones.
+ ///
+ public required string TimeZone { get; init; }
+
+ ///
+ /// The start of the time range in Unix time in seconds.
+ ///
+ public long? Start { get; init; }
+
+ ///
+ /// The end of the time range in Unix time in seconds.
+ ///
+ public long? End { get; init; }
+
+ ///
+ /// The number of items to return.
+ ///
+ public int? Limit { get; init; }
+
+ ///
+ /// The number of items to skip.
+ ///
+ [DefaultValue(0)]
+ public int Skip { get; init; }
+}
+
///
/// A claim record representing a claim of a production and consumption certificate.
///