mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Swagger: sorting tags groups & documenting the genre's API
This commit is contained in:
parent
049f545d51
commit
0fdc583d58
@ -33,7 +33,9 @@ namespace Kyoo.Abstractions.Models.Attributes
|
|||||||
[NotNull] public string Name { get; }
|
[NotNull] public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the group in witch this API is.
|
/// The name of the group in witch this API is. You can also specify a custom sort order using the following
|
||||||
|
/// format: <code>order:name</code>. Everything before the first <c>:</c> will be removed but kept for
|
||||||
|
/// th alphabetical ordering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Group { get; set; }
|
public string Group { get; set; }
|
||||||
|
|
||||||
|
@ -34,11 +34,17 @@ namespace Kyoo.Abstractions.Models.Utils
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for main resources of kyoo.
|
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for main resources of kyoo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ResourcesGroup = "Resources";
|
public const string ResourcesGroup = "0:Resources";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A group name for <see cref="ApiDefinitionAttribute"/>.
|
||||||
|
/// It should be used for sub resources of kyoo that help define the main resources.
|
||||||
|
/// </summary>
|
||||||
|
public const string MetadataGroup = "1:Metadata";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints useful for playback.
|
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints useful for playback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string WatchGroup = "Watch";
|
public const string WatchGroup = "2:Watch";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
// 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 System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/genre")]
|
|
||||||
[Route("api/genres")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(GenreApi))]
|
|
||||||
public class GenreApi : CrudApi<Genre>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public GenreApi(ILibraryManager libraryManager)
|
|
||||||
: base(libraryManager.GenreRepository)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
|
||||||
[HttpGet("{id:int}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Genres.Any(y => y.ID == id)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/show")]
|
|
||||||
[HttpGet("{slug}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Genres.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
105
src/Kyoo.Core/Views/Metadata/GenreApi.cs
Normal file
105
src/Kyoo.Core/Views/Metadata/GenreApi.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
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 Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Genre"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/genres")]
|
||||||
|
[Route("api/genre", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(GenreApi))]
|
||||||
|
[ApiDefinition("Genres", Group = MetadataGroup)]
|
||||||
|
public class GenreApi : CrudApi<Genre>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="GenreApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
public GenreApi(ILibraryManager libraryManager)
|
||||||
|
: base(libraryManager.GenreRepository)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get shows with genre
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Lists the shows that have the selected genre.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Genre"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort shows by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
|
/// <param name="afterID">An optional show's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of shows.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No genre with the given ID could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/shows")]
|
||||||
|
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
|
||||||
|
new Sort<Show>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Genre>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
src/Kyoo.Swagger/ApiSorter.cs
Normal file
64
src/Kyoo.Swagger/ApiSorter.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NSwag;
|
||||||
|
using NSwag.Generation.AspNetCore;
|
||||||
|
|
||||||
|
namespace Kyoo.Swagger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class to sort apis.
|
||||||
|
/// </summary>
|
||||||
|
public static class ApiSorter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sort apis by alphabetical orders.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The swagger settings to update.</param>
|
||||||
|
public static void SortApis(this AspNetCoreOpenApiDocumentGeneratorSettings options)
|
||||||
|
{
|
||||||
|
options.PostProcess += postProcess =>
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
options.PostProcess += postProcess =>
|
||||||
|
{
|
||||||
|
if (!postProcess.ExtensionData.TryGetValue("x-tagGroups", out object list))
|
||||||
|
return;
|
||||||
|
List<dynamic> tagGroups = (List<dynamic>)list;
|
||||||
|
postProcess.ExtensionData["x-tagGroups"] = tagGroups
|
||||||
|
.OrderBy(x => x.name)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
name = x.name.Substring(x.name.IndexOf(':') + 1),
|
||||||
|
x.tags
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
src/Kyoo.Swagger/ApiTagsFilter.cs
Normal file
119
src/Kyoo.Swagger/ApiTagsFilter.cs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Namotion.Reflection;
|
||||||
|
using NSwag;
|
||||||
|
using NSwag.Generation.AspNetCore;
|
||||||
|
using NSwag.Generation.Processors.Contexts;
|
||||||
|
|
||||||
|
namespace Kyoo.Swagger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class to handle Api Groups (OpenApi tags and x-tagGroups).
|
||||||
|
/// Tags should be specified via <see cref="ApiDefinitionAttribute"/> and this filter will map this to the
|
||||||
|
/// <see cref="OpenApiDocument"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ApiTagsFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The main operation filter that will map every <see cref="ApiDefinitionAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The processor context, this is given by the AddOperationFilter method.</param>
|
||||||
|
/// <returns>This always return <c>true</c> since it should not remove operations.</returns>
|
||||||
|
public static bool OperationFilter(OperationProcessorContext 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This add every tags that are not in a x-tagGroups to a new tagGroups named "Other".
|
||||||
|
/// Since tags that are not in a tagGroups are not shown, this is necessary if you want them displayed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="postProcess">
|
||||||
|
/// The document to do this for. This should be done in the PostProcess part of the document or after
|
||||||
|
/// the main operation filter (see <see cref="OperationFilter"/>) has finished.
|
||||||
|
/// </param>
|
||||||
|
public static void AddLeftoversToOthersGroup(this OpenApiDocument postProcess)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use <see cref="ApiDefinitionAttribute"/> to create tags and groups of tags on the resulting swagger
|
||||||
|
/// document.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The settings of the swagger document.</param>
|
||||||
|
public static void UseApiTags(this AspNetCoreOpenApiDocumentGeneratorSettings options)
|
||||||
|
{
|
||||||
|
options.AddOperationFilter(OperationFilter);
|
||||||
|
options.PostProcess += x => x.AddLeftoversToOthersGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,15 +18,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Namotion.Reflection;
|
|
||||||
using NJsonSchema;
|
using NJsonSchema;
|
||||||
using NJsonSchema.Generation.TypeMappers;
|
using NJsonSchema.Generation.TypeMappers;
|
||||||
using NSwag;
|
using NSwag;
|
||||||
@ -77,75 +73,15 @@ namespace Kyoo.Swagger
|
|||||||
Name = "GPL-3.0-or-later",
|
Name = "GPL-3.0-or-later",
|
||||||
Url = "https://github.com/AnonymusRaccoon/Kyoo/blob/master/LICENSE"
|
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.UseApiTags();
|
||||||
|
options.SortApis();
|
||||||
options.AddOperationFilter(x =>
|
options.AddOperationFilter(x =>
|
||||||
{
|
{
|
||||||
if (x is AspNetCoreOperationProcessorContext ctx)
|
if (x is AspNetCoreOperationProcessorContext ctx)
|
||||||
return ctx.ApiDescription.ActionDescriptor.AttributeRouteInfo?.Order != AlternativeRoute;
|
return ctx.ApiDescription.ActionDescriptor.AttributeRouteInfo?.Order != AlternativeRoute;
|
||||||
return true;
|
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 =>
|
options.SchemaGenerator.Settings.TypeMappers.Add(new PrimitiveTypeMapper(typeof(Identifier), x =>
|
||||||
{
|
{
|
||||||
x.IsNullableRaw = false;
|
x.IsNullableRaw = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user