mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-02-27 15:02:41 +00:00
Compare commits
9 Commits
JPVenson-p
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2068be1221 | ||
|
|
bdfb6edfa3 | ||
|
|
25e8c6d591 | ||
|
|
c11c33e1a8 | ||
|
|
e8232d31ab | ||
|
|
b456afe00e | ||
|
|
01b3c6f902 | ||
|
|
94dcaf2ea2 | ||
|
|
37b50fe13c |
8
.github/workflows/ci-compat.yml
vendored
8
.github/workflows/ci-compat.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: abi-head
|
||||
retention-days: 14
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: abi-base
|
||||
retention-days: 14
|
||||
@@ -85,13 +85,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download abi-head
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: abi-head
|
||||
path: abi-head
|
||||
|
||||
- name: Download abi-base
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: abi-base
|
||||
path: abi-base
|
||||
|
||||
12
.github/workflows/ci-openapi.yml
vendored
12
.github/workflows/ci-openapi.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
retention-days: 14
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: openapi-base
|
||||
retention-days: 14
|
||||
@@ -85,13 +85,13 @@ jobs:
|
||||
- openapi-base
|
||||
steps:
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
|
||||
- name: Download openapi-base
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: openapi-base
|
||||
path: openapi-base
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
@@ -75,8 +75,8 @@
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.9.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.2" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.4" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.4" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="7.11.0" />
|
||||
|
||||
@@ -123,10 +123,10 @@
|
||||
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
||||
"TaskRefreshChannels": "Абнавіць каналы",
|
||||
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
|
||||
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.",
|
||||
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа выконвацца доўга.",
|
||||
"TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
|
||||
"TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
|
||||
"TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты",
|
||||
"TaskCleanCollectionsAndPlaylists": "Ачысціць калекцыі і плэй-лісты",
|
||||
"TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
|
||||
"TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
|
||||
"TaskAudioNormalization": "Нармалізацыя гуку",
|
||||
|
||||
@@ -268,7 +268,7 @@ public static class StreamingHelpers
|
||||
Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
|
||||
foreach (var param in queryString)
|
||||
{
|
||||
if (char.IsLower(param.Key[0]))
|
||||
if (param.Key.Length > 0 && char.IsLower(param.Key[0]))
|
||||
{
|
||||
// This was probably not parsed initially and should be a StreamOptions
|
||||
// or the generated URL should correctly serialize it
|
||||
|
||||
@@ -3,7 +3,7 @@ using Jellyfin.Api.Middleware;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json.Nodes;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Api.Auth;
|
||||
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
|
||||
@@ -26,7 +26,6 @@ using Jellyfin.Server.Filters;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -34,9 +33,7 @@ using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||
@@ -208,7 +205,7 @@ namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
{
|
||||
"x-jellyfin-version",
|
||||
new OpenApiString(version)
|
||||
new JsonNodeExtension(JsonValue.Create(version))
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -262,6 +259,7 @@ namespace Jellyfin.Server.Extensions
|
||||
c.OperationFilter<FileRequestFilter>();
|
||||
c.OperationFilter<ParameterObsoleteFilter>();
|
||||
c.DocumentFilter<AdditionalModelFilter>();
|
||||
c.DocumentFilter<SecuritySchemeReferenceFixupFilter>();
|
||||
})
|
||||
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
||||
}
|
||||
@@ -333,10 +331,10 @@ namespace Jellyfin.Server.Extensions
|
||||
options.MapType<Dictionary<ImageType, string>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
Type = JsonSchemaType.String
|
||||
}
|
||||
});
|
||||
|
||||
@@ -344,18 +342,17 @@ namespace Jellyfin.Server.Extensions
|
||||
options.MapType<Dictionary<string, string?>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Nullable = true
|
||||
Type = JsonSchemaType.String | JsonSchemaType.Null
|
||||
}
|
||||
});
|
||||
|
||||
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
||||
options.MapType<Version>(() => new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
Type = JsonSchemaType.String
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,17 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Nodes;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Server.Migrations;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Net.WebSocketMessages;
|
||||
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
|
||||
using MediaBrowser.Model.ApiClient;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.SyncPlay;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters
|
||||
@@ -25,7 +24,7 @@ namespace Jellyfin.Server.Filters
|
||||
public class AdditionalModelFilter : IDocumentFilter
|
||||
{
|
||||
// Array of options that should not be visible in the api spec.
|
||||
private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) };
|
||||
private static readonly Type[] _ignoredConfigurations = [typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions)];
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
@@ -48,8 +47,8 @@ namespace Jellyfin.Server.Filters
|
||||
&& t != typeof(WebSocketMessageInfo))
|
||||
.ToList();
|
||||
|
||||
var inboundWebSocketSchemas = new List<OpenApiSchema>();
|
||||
var inboundWebSocketDiscriminators = new Dictionary<string, string>();
|
||||
var inboundWebSocketSchemas = new List<IOpenApiSchema>();
|
||||
var inboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
||||
foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))
|
||||
{
|
||||
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||
@@ -60,18 +59,16 @@ namespace Jellyfin.Server.Filters
|
||||
|
||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||
inboundWebSocketSchemas.Add(schema);
|
||||
inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3;
|
||||
if (schema is OpenApiSchemaReference schemaRef)
|
||||
{
|
||||
inboundWebSocketDiscriminators[messageType.ToString()!] = schemaRef;
|
||||
}
|
||||
}
|
||||
|
||||
var inboundWebSocketMessageSchema = new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
Description = "Represents the list of possible inbound websocket types",
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Id = nameof(InboundWebSocketMessage),
|
||||
Type = ReferenceType.Schema
|
||||
},
|
||||
OneOf = inboundWebSocketSchemas,
|
||||
Discriminator = new OpenApiDiscriminator
|
||||
{
|
||||
@@ -82,8 +79,8 @@ namespace Jellyfin.Server.Filters
|
||||
|
||||
context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema);
|
||||
|
||||
var outboundWebSocketSchemas = new List<OpenApiSchema>();
|
||||
var outboundWebSocketDiscriminators = new Dictionary<string, string>();
|
||||
var outboundWebSocketSchemas = new List<IOpenApiSchema>();
|
||||
var outboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
||||
foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))
|
||||
{
|
||||
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||
@@ -94,58 +91,55 @@ namespace Jellyfin.Server.Filters
|
||||
|
||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||
outboundWebSocketSchemas.Add(schema);
|
||||
outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3);
|
||||
if (schema is OpenApiSchemaReference schemaRef)
|
||||
{
|
||||
outboundWebSocketDiscriminators.Add(messageType.ToString()!, schemaRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
|
||||
var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
Description = "Untyped sync play command.",
|
||||
Properties = new Dictionary<string, OpenApiSchema>
|
||||
Properties = new Dictionary<string, IOpenApiSchema>
|
||||
{
|
||||
{
|
||||
"Data", new OpenApiSchema
|
||||
{
|
||||
AllOf =
|
||||
[
|
||||
new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } }
|
||||
],
|
||||
AllOf = new List<IOpenApiSchema>
|
||||
{
|
||||
new OpenApiSchemaReference(nameof(GroupUpdate<object>), null, null)
|
||||
},
|
||||
Description = "Group update data",
|
||||
Nullable = false,
|
||||
}
|
||||
},
|
||||
{ "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } },
|
||||
{ "MessageId", new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Description = "Gets or sets the message id." } },
|
||||
{
|
||||
"MessageType", new OpenApiSchema
|
||||
{
|
||||
Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(),
|
||||
AllOf =
|
||||
[
|
||||
new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } }
|
||||
],
|
||||
Enum = Enum.GetValues<SessionMessageType>().Select(type => (JsonNode)JsonValue.Create(type.ToString())!).ToList(),
|
||||
AllOf = new List<IOpenApiSchema>
|
||||
{
|
||||
new OpenApiSchemaReference(nameof(SessionMessageType), null, null)
|
||||
},
|
||||
Description = "The different kinds of messages that are used in the WebSocket api.",
|
||||
Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)),
|
||||
Default = JsonValue.Create(nameof(SessionMessageType.SyncPlayGroupUpdate)),
|
||||
ReadOnly = true
|
||||
}
|
||||
},
|
||||
},
|
||||
AdditionalPropertiesAllowed = false,
|
||||
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" }
|
||||
};
|
||||
context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
|
||||
outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema);
|
||||
outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.Reference.ReferenceV3;
|
||||
var syncPlayRef = new OpenApiSchemaReference("SyncPlayGroupUpdateMessage", null, null);
|
||||
outboundWebSocketSchemas.Add(syncPlayRef);
|
||||
outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayRef;
|
||||
|
||||
var outboundWebSocketMessageSchema = new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
Description = "Represents the list of possible outbound websocket types",
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Id = nameof(OutboundWebSocketMessage),
|
||||
Type = ReferenceType.Schema
|
||||
},
|
||||
OneOf = outboundWebSocketSchemas,
|
||||
Discriminator = new OpenApiDiscriminator
|
||||
{
|
||||
@@ -159,17 +153,12 @@ namespace Jellyfin.Server.Filters
|
||||
nameof(WebSocketMessage),
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
Description = "Represents the possible websocket types",
|
||||
Reference = new OpenApiReference
|
||||
OneOf = new List<IOpenApiSchema>
|
||||
{
|
||||
Id = nameof(WebSocketMessage),
|
||||
Type = ReferenceType.Schema
|
||||
},
|
||||
OneOf = new[]
|
||||
{
|
||||
inboundWebSocketMessageSchema,
|
||||
outboundWebSocketMessageSchema
|
||||
new OpenApiSchemaReference(nameof(InboundWebSocketMessage), null, null),
|
||||
new OpenApiSchemaReference(nameof(OutboundWebSocketMessage), null, null)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -180,8 +169,8 @@ namespace Jellyfin.Server.Filters
|
||||
&& t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
|
||||
.ToList();
|
||||
|
||||
var groupUpdateSchemas = new List<OpenApiSchema>();
|
||||
var groupUpdateDiscriminators = new Dictionary<string, string>();
|
||||
var groupUpdateSchemas = new List<IOpenApiSchema>();
|
||||
var groupUpdateDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
||||
foreach (var type in groupUpdateTypes)
|
||||
{
|
||||
var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||
@@ -192,18 +181,16 @@ namespace Jellyfin.Server.Filters
|
||||
|
||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||
groupUpdateSchemas.Add(schema);
|
||||
groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3;
|
||||
if (schema is OpenApiSchemaReference schemaRef)
|
||||
{
|
||||
groupUpdateDiscriminators[groupUpdateType.ToString()!] = schemaRef;
|
||||
}
|
||||
}
|
||||
|
||||
var groupUpdateSchema = new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
Type = JsonSchemaType.Object,
|
||||
Description = "Represents the list of possible group update types",
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Id = nameof(GroupUpdate<object>),
|
||||
Type = ReferenceType.Schema
|
||||
},
|
||||
OneOf = groupUpdateSchemas,
|
||||
Discriminator = new OpenApiDiscriminator
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
@@ -48,7 +48,7 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
|
||||
public OpenApiDocument GetSwagger(string documentName, string host, string basePath)
|
||||
{
|
||||
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters
|
||||
@@ -28,10 +28,11 @@ namespace Jellyfin.Server.Filters
|
||||
{
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Type = JsonSchemaType.String,
|
||||
Format = "binary"
|
||||
}
|
||||
};
|
||||
body.Content ??= new System.Collections.Generic.Dictionary<string, OpenApiMediaType>();
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
body.Content.Add(contentType, mediaType);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters
|
||||
@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Filters
|
||||
{
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Type = JsonSchemaType.String,
|
||||
Format = "binary"
|
||||
}
|
||||
};
|
||||
@@ -22,6 +22,11 @@ namespace Jellyfin.Server.Filters
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
if (operation.Responses is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
|
||||
{
|
||||
if (attribute is ProducesFileAttribute producesFileAttribute)
|
||||
@@ -31,7 +36,7 @@ namespace Jellyfin.Server.Filters
|
||||
.FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
|
||||
|
||||
// Operation doesn't have a response.
|
||||
if (response.Value is null)
|
||||
if (response.Value?.Content is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters;
|
||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
|
||||
public class FlagsEnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||
public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
|
||||
{
|
||||
var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
|
||||
if (type is null || !type.IsEnum)
|
||||
@@ -29,11 +29,16 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
|
||||
return;
|
||||
}
|
||||
|
||||
if (schema is not OpenApiSchema concreteSchema)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.MemberInfo is null)
|
||||
{
|
||||
// Processing the enum definition itself - ensure it's type "string" not "integer"
|
||||
schema.Type = "string";
|
||||
schema.Format = null;
|
||||
concreteSchema.Type = JsonSchemaType.String;
|
||||
concreteSchema.Format = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -43,11 +48,11 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
|
||||
|
||||
// Flags enums should be represented as arrays referencing the enum schema
|
||||
// since multiple values can be combined
|
||||
schema.Type = "array";
|
||||
schema.Format = null;
|
||||
schema.Enum = null;
|
||||
schema.AllOf = null;
|
||||
schema.Items = enumSchema;
|
||||
concreteSchema.Type = JsonSchemaType.Array;
|
||||
concreteSchema.Format = null;
|
||||
concreteSchema.Enum = null;
|
||||
concreteSchema.AllOf = null;
|
||||
concreteSchema.Items = enumSchema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Nodes;
|
||||
using Jellyfin.Data.Attributes;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters;
|
||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
|
||||
public class IgnoreEnumSchemaFilter : ISchemaFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||
public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
|
||||
{
|
||||
if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false))
|
||||
{
|
||||
@@ -25,18 +25,23 @@ public class IgnoreEnumSchemaFilter : ISchemaFilter
|
||||
return;
|
||||
}
|
||||
|
||||
var enumOpenApiStrings = new List<IOpenApiAny>();
|
||||
if (schema is not OpenApiSchema concreteSchema)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var enumOpenApiNodes = new List<JsonNode>();
|
||||
|
||||
foreach (var enumName in Enum.GetNames(type))
|
||||
{
|
||||
var member = type.GetMember(enumName)[0];
|
||||
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
|
||||
{
|
||||
enumOpenApiStrings.Add(new OpenApiString(enumName));
|
||||
enumOpenApiNodes.Add(JsonValue.Create(enumName)!);
|
||||
}
|
||||
}
|
||||
|
||||
schema.Enum = enumOpenApiStrings;
|
||||
concreteSchema.Enum = enumOpenApiNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters
|
||||
@@ -21,11 +21,17 @@ namespace Jellyfin.Server.Filters
|
||||
.OfType<ParameterObsoleteAttribute>()
|
||||
.Any())
|
||||
{
|
||||
if (operation.Parameters is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var parameter in operation.Parameters)
|
||||
{
|
||||
if (parameter.Name.Equals(parameterDescription.Name, StringComparison.Ordinal))
|
||||
if (parameter is OpenApiParameter concreteParam
|
||||
&& string.Equals(concreteParam.Name, parameterDescription.Name, StringComparison.Ordinal))
|
||||
{
|
||||
parameter.Deprecated = true;
|
||||
concreteParam.Deprecated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters;
|
||||
@@ -8,12 +8,12 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
operation.Responses.TryAdd(
|
||||
operation.Responses?.TryAdd(
|
||||
"503",
|
||||
new OpenApiResponse
|
||||
{
|
||||
Description = "The server is currently starting or is temporarily not available.",
|
||||
Headers = new Dictionary<string, OpenApiHeader>
|
||||
Headers = new Dictionary<string, IOpenApiHeader>
|
||||
{
|
||||
{
|
||||
"Retry-After", new OpenApiHeader
|
||||
@@ -23,7 +23,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
||||
Description = "A hint for when to retry the operation in full seconds.",
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "integer",
|
||||
Type = JsonSchemaType.Integer,
|
||||
Format = "int32"
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
||||
Description = "A short plain-text reason why the server is not available.",
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Type = JsonSchemaType.String,
|
||||
Format = "text"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters;
|
||||
@@ -66,17 +66,10 @@ public class SecurityRequirementsOperationFilter : IOperationFilter
|
||||
return;
|
||||
}
|
||||
|
||||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||
operation.Responses?.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||
operation.Responses?.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||
|
||||
var scheme = new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = AuthenticationSchemes.CustomAuthentication
|
||||
},
|
||||
};
|
||||
var scheme = new OpenApiSecuritySchemeReference(AuthenticationSchemes.CustomAuthentication, null, null);
|
||||
|
||||
// Add DefaultAuthorization scope to any endpoint that has a policy with a requirement that is a subset of DefaultAuthorization.
|
||||
if (!requiredScopes.Contains(DefaultAuthPolicy.AsSpan(), StringComparison.Ordinal))
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Jellyfin.Server.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Document filter that fixes security scheme references after document generation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In Microsoft.OpenApi v2, <see cref="OpenApiSecuritySchemeReference"/> requires a resolved
|
||||
/// <c>Target</c> to serialize correctly. References created without a host document (as in
|
||||
/// operation filters) serialize as empty objects. This filter re-creates all security scheme
|
||||
/// references with the document context so they resolve properly during serialization.
|
||||
/// </remarks>
|
||||
internal class SecuritySchemeReferenceFixupFilter : IDocumentFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
swaggerDoc.RegisterComponents();
|
||||
|
||||
if (swaggerDoc.Paths is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pathItem in swaggerDoc.Paths.Values)
|
||||
{
|
||||
if (pathItem.Operations is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var operation in pathItem.Operations.Values)
|
||||
{
|
||||
if (operation.Security is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < operation.Security.Count; i++)
|
||||
{
|
||||
var oldReq = operation.Security[i];
|
||||
var newReq = new OpenApiSecurityRequirement();
|
||||
foreach (var kvp in oldReq)
|
||||
{
|
||||
var fixedRef = new OpenApiSecuritySchemeReference(kvp.Key.Reference.Id!, swaggerDoc);
|
||||
newReq[fixedRef] = kvp.Value;
|
||||
}
|
||||
|
||||
operation.Security[i] = newReq;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,12 +201,17 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
public List<BaseItem> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes, DtoOptions options, bool shouldIncludeMissingEpisodes)
|
||||
{
|
||||
if (series is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes);
|
||||
}
|
||||
|
||||
public List<BaseItem> GetEpisodes()
|
||||
{
|
||||
return Series.GetSeasonEpisodes(this, null, null, new DtoOptions(true), true);
|
||||
return GetEpisodes(Series, null, null, new DtoOptions(true), true);
|
||||
}
|
||||
|
||||
public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
|
||||
|
||||
@@ -895,7 +895,7 @@ public class StreamInfo
|
||||
|
||||
if (SubProtocol == MediaStreamProtocol.hls)
|
||||
{
|
||||
sb.Append("/master.m3u8?");
|
||||
sb.Append("/master.m3u8");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -906,10 +906,10 @@ public class StreamInfo
|
||||
sb.Append('.');
|
||||
sb.Append(Container);
|
||||
}
|
||||
|
||||
sb.Append('?');
|
||||
}
|
||||
|
||||
var queryStart = sb.Length;
|
||||
|
||||
if (!string.IsNullOrEmpty(DeviceProfileId))
|
||||
{
|
||||
sb.Append("&DeviceProfileId=");
|
||||
@@ -1133,6 +1133,12 @@ public class StreamInfo
|
||||
sb.Append(query);
|
||||
}
|
||||
|
||||
// Replace the first '&' with '?' to form a valid query string.
|
||||
if (sb.Length > queryStart)
|
||||
{
|
||||
sb[queryStart] = '?';
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -216,8 +216,7 @@ public class StreamInfoTests
|
||||
|
||||
string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123");
|
||||
|
||||
// New version will return and & after the ? due to optional parameters.
|
||||
string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase);
|
||||
string newUrl = streamInfo.ToUrl(BaseUrl, "123", null);
|
||||
|
||||
Assert.Equal(legacyUrl, newUrl, ignoreCase: true);
|
||||
}
|
||||
@@ -234,8 +233,7 @@ public class StreamInfoTests
|
||||
FillAllProperties(streamInfo);
|
||||
string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123");
|
||||
|
||||
// New version will return and & after the ? due to optional parameters.
|
||||
string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase);
|
||||
string newUrl = streamInfo.ToUrl(BaseUrl, "123", null);
|
||||
|
||||
Assert.Equal(legacyUrl, newUrl, ignoreCase: true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user