mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Swagger: handling tags and sort order
This commit is contained in:
parent
9b3eb7fede
commit
2a22661c46
@ -0,0 +1,51 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute to specify on apis to specify it's documentation's name and category.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ApiDefinitionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The public name of this api.
|
||||
/// </summary>
|
||||
[NotNull] public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the group in witch this API is.
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ApiDefinitionAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the api that will be used on the documentation page.</param>
|
||||
public ApiDefinitionAttribute([NotNull] string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Utils
|
||||
{
|
||||
/// <summary>
|
||||
@ -28,5 +30,10 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// that won't be included on the swagger.
|
||||
/// </summary>
|
||||
public const int AlternativeRoute = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for every <see cref="IResource"/>.
|
||||
/// </summary>
|
||||
public const string ResourceGroup = "Resource";
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,14 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSwag.Annotations;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
@ -39,6 +41,7 @@ namespace Kyoo.Core.Api
|
||||
[Route("api/collection", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[PartialPermission(nameof(CollectionApi))]
|
||||
[ApiDefinition("Collection", Group = ResourceGroup)]
|
||||
public class CollectionApi : CrudThumbsApi<Collection>
|
||||
{
|
||||
/// <summary>
|
||||
@ -46,6 +49,17 @@ namespace Kyoo.Core.Api
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CollectionApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
/// <param name="options">
|
||||
/// Options used to retrieve the base URL of Kyoo.
|
||||
/// </param>
|
||||
public CollectionApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs,
|
||||
@ -115,6 +129,9 @@ namespace Kyoo.Core.Api
|
||||
[HttpGet("{identifier:id}/libraries")]
|
||||
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
|
@ -28,12 +28,37 @@ using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class to handle CRUD operations and services thumbnails for
|
||||
/// a specific resource type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of resource to make CRUD and thumbnails apis for.</typeparam>
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
public class CrudThumbsApi<T> : CrudApi<T>
|
||||
where T : class, IResource, IThumbnails
|
||||
{
|
||||
/// <summary>
|
||||
/// The file manager used to send images.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// The thumbnail manager used to retrieve images paths.
|
||||
/// </summary>
|
||||
private readonly IThumbnailsManager _thumbs;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CrudThumbsApi{T}"/> that handles crud requests and thumbnails.
|
||||
/// </summary>
|
||||
/// <param name="repository">
|
||||
/// The repository to use as a baking store for the type <typeparamref name="T"/>.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
/// <param name="baseURL">
|
||||
/// The base URL of Kyoo to use to create links.
|
||||
/// </param>
|
||||
public CrudThumbsApi(IRepository<T> repository,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs,
|
||||
@ -49,26 +74,17 @@ namespace Kyoo.Core.Api
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get an image for the specified item.
|
||||
/// List of commonly available images:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Poster: Image 0, also available at /poster</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Thumbnail: Image 1, also available at /thumbnail</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Logo: Image 3, also available at /logo</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// List of commonly available images:<br/>
|
||||
/// - Poster: Image 0, also available at /poster<br/>
|
||||
/// - Thumbnail: Image 1, also available at /thumbnail<br/>
|
||||
/// - Logo: Image 3, also available at /logo<br/>
|
||||
/// <br/>
|
||||
/// Other images can be arbitrarily added by plugins so any image number can be specified from this endpoint.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||
/// <param name="image">The number of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">
|
||||
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||
/// </response>
|
||||
/// <response code="404">No item exist with the specific identifier or the image does not exists on kyoo.</response>
|
||||
[HttpGet("{identifier:id}/image-{image:int}")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
|
@ -18,11 +18,15 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Namotion.Reflection;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Generation.TypeMappers;
|
||||
using NSwag;
|
||||
@ -61,18 +65,42 @@ namespace Kyoo.Swagger
|
||||
options.DocumentName = "v1";
|
||||
options.UseControllerSummaryAsTagDescription = true;
|
||||
options.GenerateExamples = true;
|
||||
options.PostProcess = x =>
|
||||
options.PostProcess = postProcess =>
|
||||
{
|
||||
x.Info.Contact = new OpenApiContact
|
||||
postProcess.Info.Contact = new OpenApiContact
|
||||
{
|
||||
Name = "Kyoo's github",
|
||||
Url = "https://github.com/AnonymusRaccoon/Kyoo"
|
||||
};
|
||||
x.Info.License = new OpenApiLicense
|
||||
postProcess.Info.License = new OpenApiLicense
|
||||
{
|
||||
Name = "GPL-3.0-or-later",
|
||||
Url = "https://github.com/AnonymusRaccoon/Kyoo/blob/master/LICENSE"
|
||||
};
|
||||
|
||||
// We can't reorder items by assigning the sorted value to the Paths variable since it has no setter.
|
||||
List<KeyValuePair<string, OpenApiPathItem>> sorted = postProcess.Paths
|
||||
.OrderBy(x => x.Key)
|
||||
.ToList();
|
||||
postProcess.Paths.Clear();
|
||||
foreach ((string key, OpenApiPathItem value) in sorted)
|
||||
postProcess.Paths.Add(key, value);
|
||||
|
||||
List<dynamic> tagGroups = (List<dynamic>)postProcess.ExtensionData["x-tagGroups"];
|
||||
List<string> tagsWithoutGroup = postProcess.Tags
|
||||
.Select(x => x.Name)
|
||||
.Where(x => tagGroups
|
||||
.SelectMany<dynamic, string>(y => y.tags)
|
||||
.All(y => y != x))
|
||||
.ToList();
|
||||
if (tagsWithoutGroup.Any())
|
||||
{
|
||||
tagGroups.Add(new
|
||||
{
|
||||
name = "Others",
|
||||
tags = tagsWithoutGroup
|
||||
});
|
||||
}
|
||||
};
|
||||
options.AddOperationFilter(x =>
|
||||
{
|
||||
@ -80,6 +108,44 @@ namespace Kyoo.Swagger
|
||||
return ctx.ApiDescription.ActionDescriptor.AttributeRouteInfo?.Order != AlternativeRoute;
|
||||
return true;
|
||||
});
|
||||
options.AddOperationFilter(context =>
|
||||
{
|
||||
ApiDefinitionAttribute def = context.ControllerType.GetCustomAttribute<ApiDefinitionAttribute>();
|
||||
string name = def?.Name ?? context.ControllerType.Name;
|
||||
|
||||
context.OperationDescription.Operation.Tags.Add(name);
|
||||
if (context.Document.Tags.All(x => x.Name != name))
|
||||
{
|
||||
context.Document.Tags.Add(new OpenApiTag
|
||||
{
|
||||
Name = name,
|
||||
Description = context.ControllerType.GetXmlDocsSummary()
|
||||
});
|
||||
}
|
||||
|
||||
if (def == null)
|
||||
return true;
|
||||
|
||||
context.Document.ExtensionData ??= new Dictionary<string, object>();
|
||||
context.Document.ExtensionData.TryAdd("x-tagGroups", new List<dynamic>());
|
||||
List<dynamic> obj = (List<dynamic>)context.Document.ExtensionData["x-tagGroups"];
|
||||
dynamic existing = obj.FirstOrDefault(x => x.name == def.Group);
|
||||
if (existing != null)
|
||||
{
|
||||
if (!existing.tags.Contains(def.Name))
|
||||
existing.tags.Add(def.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.Add(new
|
||||
{
|
||||
name = def.Group,
|
||||
tags = new List<string> { def.Name }
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
options.SchemaGenerator.Settings.TypeMappers.Add(new PrimitiveTypeMapper(typeof(Identifier), x =>
|
||||
{
|
||||
x.IsNullableRaw = false;
|
||||
@ -92,11 +158,7 @@ namespace Kyoo.Swagger
|
||||
public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
|
||||
{
|
||||
SA.New<IApplicationBuilder>(app => app.UseOpenApi(), SA.Before + 1),
|
||||
SA.New<IApplicationBuilder>(app => app.UseSwaggerUi3(x =>
|
||||
{
|
||||
x.OperationsSorter = "alpha";
|
||||
x.TagsSorter = "alpha";
|
||||
}), SA.Before),
|
||||
SA.New<IApplicationBuilder>(app => app.UseSwaggerUi3(), SA.Before),
|
||||
SA.New<IApplicationBuilder>(app => app.UseReDoc(x =>
|
||||
{
|
||||
x.Path = "/redoc";
|
||||
|
Loading…
x
Reference in New Issue
Block a user