API: Documenting the search API

This commit is contained in:
Zoe Roux 2021-10-02 18:31:25 +02:00
parent cb6ea80adb
commit 40e32a1689
19 changed files with 282 additions and 147 deletions

View File

@ -23,8 +23,10 @@ namespace Kyoo.Abstractions.Models.Attributes
{ {
/// <summary> /// <summary>
/// An attribute to specify on apis to specify it's documentation's name and category. /// An attribute to specify on apis to specify it's documentation's name and category.
/// If this is applied on a method, the specified method will be exploded from the controller's page and be
/// included on the specified tag page.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ApiDefinitionAttribute : Attribute public class ApiDefinitionAttribute : Attribute
{ {
/// <summary> /// <summary>

View File

@ -54,14 +54,9 @@ namespace Kyoo.Abstractions.Models.Permissions
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will /// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
/// lead to unspecified behaviors. /// lead to unspecified behaviors.
/// </remarks> /// </remarks>
/// <param name="type"> /// <param name="type">The type of the action</param>
/// The type of the action
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
/// </param>
public PartialPermissionAttribute(string type) public PartialPermissionAttribute(string type)
{ {
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
type = type[..^3];
Type = type.ToLower(); Type = type.ToLower();
} }

View File

@ -91,17 +91,16 @@ namespace Kyoo.Abstractions.Models.Permissions
/// </summary> /// </summary>
/// <param name="type"> /// <param name="type">
/// The type of the action /// The type of the action
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
/// </param> /// </param>
/// <param name="permission">The kind of permission needed.</param> /// <param name="permission">
/// The kind of permission needed.
/// </param>
/// <param name="group"> /// <param name="group">
/// The group of this permission (allow grouped permission like overall.read /// The group of this permission (allow grouped permission like overall.read
/// for all read permissions of this group). /// for all read permissions of this group).
/// </param> /// </param>
public PermissionAttribute(string type, Kind permission, Group group = Group.Overall) public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
{ {
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
type = type[..^3];
Type = type.ToLower(); Type = type.ToLower();
Kind = permission; Kind = permission;
Group = group; Group = group;

View File

@ -36,7 +36,7 @@ namespace Kyoo.Core.Api
[Route("api/task", Order = AlternativeRoute)] [Route("api/task", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(TaskApi), Group = Group.Admin)] [PartialPermission("Task", Group = Group.Admin)]
[ApiDefinition("Tasks", Group = AdminGroup)] [ApiDefinition("Tasks", Group = AdminGroup)]
public class TaskApi : ControllerBase public class TaskApi : ControllerBase
{ {

View File

@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
[Route("api/genres")] [Route("api/genres")]
[Route("api/genre", Order = AlternativeRoute)] [Route("api/genre", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(GenreApi))] [PartialPermission(nameof(Genre))]
[ApiDefinition("Genres", Group = MetadataGroup)] [ApiDefinition("Genres", Group = MetadataGroup)]
public class GenreApi : CrudApi<Genre> public class GenreApi : CrudApi<Genre>
{ {

View File

@ -34,7 +34,7 @@ namespace Kyoo.Core.Api
[Route("api/provider", Order = AlternativeRoute)] [Route("api/provider", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(ProviderApi))] [PartialPermission(nameof(Provider))]
[ApiDefinition("Providers", Group = MetadataGroup)] [ApiDefinition("Providers", Group = MetadataGroup)]
public class ProviderApi : CrudThumbsApi<Provider> public class ProviderApi : CrudThumbsApi<Provider>
{ {

View File

@ -39,7 +39,7 @@ namespace Kyoo.Core.Api
[Route("api/people", Order = AlternativeRoute)] [Route("api/people", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(StaffApi))] [PartialPermission(nameof(People))]
[ApiDefinition("Staff", Group = MetadataGroup)] [ApiDefinition("Staff", Group = MetadataGroup)]
public class StaffApi : CrudThumbsApi<People> public class StaffApi : CrudThumbsApi<People>
{ {

View File

@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
[Route("api/studios")] [Route("api/studios")]
[Route("api/studio", Order = AlternativeRoute)] [Route("api/studio", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(ShowApi))] [PartialPermission(nameof(Show))]
[ApiDefinition("Studios", Group = MetadataGroup)] [ApiDefinition("Studios", Group = MetadataGroup)]
public class StudioApi : CrudApi<Studio> public class StudioApi : CrudApi<Studio>
{ {

View File

@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
[Route("api/collections")] [Route("api/collections")]
[Route("api/collection", Order = AlternativeRoute)] [Route("api/collection", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(CollectionApi))] [PartialPermission(nameof(Collection))]
[ApiDefinition("Collections", Group = ResourcesGroup)] [ApiDefinition("Collections", Group = ResourcesGroup)]
public class CollectionApi : CrudThumbsApi<Collection> public class CollectionApi : CrudThumbsApi<Collection>
{ {

View File

@ -38,7 +38,7 @@ namespace Kyoo.Core.Api
[Route("api/episode", Order = AlternativeRoute)] [Route("api/episode", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(EpisodeApi))] [PartialPermission(nameof(Episode))]
[ApiDefinition("Episodes", Group = ResourcesGroup)] [ApiDefinition("Episodes", Group = ResourcesGroup)]
public class EpisodeApi : CrudThumbsApi<Episode> public class EpisodeApi : CrudThumbsApi<Episode>
{ {

View File

@ -40,7 +40,7 @@ namespace Kyoo.Core.Api
[Route("api/library", Order = AlternativeRoute)] [Route("api/library", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(LibraryApi), Group = Group.Admin)] [PartialPermission(nameof(Library), Group = Group.Admin)]
[ApiDefinition("Library", Group = ResourcesGroup)] [ApiDefinition("Library", Group = ResourcesGroup)]
public class LibraryApi : CrudApi<Library> public class LibraryApi : CrudApi<Library>
{ {

View File

@ -38,6 +38,7 @@ namespace Kyoo.Core.Api
[Route("api/item", Order = AlternativeRoute)] [Route("api/item", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(LibraryItem))]
[ApiDefinition("Items", Group = ResourcesGroup)] [ApiDefinition("Items", Group = ResourcesGroup)]
public class LibraryItemApi : BaseApi public class LibraryItemApi : BaseApi
{ {
@ -74,7 +75,7 @@ namespace Kyoo.Core.Api
/// <response code="400">The filters or the sort parameters are invalid.</response> /// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No library with the given ID or slug could be found.</response> /// <response code="404">No library with the given ID or slug could be found.</response>
[HttpGet] [HttpGet]
[Permission(nameof(LibraryItemApi), Kind.Read)] [PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]

View File

@ -0,0 +1,194 @@
// 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.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
{
/// <summary>
/// An endpoint to search for every resources of kyoo. Searching for only a specific type of resource
/// is available on the said endpoint.
/// </summary>
[Route("api/search/{query}")]
[ApiController]
[ResourceView]
[ApiDefinition("Search", Group = ResourcesGroup)]
public class SearchApi : ControllerBase
{
/// <summary>
/// The library manager used to modify or retrieve information in the data store.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Create a new <see cref="SearchApi"/>.
/// </summary>
/// <param name="libraryManager">The library manager used to interact with the data store.</param>
public SearchApi(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Global search
/// </summary>
/// <remarks>
/// Search for collections, shows, episodes, staff, genre and studios at the same time
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of every resources found for the specified query.</returns>
[HttpGet]
[Permission(nameof(Collection), Kind.Read)]
[Permission(nameof(Show), Kind.Read)]
[Permission(nameof(Episode), Kind.Read)]
[Permission(nameof(People), Kind.Read)]
[Permission(nameof(Genre), Kind.Read)]
[Permission(nameof(Studio), Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<SearchResult>> Search(string query)
{
return new SearchResult
{
Query = query,
Collections = await _libraryManager.Search<Collection>(query),
Shows = await _libraryManager.Search<Show>(query),
Episodes = await _libraryManager.Search<Episode>(query),
People = await _libraryManager.Search<People>(query),
Genres = await _libraryManager.Search<Genre>(query),
Studios = await _libraryManager.Search<Studio>(query)
};
}
/// <summary>
/// Search collections
/// </summary>
/// <remarks>
/// Search for collections
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of collections found for the specified query.</returns>
[HttpGet("collections")]
[HttpGet("collection", Order = AlternativeRoute)]
[Permission(nameof(Collection), Kind.Read)]
[ApiDefinition("Collections")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Collection>> SearchCollections(string query)
{
return _libraryManager.Search<Collection>(query);
}
/// <summary>
/// Search shows
/// </summary>
/// <remarks>
/// Search for shows
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of shows found for the specified query.</returns>
[HttpGet("shows")]
[HttpGet("show", Order = AlternativeRoute)]
[Permission(nameof(Show), Kind.Read)]
[ApiDefinition("Shows")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Show>> SearchShows(string query)
{
return _libraryManager.Search<Show>(query);
}
/// <summary>
/// Search episodes
/// </summary>
/// <remarks>
/// Search for episodes
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of episodes found for the specified query.</returns>
[HttpGet("episodes")]
[HttpGet("episode", Order = AlternativeRoute)]
[Permission(nameof(Episode), Kind.Read)]
[ApiDefinition("Episodes")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Episode>> SearchEpisodes(string query)
{
return _libraryManager.Search<Episode>(query);
}
/// <summary>
/// Search staff
/// </summary>
/// <remarks>
/// Search for staff
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of staff members found for the specified query.</returns>
[HttpGet("staff")]
[HttpGet("person", Order = AlternativeRoute)]
[HttpGet("people", Order = AlternativeRoute)]
[Permission(nameof(People), Kind.Read)]
[ApiDefinition("Staff")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<People>> SearchPeople(string query)
{
return _libraryManager.Search<People>(query);
}
/// <summary>
/// Search genres
/// </summary>
/// <remarks>
/// Search for genres
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of genres found for the specified query.</returns>
[HttpGet("genres")]
[HttpGet("genre", Order = AlternativeRoute)]
[Permission(nameof(Genre), Kind.Read)]
[ApiDefinition("Genres")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Genre>> SearchGenres(string query)
{
return _libraryManager.Search<Genre>(query);
}
/// <summary>
/// Search studios
/// </summary>
/// <remarks>
/// Search for studios
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of studios found for the specified query.</returns>
[HttpGet("studios")]
[HttpGet("studio", Order = AlternativeRoute)]
[Permission(nameof(Studio), Kind.Read)]
[ApiDefinition("Studios")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Studio>> SearchStudios(string query)
{
return _libraryManager.Search<Studio>(query);
}
}
}

View File

@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
[Route("api/seasons")] [Route("api/seasons")]
[Route("api/season", Order = AlternativeRoute)] [Route("api/season", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(SeasonApi))] [PartialPermission(nameof(Season))]
[ApiDefinition("Seasons", Group = ResourcesGroup)] [ApiDefinition("Seasons", Group = ResourcesGroup)]
public class SeasonApi : CrudThumbsApi<Season> public class SeasonApi : CrudThumbsApi<Season>
{ {

View File

@ -44,7 +44,7 @@ namespace Kyoo.Core.Api
[Route("api/movie", Order = AlternativeRoute)] [Route("api/movie", Order = AlternativeRoute)]
[Route("api/movies", Order = AlternativeRoute)] [Route("api/movies", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(ShowApi))] [PartialPermission(nameof(Show))]
[ApiDefinition("Shows", Group = ResourcesGroup)] [ApiDefinition("Shows", Group = ResourcesGroup)]
public class ShowApi : CrudThumbsApi<Show> public class ShowApi : CrudThumbsApi<Show>
{ {

View File

@ -1,107 +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.Collections.Generic;
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/search/{query}")]
[ApiController]
public class SearchApi : ControllerBase
{
private readonly ILibraryManager _libraryManager;
public SearchApi(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
[HttpGet]
[Permission(nameof(Collection), Kind.Read)]
[Permission(nameof(Show), Kind.Read)]
[Permission(nameof(Episode), Kind.Read)]
[Permission(nameof(People), Kind.Read)]
[Permission(nameof(Genre), Kind.Read)]
[Permission(nameof(Studio), Kind.Read)]
public async Task<ActionResult<SearchResult>> Search(string query)
{
return new SearchResult
{
Query = query,
Collections = await _libraryManager.Search<Collection>(query),
Shows = await _libraryManager.Search<Show>(query),
Episodes = await _libraryManager.Search<Episode>(query),
People = await _libraryManager.Search<People>(query),
Genres = await _libraryManager.Search<Genre>(query),
Studios = await _libraryManager.Search<Studio>(query)
};
}
[HttpGet("collection")]
[HttpGet("collections")]
[Permission(nameof(Collection), Kind.Read)]
public Task<ICollection<Collection>> SearchCollections(string query)
{
return _libraryManager.Search<Collection>(query);
}
[HttpGet("show")]
[HttpGet("shows")]
[Permission(nameof(Show), Kind.Read)]
public Task<ICollection<Show>> SearchShows(string query)
{
return _libraryManager.Search<Show>(query);
}
[HttpGet("episode")]
[HttpGet("episodes")]
[Permission(nameof(Episode), Kind.Read)]
public Task<ICollection<Episode>> SearchEpisodes(string query)
{
return _libraryManager.Search<Episode>(query);
}
[HttpGet("people")]
[Permission(nameof(People), Kind.Read)]
public Task<ICollection<People>> SearchPeople(string query)
{
return _libraryManager.Search<People>(query);
}
[HttpGet("genre")]
[HttpGet("genres")]
[Permission(nameof(Genre), Kind.Read)]
public Task<ICollection<Genre>> SearchGenres(string query)
{
return _libraryManager.Search<Genre>(query);
}
[HttpGet("studio")]
[HttpGet("studios")]
[Permission(nameof(Studio), Kind.Read)]
public Task<ICollection<Studio>> SearchStudios(string query)
{
return _libraryManager.Search<Studio>(query);
}
}
}

View File

@ -18,6 +18,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Swagger.Models;
using NSwag; using NSwag;
using NSwag.Generation.AspNetCore; using NSwag.Generation.AspNetCore;
@ -49,13 +50,16 @@ namespace Kyoo.Swagger
{ {
if (!postProcess.ExtensionData.TryGetValue("x-tagGroups", out object list)) if (!postProcess.ExtensionData.TryGetValue("x-tagGroups", out object list))
return; return;
List<dynamic> tagGroups = (List<dynamic>)list; List<TagGroups> tagGroups = (List<TagGroups>)list;
postProcess.ExtensionData["x-tagGroups"] = tagGroups postProcess.ExtensionData["x-tagGroups"] = tagGroups
.OrderBy(x => x.name) .OrderBy(x => x.Name)
.Select(x => new .Select(x =>
{ {
name = x.name.Substring(x.name.IndexOf(':') + 1), x.Name = x.Name[(x.Name.IndexOf(':') + 1)..];
x.tags x.Tags = x.Tags
.OrderBy(y => y)
.ToList();
return x;
}) })
.ToList(); .ToList();
}; };

View File

@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Swagger.Models;
using Namotion.Reflection; using Namotion.Reflection;
using NSwag; using NSwag;
using NSwag.Generation.AspNetCore; using NSwag.Generation.AspNetCore;
@ -44,6 +45,10 @@ namespace Kyoo.Swagger
ApiDefinitionAttribute def = context.ControllerType.GetCustomAttribute<ApiDefinitionAttribute>(); ApiDefinitionAttribute def = context.ControllerType.GetCustomAttribute<ApiDefinitionAttribute>();
string name = def?.Name ?? context.ControllerType.Name; string name = def?.Name ?? context.ControllerType.Name;
ApiDefinitionAttribute methodOverride = context.MethodInfo.GetCustomAttribute<ApiDefinitionAttribute>();
if (methodOverride != null)
name = methodOverride.Name;
context.OperationDescription.Operation.Tags.Add(name); context.OperationDescription.Operation.Tags.Add(name);
if (context.Document.Tags.All(x => x.Name != name)) if (context.Document.Tags.All(x => x.Name != name))
{ {
@ -58,20 +63,20 @@ namespace Kyoo.Swagger
return true; return true;
context.Document.ExtensionData ??= new Dictionary<string, object>(); context.Document.ExtensionData ??= new Dictionary<string, object>();
context.Document.ExtensionData.TryAdd("x-tagGroups", new List<dynamic>()); context.Document.ExtensionData.TryAdd("x-tagGroups", new List<TagGroups>());
List<dynamic> obj = (List<dynamic>)context.Document.ExtensionData["x-tagGroups"]; List<TagGroups> obj = (List<TagGroups>)context.Document.ExtensionData["x-tagGroups"];
dynamic existing = obj.FirstOrDefault(x => x.name == def.Group); TagGroups existing = obj.FirstOrDefault(x => x.Name == def.Group);
if (existing != null) if (existing != null)
{ {
if (!existing.tags.Contains(def.Name)) if (!existing.Tags.Contains(def.Name))
existing.tags.Add(def.Name); existing.Tags.Add(def.Name);
} }
else else
{ {
obj.Add(new obj.Add(new TagGroups
{ {
name = def.Group, Name = def.Group,
tags = new List<string> { def.Name } Tags = new List<string> { def.Name }
}); });
} }
@ -88,19 +93,19 @@ namespace Kyoo.Swagger
/// </param> /// </param>
public static void AddLeftoversToOthersGroup(this OpenApiDocument postProcess) public static void AddLeftoversToOthersGroup(this OpenApiDocument postProcess)
{ {
List<dynamic> tagGroups = (List<dynamic>)postProcess.ExtensionData["x-tagGroups"]; List<TagGroups> tagGroups = (List<TagGroups>)postProcess.ExtensionData["x-tagGroups"];
List<string> tagsWithoutGroup = postProcess.Tags List<string> tagsWithoutGroup = postProcess.Tags
.Select(x => x.Name) .Select(x => x.Name)
.Where(x => tagGroups .Where(x => tagGroups
.SelectMany<dynamic, string>(y => y.tags) .SelectMany(y => y.Tags)
.All(y => y != x)) .All(y => y != x))
.ToList(); .ToList();
if (tagsWithoutGroup.Any()) if (tagsWithoutGroup.Any())
{ {
tagGroups.Add(new tagGroups.Add(new TagGroups
{ {
name = "Others", Name = "Others",
tags = tagsWithoutGroup Tags = tagsWithoutGroup
}); });
} }
} }

View File

@ -0,0 +1,42 @@
// 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 Newtonsoft.Json;
using NSwag;
namespace Kyoo.Swagger.Models
{
/// <summary>
/// A class representing a group of tags in the <see cref="OpenApiDocument"/>
/// </summary>
public class TagGroups
{
/// <summary>
/// The name of the tag group.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// The list of tags in this group.
/// </summary>
[JsonProperty(PropertyName = "tags")]
public List<string> Tags { get; set; }
}
}