Skip to content

Commit

Permalink
Merge branch 'main' into ABL-174-admin-api-send-announcements-to-spec…
Browse files Browse the repository at this point in the history
…ify-identities
  • Loading branch information
tnotheis authored Jan 17, 2025
2 parents 6e170dc + 7366fc4 commit 9328f7f
Show file tree
Hide file tree
Showing 99 changed files with 527 additions and 182 deletions.
2 changes: 1 addition & 1 deletion .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
// we don't want to update ArchUnitNET currently, because there is a bug in the latest version (see https://github.com/TNG/ArchUnitNET/issues/320)
matchDatasources: ["nuget"],
packageNames: ["TngTech.ArchUnitNET.xUnit"],
packageNames: ["TngTech.ArchUnitNET.xUnit", "FluentAssertions", "FluentAssertions.Json"],
enabled: false
},

Expand Down
17 changes: 17 additions & 0 deletions Applications/AdminApi/http/Tokens/List Tokens.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
meta {
name: /Tokens
type: http
seq: 1
}

get {
url: {{baseUrl}}/Tokens?createdBy=did:e:localhost:dids:c179861648989f28d189c9&PageNumber=1&PageSize=1
body: none
auth: inherit
}

params:query {
createdBy: did:e:localhost:dids:c179861648989f28d189c9
PageNumber: 1
PageSize: 1
}
6 changes: 4 additions & 2 deletions Applications/AdminApi/src/AdminApi/AdminApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="9.1.1" />
<PackageReference Include="Microsoft.AspNetCore.OData" Version="9.1.3" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.24.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.1" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
Expand All @@ -37,6 +37,8 @@
<ProjectReference Include="..\..\..\..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Quotas\src\Quotas.Application\Quotas.Application.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Quotas\src\Quotas.Infrastructure\Quotas.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Tokens\src\Tokens.Application\Tokens.Application.csproj" />
<ProjectReference Include="..\..\..\..\Modules\Tokens\src\Tokens.Infrastructure\Tokens.Infrastructure.csproj" />
<ProjectReference Include="..\AdminApi.Infrastructure.Database.Postgres\AdminApi.Infrastructure.Database.Postgres.csproj" />
<ProjectReference Include="..\AdminApi.Infrastructure.Database.SqlServer\AdminApi.Infrastructure.Database.SqlServer.csproj" />
<ProjectReference Include="..\AdminApi.Infrastructure\AdminApi.Infrastructure.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using Backbone.Modules.Tokens.Application;

namespace Backbone.AdminApi.Configuration;

public class TokensConfiguration
{
[Required]
public ApplicationOptions Application { get; set; } = new();

[Required]
public InfrastructureConfiguration Infrastructure { get; set; } = new();

public class InfrastructureConfiguration
{
[Required]
public SqlDatabaseConfiguration SqlDatabase { get; set; } = new();

public class SqlDatabaseConfiguration
{
[Required]
[MinLength(1)]
[RegularExpression("SqlServer|Postgres")]
public string Provider { get; set; } = string.Empty;

[Required]
[MinLength(1)]
public string ConnectionString { get; set; } = string.Empty;

[Required]
public bool EnableHealthCheck { get; set; } = true;
}
}
}
38 changes: 38 additions & 0 deletions Applications/AdminApi/src/AdminApi/Controllers/TokensController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Backbone.BuildingBlocks.API.Mvc;
using Backbone.BuildingBlocks.Application.Abstractions.Exceptions;
using Backbone.BuildingBlocks.Application.Pagination;
using Backbone.Modules.Tokens.Application;
using Backbone.Modules.Tokens.Application.Tokens.DTOs;
using Backbone.Modules.Tokens.Application.Tokens.Queries.ListTokensByIdentity;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using ApplicationException = Backbone.BuildingBlocks.Application.Abstractions.Exceptions.ApplicationException;

namespace Backbone.AdminApi.Controllers;

