mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Move transcoder api to /video with path b64 encoded to prevent db hit
This commit is contained in:
parent
f871f4e1bf
commit
59e1832c3e
42
back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
Normal file
42
back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
Normal 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.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Controllers;
|
||||||
|
|
||||||
|
public class Base64RouteConstraint : IRouteConstraint
|
||||||
|
{
|
||||||
|
static Regex Base64Reg = new("^[-A-Za-z0-9+/]*={0,3}$");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Match(
|
||||||
|
HttpContext? httpContext,
|
||||||
|
IRouter? route,
|
||||||
|
string routeKey,
|
||||||
|
RouteValueDictionary values,
|
||||||
|
RouteDirection routeDirection
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return values.TryGetValue(routeKey, out object? val)
|
||||||
|
&& val is string str
|
||||||
|
&& Base64Reg.IsMatch(str);
|
||||||
|
}
|
||||||
|
}
|
@ -76,6 +76,7 @@ public static class ServiceExtensions
|
|||||||
services.Configure<RouteOptions>(x =>
|
services.Configure<RouteOptions>(x =>
|
||||||
{
|
{
|
||||||
x.ConstraintMap.Add("id", typeof(IdentifierRouteConstraint));
|
x.ConstraintMap.Add("id", typeof(IdentifierRouteConstraint));
|
||||||
|
x.ConstraintMap.Add("base64", typeof(Base64RouteConstraint));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddResponseCompression(x =>
|
services.AddResponseCompression(x =>
|
||||||
|
@ -1,95 +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.Threading.Tasks;
|
|
||||||
using AspNetCore.Proxy;
|
|
||||||
using AspNetCore.Proxy.Options;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
|
||||||
using Kyoo.Utils;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Proxy to other services
|
|
||||||
/// </summary>
|
|
||||||
[ApiController]
|
|
||||||
[Obsolete("Use /episode/id/master.m3u8 or routes like that")]
|
|
||||||
public class ProxyApi(ILibraryManager library) : Controller
|
|
||||||
{
|
|
||||||
private Task _Proxy(string route, (string path, string route) info)
|
|
||||||
{
|
|
||||||
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
|
||||||
.Instance.WithBeforeSend(
|
|
||||||
(ctx, req) =>
|
|
||||||
{
|
|
||||||
req.Headers.Add("X-Path", info.path);
|
|
||||||
req.Headers.Add("X-Route", info.route);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.WithHandleFailure(
|
|
||||||
async (context, exception) =>
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
|
||||||
await context.Response.WriteAsJsonAsync(
|
|
||||||
new RequestError("Service unavailable")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.Build();
|
|
||||||
return this.HttpProxyAsync($"{Transcoder.TranscoderUrl}{route}", proxyOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transcoder proxy
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Simply proxy requests to the transcoder
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="rest">The path of the transcoder.</param>
|
|
||||||
/// <returns>The return value of the transcoder.</returns>
|
|
||||||
[Route("video/{type}/{id:id}/{**rest}")]
|
|
||||||
[Permission("video", Kind.Read)]
|
|
||||||
[Obsolete("Use /episode/id/master.m3u8 or routes like that")]
|
|
||||||
public async Task Proxy(
|
|
||||||
string type,
|
|
||||||
Identifier id,
|
|
||||||
string rest,
|
|
||||||
[FromQuery] Dictionary<string, string> query
|
|
||||||
)
|
|
||||||
{
|
|
||||||
string path = await (
|
|
||||||
type is "movie" or "movies"
|
|
||||||
? id.Match(
|
|
||||||
async id => (await library.Movies.Get(id)).Path,
|
|
||||||
async slug => (await library.Movies.Get(slug)).Path
|
|
||||||
)
|
|
||||||
: id.Match(
|
|
||||||
async id => (await library.Episodes.Get(id)).Path,
|
|
||||||
async slug => (await library.Episodes.Get(slug)).Path
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await _Proxy(rest + query.ToQueryString(), (path, $"{type}/{id}"));
|
|
||||||
}
|
|
||||||
}
|
|
122
back/src/Kyoo.Core/Views/Content/VideoApi.cs
Normal file
122
back/src/Kyoo.Core/Views/Content/VideoApi.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using AspNetCore.Proxy;
|
||||||
|
using AspNetCore.Proxy.Options;
|
||||||
|
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>
|
||||||
|
/// Private routes of the transcoder.
|
||||||
|
/// Url for these routes will be returned from /info or /master.m3u8 routes.
|
||||||
|
/// This should not be called manually
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("videos")]
|
||||||
|
[Route("video", Order = AlternativeRoute)]
|
||||||
|
[Permission("video", Kind.Read, Group = Group.Overall)]
|
||||||
|
[ApiDefinition("Video", Group = OtherGroup)]
|
||||||
|
public class VideoApi : Controller
|
||||||
|
{
|
||||||
|
public static string TranscoderUrl =
|
||||||
|
Environment.GetEnvironmentVariable("TRANSCODER_URL") ?? "http://transcoder:7666";
|
||||||
|
|
||||||
|
private Task _Proxy(string route)
|
||||||
|
{
|
||||||
|
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||||
|
.Instance.WithHandleFailure(
|
||||||
|
async (context, exception) =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||||
|
await context.Response.WriteAsJsonAsync(
|
||||||
|
new RequestError("Service unavailable")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.Build();
|
||||||
|
return this.HttpProxyAsync($"{TranscoderUrl}/{route}", proxyOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/direct")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task GetDirectStream(string path)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/direct");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/master.m3u8")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task GetMaster(string path)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/master.m3u8");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/{quality}/index.m3u8")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetVideoIndex(string path, string quality)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/{quality}/index.m3u8");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/{quality}/{segment}")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetVideoSegment(string path, string quality, string segment)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/{quality}/{segment}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/audio/{audio}/index.m3u8")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetAudioIndex(string path, string audio)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/audio/{audio}/index.m3u8");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/audio/{audio}/{segment}")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetAudioSegment(string path, string audio, string segment)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/audio/{audio}/{segment}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/attachment/{name}")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetAttachment(string path, string name)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/attachment/{name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{path:base64}/subtitle/{name}")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetSubtitle(string path, string name)
|
||||||
|
{
|
||||||
|
await _Proxy($"{path}/subtitle/{name}");
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AspNetCore.Proxy;
|
using AspNetCore.Proxy;
|
||||||
using AspNetCore.Proxy.Options;
|
using AspNetCore.Proxy.Options;
|
||||||
@ -29,27 +30,13 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Kyoo.Core.Api;
|
namespace Kyoo.Core.Api;
|
||||||
|
|
||||||
public static class Transcoder
|
|
||||||
{
|
|
||||||
public static string TranscoderUrl =
|
|
||||||
Environment.GetEnvironmentVariable("TRANSCODER_URL") ?? "http://transcoder:7666";
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsApi<T>(repository)
|
public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsApi<T>(repository)
|
||||||
where T : class, IResource, IThumbnails, IQuery
|
where T : class, IResource, IThumbnails, IQuery
|
||||||
{
|
{
|
||||||
private Task _Proxy(string route, (string path, string route) info)
|
private Task _Proxy(string route)
|
||||||
{
|
{
|
||||||
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||||
.Instance.WithBeforeSend(
|
.Instance.WithHandleFailure(
|
||||||
(ctx, req) =>
|
|
||||||
{
|
|
||||||
req.Headers.Add("X-Path", info.path);
|
|
||||||
req.Headers.Add("X-Route", info.route);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.WithHandleFailure(
|
|
||||||
async (context, exception) =>
|
async (context, exception) =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||||
@ -59,10 +46,16 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.Build();
|
.Build();
|
||||||
return this.HttpProxyAsync($"{Transcoder.TranscoderUrl}{route}", proxyOptions);
|
return this.HttpProxyAsync($"{VideoApi.TranscoderUrl}/{route}", proxyOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task<(string path, string route)> GetPath(Identifier identifier);
|
protected abstract Task<string> GetPath(Identifier identifier);
|
||||||
|
|
||||||
|
private async Task<string> _GetPath64(Identifier identifier)
|
||||||
|
{
|
||||||
|
string path = await GetPath(identifier);
|
||||||
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(path));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Direct stream
|
/// Direct stream
|
||||||
@ -76,11 +69,12 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||||
[HttpGet("{identifier:id}/direct")]
|
[HttpGet("{identifier:id}/direct")]
|
||||||
[PartialPermission(Kind.Play)]
|
[PartialPermission(Kind.Play)]
|
||||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task GetDirectStream(Identifier identifier)
|
public async Task<IActionResult> GetDirectStream(Identifier identifier)
|
||||||
{
|
{
|
||||||
await _Proxy("/direct", await GetPath(identifier));
|
// TODO: Remove the /api and use a proxy rewrite instead.
|
||||||
|
return Redirect($"/api/video/{await _GetPath64(identifier)}/direct");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -96,39 +90,12 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||||
[HttpGet("{identifier:id}/master.m3u8")]
|
[HttpGet("{identifier:id}/master.m3u8")]
|
||||||
[PartialPermission(Kind.Play)]
|
[PartialPermission(Kind.Play)]
|
||||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task GetMaster(Identifier identifier)
|
public async Task<IActionResult> GetMaster(Identifier identifier)
|
||||||
{
|
{
|
||||||
await _Proxy("/master.m3u8", await GetPath(identifier));
|
// TODO: Remove the /api and use a proxy rewrite instead.
|
||||||
}
|
return Redirect($"/api/video/{await _GetPath64(identifier)}/master.m3u8");
|
||||||
|
|
||||||
[HttpGet("{identifier:id}/{quality}/index.m3u8")]
|
|
||||||
[PartialPermission(Kind.Play)]
|
|
||||||
public async Task GetVideoIndex(Identifier identifier, string quality)
|
|
||||||
{
|
|
||||||
await _Proxy($"/{quality}/index.m3u8", await GetPath(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{identifier:id}/{quality}/{segment}")]
|
|
||||||
[PartialPermission(Kind.Play)]
|
|
||||||
public async Task GetVideoSegment(Identifier identifier, string quality, string segment)
|
|
||||||
{
|
|
||||||
await _Proxy($"/{quality}/{segment}", await GetPath(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{identifier:id}/audio/{audio}/index.m3u8")]
|
|
||||||
[PartialPermission(Kind.Play)]
|
|
||||||
public async Task GetAudioIndex(Identifier identifier, string audio)
|
|
||||||
{
|
|
||||||
await _Proxy($"/audio/{audio}/index.m3u8", await GetPath(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{identifier:id}/audio/{audio}/{segment}")]
|
|
||||||
[PartialPermission(Kind.Play)]
|
|
||||||
public async Task GetAudioSegment(Identifier identifier, string audio, string segment)
|
|
||||||
{
|
|
||||||
await _Proxy($"/audio/{audio}/{segment}", await GetPath(identifier));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,7 +111,7 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public async Task GetInfo(Identifier identifier)
|
public async Task GetInfo(Identifier identifier)
|
||||||
{
|
{
|
||||||
await _Proxy("/info", await GetPath(identifier));
|
await _Proxy($"{await _GetPath64(identifier)}/info");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -160,7 +127,7 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public async Task GetThumbnails(Identifier identifier)
|
public async Task GetThumbnails(Identifier identifier)
|
||||||
{
|
{
|
||||||
await _Proxy("/thumbnails.png", await GetPath(identifier));
|
await _Proxy($"{await _GetPath64(identifier)}/thumbnails.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -177,6 +144,6 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
|||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public async Task GetThumbnailsVtt(Identifier identifier)
|
public async Task GetThumbnailsVtt(Identifier identifier)
|
||||||
{
|
{
|
||||||
await _Proxy("/thumbnails.vtt", await GetPath(identifier));
|
await _Proxy($"{await _GetPath64(identifier)}/thumbnails.vtt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,12 +210,12 @@ public class EpisodeApi(ILibraryManager libraryManager)
|
|||||||
await libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
|
await libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<(string path, string route)> GetPath(Identifier identifier)
|
protected override async Task<string> GetPath(Identifier identifier)
|
||||||
{
|
{
|
||||||
string path = await identifier.Match(
|
string path = await identifier.Match(
|
||||||
async id => (await Repository.Get(id)).Path,
|
async id => (await Repository.Get(id)).Path,
|
||||||
async slug => (await Repository.Get(slug)).Path
|
async slug => (await Repository.Get(slug)).Path
|
||||||
);
|
);
|
||||||
return (path, $"/episodes/{identifier}");
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,12 +221,12 @@ public class MovieApi(ILibraryManager libraryManager) : TranscoderApi<Movie>(lib
|
|||||||
await libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetIdOrThrow());
|
await libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetIdOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<(string path, string route)> GetPath(Identifier identifier)
|
protected override async Task<string> GetPath(Identifier identifier)
|
||||||
{
|
{
|
||||||
string path = await identifier.Match(
|
string path = await identifier.Match(
|
||||||
async id => (await Repository.Get(id)).Path,
|
async id => (await Repository.Get(id)).Path,
|
||||||
async slug => (await Repository.Get(slug)).Path
|
async slug => (await Repository.Get(slug)).Path
|
||||||
);
|
);
|
||||||
return (path, $"/movies/{identifier}");
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user