Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions TeachingRecordSystem/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="SerilogTimings" Version="3.1.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.6.2" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.2" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.12.24" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
using TeachingRecordSystem.Api.Infrastructure.Security;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class AddSecuritySchemeOperationFilter : IOperationFilter
public class AddSecuritySchemeOperationTransformer : IOpenApiOperationTransformer
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var actionDescriptor = context.ApiDescription.ActionDescriptor;
var actionDescriptor = context.Description.ActionDescriptor;

var authorizeAttributes = actionDescriptor.EndpointMetadata.OfType<AuthorizeAttribute>().ToArray();

Expand Down Expand Up @@ -54,5 +54,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)

operation.Security ??= new List<OpenApiSecurityRequirement>();
operation.Security.Add(requirement);

return Task.CompletedTask;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class AddWebHookMessagesDocumentTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument swaggerDoc, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
(int majorVersion, string? minorVersion) = OpenApiDocumentHelper.GetVersionsFromVersionName(swaggerDoc.Info.Version);

if (majorVersion == 3)
{
var messagesNamespace = $"TeachingRecordSystem.Api.V3.V{minorVersion}.WebHookMessages";

var messageTypes = typeof(AddWebHookMessagesDocumentTransformer).Assembly.GetTypes()
.Where(t => t.Namespace == messagesNamespace);

foreach (var messageType in messageTypes)
{
// Generate a schema for each webhook message type to ensure they appear in the document
var schemaName = messageType.Name;
if (!swaggerDoc.Components.Schemas.ContainsKey(schemaName))
{
try
{
// The schema service will handle generating the schema
var jsonTypeInfo = context.ApplicationServices.GetRequiredService<JsonSerializerOptions>()
.GetTypeInfo(messageType);

if (jsonTypeInfo != null)
{
var schemaContext = new OpenApiSchemaTransformerContext(
jsonTypeInfo,
context.ApplicationServices,
context.DocumentName);

// Request the schema to be generated
_ = context.SchemaService.GetOrCreateSchema(messageType, schemaContext);
}
}
catch
{
// If we can't generate the schema, skip it
}
}
}
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class ContentTypesOperationFilter : IOperationFilter
public class ContentTypesOperationTransformer : IOpenApiOperationTransformer
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
// Remove invalid Content-Types for a given request/response

Expand Down Expand Up @@ -35,5 +35,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
}
}
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class MinorVersionHeaderDocumentFilter : IDocumentFilter
public class MinorVersionHeaderDocumentTransformer : IOpenApiDocumentTransformer
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
public Task TransformAsync(OpenApiDocument swaggerDoc, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var documentVersion = swaggerDoc.Info.Version;
var isMinorVersion = documentVersion.StartsWith("v3_", StringComparison.Ordinal);

if (!isMinorVersion)
{
return;
return Task.CompletedTask;
}

var minorVersion = documentVersion["v3_".Length..];
Expand All @@ -25,7 +24,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
new OpenApiSchema
{
Type = "string",
Enum = [new OpenApiString(minorVersion)] // TODO Use const when Swashbuckle supports openapi 3.1
Enum = [new OpenApiString(minorVersion)] // TODO Use const when OpenAPI supports openapi 3.1
});

foreach (var (_, path) in swaggerDoc.Paths)
Expand All @@ -44,5 +43,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
});
}
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
using OneOf;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class OneOfSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
var type = context.JsonTypeInfo.Type;

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(OneOf<,>))
{
// By convention, use the schema of the T0 in OneOf<T0, T1>.
var innerType = type.GetGenericArguments()[0];
var innerSchema = context.SchemaService.GetOrCreateSchema(innerType, context);

// Copy properties from inner schema to this schema
schema.Type = innerSchema.Type;
schema.Format = innerSchema.Format;
schema.Properties = innerSchema.Properties;
schema.Items = innerSchema.Items;
schema.Reference = innerSchema.Reference;
schema.AllOf = innerSchema.AllOf;
schema.AnyOf = innerSchema.AnyOf;
schema.OneOf = innerSchema.OneOf;
}

return Task.CompletedTask;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Reflection;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class RemoveEnumValuesForFlagsEnumSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
var type = context.JsonTypeInfo.Type;

if (!type.IsEnum)
{
return Task.CompletedTask;
}

if (type.GetCustomAttribute<FlagsAttribute>() is null)
{
return Task.CompletedTask;
}

schema.Enum.Clear();

return Task.CompletedTask;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Reflection;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class RemoveExcludedEnumOptionsSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
var type = context.JsonTypeInfo.Type;

if (!type.IsEnum)
{
return Task.CompletedTask;
}

foreach (var enumValue in schema.Enum.Cast<OpenApiString>().ToArray())
{
var field = type.GetField(enumValue.Value)!;

if (field.GetCustomAttribute<ExcludeFromSchemaAttribute>() is not null)
{
schema.Enum.Remove(enumValue);
}
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

namespace TeachingRecordSystem.Api.Infrastructure.OpenApi;

public class SecurityDefinitionsDocumentTransformer(IConfiguration configuration) : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();

document.Components.SecuritySchemes[SecuritySchemes.ApiKey] = new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Authorization",
Scheme = "Bearer",
Type = SecuritySchemeType.Http
};

document.Components.SecuritySchemes[SecuritySchemes.GetAnIdentityAccessToken] = new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Scheme = "Bearer",
Type = SecuritySchemeType.OpenIdConnect,
OpenIdConnectUrl = new Uri(configuration.GetRequiredValue("GetAnIdentity:BaseAddress") + ".well-known/openid-configuration")
};

return Task.CompletedTask;
}
}
Loading