[Route("api/v1/[controller]")]
[Authorize("ApiKey")]
public class TokensController(IMediator mediator, IOptions<ApplicationOptions> options) : ApiControllerBase(mediator)
{
[HttpGet]
[ProducesResponseType(typeof(List<TokenDTO>), StatusCodes.Status200OK)]
public async Task<IActionResult> ListTokensByIdentity([FromQuery] PaginationFilter paginationFilter, [FromQuery] string createdBy, CancellationToken cancellationToken)
{
if (paginationFilter.PageSize != null)
{
var maxPageSize = options.Value.Pagination.MaxPageSize;

if (paginationFilter.PageSize > maxPageSize)
{
throw new ApplicationException(GenericApplicationErrors.Validation.InvalidPageSize(maxPageSize));
}
}

var request = new ListTokensByIdentityQuery(createdBy, paginationFilter);
var pagedResult = await _mediator.Send(request, cancellationToken);

return Paged(pagedResult);
}
}
14 changes: 9 additions & 5 deletions Applications/AdminApi/src/AdminApi/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,22 @@ COPY ["Modules/Announcements/src/Announcements.Application/Announcements.Applica
COPY ["Modules/Announcements/src/Announcements.Domain/Announcements.Domain.csproj", "Modules/Announcements/src/Announcements.Domain/"]
COPY ["Modules/Announcements/src/Announcements.Infrastructure/Announcements.Infrastructure.csproj", "Modules/Announcements/src/Announcements.Infrastructure/"]

COPY ["Modules/Devices/src/Devices.Domain/Devices.Domain.csproj", "Modules/Devices/src/Devices.Domain/"]
COPY ["Modules/Devices/src/Devices.Infrastructure/Devices.Infrastructure.csproj", "Modules/Devices/src/Devices.Infrastructure/"]
COPY ["Modules/Devices/src/Devices.Application/Devices.Application.csproj", "Modules/Devices/src/Devices.Application/"]

COPY ["Modules/Challenges/src/Challenges.Application/Challenges.Application.csproj", "Modules/Challenges/src/Challenges.Application/"]
COPY ["Modules/Challenges/src/Challenges.Domain/Challenges.Domain.csproj", "Modules/Challenges/src/Challenges.Domain/"]
COPY ["Modules/Challenges/src/Challenges.Infrastructure/Challenges.Infrastructure.csproj", "Modules/Challenges/src/Challenges.Infrastructure/"]

COPY ["Modules/Devices/src/Devices.Domain/Devices.Domain.csproj", "Modules/Devices/src/Devices.Domain/"]
COPY ["Modules/Devices/src/Devices.Infrastructure/Devices.Infrastructure.csproj", "Modules/Devices/src/Devices.Infrastructure/"]
COPY ["Modules/Devices/src/Devices.Application/Devices.Application.csproj", "Modules/Devices/src/Devices.Application/"]

COPY ["Modules/Quotas/src/Quotas.Application/Quotas.Application.csproj", "Modules/Quotas/src/Quotas.Application/"]
COPY ["Modules/Quotas/src/Quotas.Domain/Quotas.Domain.csproj", "Modules/Quotas/src/Quotas.Domain/"]
COPY ["Modules/Quotas/src/Quotas.Infrastructure/Quotas.Infrastructure.csproj", "Modules/Quotas/src/Quotas.Infrastructure/"]

COPY ["Modules/Tokens/src/Tokens.Application/Tokens.Application.csproj", "Modules/Tokens/src/Tokens.Application/"]
COPY ["Modules/Tokens/src/Tokens.Domain/Tokens.Domain.csproj", "Modules/Tokens/src/Tokens.Domain/"]
COPY ["Modules/Tokens/src/Tokens.Infrastructure/Tokens.Infrastructure.csproj", "Modules/Tokens/src/Tokens.Infrastructure/"]

RUN dotnet restore /p:ContinuousIntegrationBuild=true "Applications/AdminApi/src/AdminApi/AdminApi.csproj"

COPY . .
Expand All @@ -98,7 +102,7 @@ RUN dotnet publish /p:ContinuousIntegrationBuild=true /p:UseAppHost=false --no-r
RUN dotnet publish /p:ContinuousIntegrationBuild=true --configuration Release --output /app/publish/health "/src/Applications/HealthCheck/src/HealthCheck.csproj"

#### Build Flutter Admin UI ####
FROM ghcr.io/cirruslabs/flutter:3.27.1 AS flutter-build-env
FROM ghcr.io/cirruslabs/flutter:3.27.2 AS flutter-build-env

COPY Applications/AdminUi /src
WORKDIR /src
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,7 @@ public static IServiceCollection AddCustomAspNetCore(this IServiceCollection ser
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
options.Filters.Add(new RedirectAntiforgeryValidationFailedResultFilter());
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var firstPropertyWithError =
context.ModelState.First(p => p.Value is { Errors.Count: > 0 });
var nameOfPropertyWithError = firstPropertyWithError.Key;
var firstError = firstPropertyWithError.Value!.Errors.First();
var firstErrorMessage = !string.IsNullOrWhiteSpace(firstError.ErrorMessage)
? firstError.ErrorMessage
: firstError.Exception != null
? firstError.Exception.Message
: "";

var formattedMessage = string.IsNullOrEmpty(nameOfPropertyWithError)
? firstErrorMessage
: $"'{nameOfPropertyWithError}': {firstErrorMessage}";
context.HttpContext.Response.ContentType = "application/json";
var responsePayload = new HttpResponseEnvelopeError(
HttpError.ForProduction(GenericApplicationErrors.Validation.InputCannotBeParsed().Code, formattedMessage,
"")); // TODO: add docs
return new BadRequestObjectResult(responsePayload);
};
})
.ConfigureApiBehaviorOptions(options => options.InvalidModelStateResponseFactory = InvalidModelStateResponseFactory())
.AddJsonOptions(options =>
{
var jsonConverters =
Expand Down Expand Up @@ -151,6 +128,31 @@ public static IServiceCollection AddCustomAspNetCore(this IServiceCollection ser
return services;
}

private static Func<ActionContext, IActionResult> InvalidModelStateResponseFactory() => context =>
{
var (nameOfPropertyWithError, value) = context.ModelState.First(p => p.Value is { Errors.Count: > 0 });

var firstError = value!.Errors.First();
var firstErrorMessage = !string.IsNullOrWhiteSpace(firstError.ErrorMessage)
? firstError.ErrorMessage
: firstError.Exception != null
? firstError.Exception.Message
: "";

var formattedMessage = string.IsNullOrEmpty(nameOfPropertyWithError)
? firstErrorMessage
: $"'{nameOfPropertyWithError}': {firstErrorMessage}";

context.HttpContext.Response.ContentType = "application/json";

var responsePayload = new HttpResponseEnvelopeError(
HttpError.ForProduction(GenericApplicationErrors.Validation.InputCannotBeParsed().Code,
formattedMessage,
"")); // TODO: add docs

return new BadRequestObjectResult(responsePayload);
};

private static object GetPropertyValue(object source, string propertyPath)
{
foreach (var property in propertyPath.Split('.').Select(s => source.GetType().GetProperty(s)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Backbone.AdminApi.Configuration;
using Backbone.Modules.Tokens.Application.Extensions;
using Backbone.Modules.Tokens.Infrastructure.Persistence;
using Microsoft.Extensions.Options;

namespace Backbone.AdminApi.Extensions;

public static class TokensServiceCollectionExtensions
{
public static IServiceCollection AddTokens(this IServiceCollection services, IConfiguration configuration)
{
services.AddApplication(configuration.GetSection("Application"));

services.ConfigureAndValidate<TokensConfiguration.InfrastructureConfiguration>(configuration.GetSection("Infrastructure").Bind);

var infrastructureConfiguration = services.BuildServiceProvider().GetRequiredService<IOptions<TokensConfiguration.InfrastructureConfiguration>>().Value;

services.AddPersistence(options =>
{
options.DbOptions.Provider = infrastructureConfiguration.SqlDatabase.Provider;
options.DbOptions.DbConnectionString = infrastructureConfiguration.SqlDatabase.ConnectionString;
});

return services;
}
}
1 change: 1 addition & 0 deletions Applications/AdminApi/src/AdminApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ static void ConfigureServices(IServiceCollection services, IConfiguration config
.AddCustomIdentity(environment)
.AddDatabase(parsedConfiguration.Infrastructure.SqlDatabase)
.AddDevices(configuration.GetSection("Modules:Devices"))
.AddTokens(configuration.GetSection("Modules:Tokens"))
.AddQuotas(parsedConfiguration.Modules.Quotas)
.AddAnnouncements(parsedConfiguration.Modules.Announcements)
.AddChallenges(parsedConfiguration.Modules.Challenges)
Expand Down
17 changes: 16 additions & 1 deletion Applications/AdminApi/src/AdminApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@
"EnableHealthCheck": true
}
}
},

"Tokens": {
"Application": {
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
}
},
"Infrastructure": {
"SqlDatabase": {
"EnableHealthCheck": true
}
}
}
},
"Logging": {
Expand All @@ -64,7 +78,6 @@
"Backbone": "Information",
"Enmeshed": "Information",
"AdminApi": "Information",

"Microsoft": "Information"
}
},
Expand All @@ -76,5 +89,7 @@
}
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NJsonSchema.NewtonsoftJson" Version="11.1.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.1" />
<PackageReference Include="SolidToken.SpecFlow.DependencyInjection" Version="3.9.3" />
<PackageReference Include="SpecFlow.NUnit" Version="3.9.74" />
<PackageReference Include="nunit" Version="4.3.2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@Integration
Feature: GET /Tokens?createdBy={identity-address}

