using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.Options; using Microsoft.OpenApi; using Musify.Api.Authentication; namespace Musify.Api.OpenApi; public sealed class OpenApiOptionsSetup(IOptions options) : IConfigureNamedOptions { private const string SecuritySchemeId = "OAuth2"; private readonly AuthenticationConfiguration _configuration = options.Value; public void Configure(string? name, OpenApiOptions options) => Configure(options); public void Configure(OpenApiOptions options) { options.AddDocumentTransformer((document, _, _) => { document.Components ??= new OpenApiComponents(); document.Components.SecuritySchemes ??= new Dictionary(); document.Components.SecuritySchemes[SecuritySchemeId] = new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri(_configuration.AuthorizationEndpoint), TokenUrl = new Uri(_configuration.TokenEndpoint), Scopes = _configuration.Scopes.ToDictionary(scope => scope, scope => scope), }, }, }; return Task.CompletedTask; }); options.AddSchemaTransformer((schema, context, _) => { // Every long the API returns is a user id, serialized as a string (JS loses precision above 2^53). // Request DTOs only carry byte sizes, which may be sent as numbers or strings. if (context.JsonPropertyInfo is { } property && (property.PropertyType == typeof(long) || property.PropertyType == typeof(long?))) { var isRequest = property.DeclaringType?.Name.EndsWith("Request", StringComparison.Ordinal) == true; schema.Type = isRequest ? JsonSchemaType.Integer | JsonSchemaType.String : JsonSchemaType.String; if (property.PropertyType == typeof(long?)) schema.Type |= JsonSchemaType.Null; schema.Format = isRequest ? "int64" : null; } return Task.CompletedTask; }); options.AddOperationTransformer((operation, context, _) => { var requiresAuthorization = context.Description.ActionDescriptor.EndpointMetadata .OfType() .Any(); var allowsAnonymous = context.Description.ActionDescriptor.EndpointMetadata .OfType() .Any(); if (!requiresAuthorization || allowsAnonymous) { return Task.CompletedTask; } operation.Security ??= new List(); operation.Security.Add(new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference(SecuritySchemeId, context.Document)] = _configuration.Scopes.ToList(), }); return Task.CompletedTask; }); } }