Listing all tokens of an identity that doesn't have any tokens

Scenario: Get all Tokens for an identity with no tokens
Given an identity with no tokens
When a GET request is sent to the /Tokens endpoint with the identity's address
Then the response status code is 200 (OK)
And the response content is an empty array
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Backbone.AdminApi.Sdk.Endpoints.Tokens.Response;
using Backbone.AdminApi.Sdk.Services;
using Backbone.AdminApi.Tests.Integration.Configuration;
using Backbone.AdminApi.Tests.Integration.Extensions;
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;
using Microsoft.Extensions.Options;
using PaginationFilter = Backbone.BuildingBlocks.SDK.Endpoints.Common.Types.PaginationFilter;


namespace Backbone.AdminApi.Tests.Integration.StepDefinitions;

[Binding]
[Scope(Feature = "GET /Tokens?createdBy={identity-address}")]
internal class TokensStepDefinitions(HttpClientFactory factory, IOptions<HttpClientOptions> options) : BaseStepDefinitions(factory, options)
{
private ApiResponse<ListTokensTestResponse> _listTokensResponse = null!;
private string _newIdentityAddress = string.Empty;

[Given(@"an identity with no tokens")]
public async Task GivenAnIdentityWithNoTokens()
{
var createIdentityResponse = await IdentityCreationHelper.CreateIdentity(_client);

createIdentityResponse.Should().BeASuccess();

_newIdentityAddress = createIdentityResponse.Result!.Address;
}


[When(@"a GET request is sent to the /Tokens endpoint with the identity's address")]
public async Task WhenAGETRequestIsSentToTheTokensEndpointWithTheIdentitysAddress()
{
_listTokensResponse = await _client.Tokens.ListTokensByIdentity(new PaginationFilter { PageNumber = 1, PageSize = 5 }, _newIdentityAddress, CancellationToken.None);
}

[Then(@"the response status code is (\d+) \(.+\)")]
public void ThenTheResponseStatusCodeIs(int expectedStatusCode)
{
((int)_listTokensResponse!.Status).Should().Be(expectedStatusCode);
}


[Then(@"the response content is an empty array")]
public void ThenTheResponseContentIsAnEmptyArray()
{
_listTokensResponse.Result!.Count.Should().Be(0);
}
}
8 changes: 4 additions & 4 deletions Applications/ConsumerApi/src/ConsumerApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.1" />
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.24.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="9.0.1" />
<PackageReference Include="Serilog.Enrichers.Sensitive" Version="1.7.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
Expand Down
Loading

0 comments on commit 9328f7f

Please sign in to comment.