diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index e79bb825..9abda1c4 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -23,6 +23,18 @@ namespace Kyoo.Controllers IGenreRepository GenreRepository { get; } IProviderRepository ProviderRepository { get; } + // Get by id + Task GetLibrary(int id); + Task GetCollection(int id); + Task GetShow(int id); + Task GetSeason(int id); + Task GetSeason(int showID, int seasonNumber); + Task GetEpisode(int id); + Task GetEpisode(int showID, int seasonNumber, int episodeNumber); + Task GetGenre(int id); + Task GetStudio(int id); + Task GetPeople(int id); + // Get by slug Task GetLibrary(string slug); Task GetCollection(string slug); @@ -31,7 +43,6 @@ namespace Kyoo.Controllers Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber); Task GetMovieEpisode(string movieSlug); Task GetTrack(int id); - Task GetTrack(int episodeID, string language, bool isForced); Task GetGenre(string slug); Task GetStudio(string slug); Task GetPeople(string slug); @@ -150,8 +161,49 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetGenresFromShow(showSlug, where, new Sort(sort), limit); + Task> GetTracksFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(int episodeID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(episodeID, where, new Sort(sort), limit); + + Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(showID, seasonNumber, episodeNumber, where, new Sort(sort), limit); + + Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(showSlug, seasonNumber, episodeNumber, where, new Sort(sort), limit); + Task GetStudioFromShow(int showID); Task GetStudioFromShow(string showSlug); + Task GetShowFromSeason(int seasonID); + Task GetShowFromEpisode(int episodeID); + Task GetSeasonFromEpisode(int episodeID); Task> GetLibrariesFromShow(int showID, Expression> where = null, diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 2c4f5c23..01877446 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -142,6 +142,9 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromCollection(slug, where, new Sort(sort), limit); + + Task GetFromSeason(int seasonID); + Task GetFromEpisode(int episodeID); } public interface ISeasonRepository : IRepository @@ -169,11 +172,17 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); + + Task GetFromEpisode(int episodeID); } public interface IEpisodeRepository : IRepository { + Task Get(int showID, int seasonNumber, int episodeNumber); Task Get(string showSlug, int seasonNumber, int episodeNumber); + Task Get(int seasonID, int episodeNumber); + Task GetAbsolute(int showID, int absoluteNumber); + Task GetAbsolute(string showSlug, int absoluteNumber); Task Delete(string showSlug, int seasonNumber, int episodeNumber); Task> GetFromShow(int showID, @@ -231,7 +240,43 @@ namespace Kyoo.Controllers public interface ITrackRepository : IRepository { - Task Get(int episodeID, string languageTag, bool isForced); + Task> GetFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(int episodeID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(episodeID, where, new Sort(sort), limit); + + Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(showID, seasonNumber, episodeNumber, where, new Sort(sort), limit); + + Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(showSlug, seasonNumber, episodeNumber, where, new Sort(sort), limit); } public interface ILibraryRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index db85d358..cb926f21 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -75,6 +75,56 @@ namespace Kyoo.Controllers ); } + public Task GetLibrary(int id) + { + return LibraryRepository.Get(id); + } + + public Task GetCollection(int id) + { + return CollectionRepository.Get(id); + } + + public Task GetShow(int id) + { + return ShowRepository.Get(id); + } + + public Task GetSeason(int id) + { + return SeasonRepository.Get(id); + } + + public Task GetSeason(int showID, int seasonNumber) + { + return SeasonRepository.Get(showID, seasonNumber); + } + + public Task GetEpisode(int id) + { + return EpisodeRepository.Get(id); + } + + public Task GetEpisode(int showID, int seasonNumber, int episodeNumber) + { + return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); + } + + public Task GetGenre(int id) + { + return GenreRepository.Get(id); + } + + public Task GetStudio(int id) + { + return StudioRepository.Get(id); + } + + public Task GetPeople(int id) + { + return PeopleRepository.Get(id); + } + public Task GetLibrary(string slug) { return LibraryRepository.Get(slug); @@ -109,11 +159,6 @@ namespace Kyoo.Controllers { return TrackRepository.Get(id); } - - public Task GetTrack(int episodeID, string language, bool isForced) - { - return TrackRepository.Get(episodeID, language, isForced); - } public Task GetGenre(string slug) { @@ -290,6 +335,34 @@ namespace Kyoo.Controllers return GenreRepository.GetFromShow(showSlug, where, sort, limit); } + public Task> GetTracksFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(episodeID, where, sort, limit); + } + + public Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(showID, seasonNumber, episodeNumber, where, sort, limit); + } + + public Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(showSlug, seasonNumber, episodeNumber, where, sort, limit); + } + public Task GetStudioFromShow(int showID) { return StudioRepository.GetFromShow(showID); @@ -300,6 +373,21 @@ namespace Kyoo.Controllers return StudioRepository.GetFromShow(showSlug); } + public Task GetShowFromSeason(int seasonID) + { + return ShowRepository.GetFromSeason(seasonID); + } + + public Task GetShowFromEpisode(int episodeID) + { + return ShowRepository.GetFromEpisode(episodeID); + } + + public Task GetSeasonFromEpisode(int episodeID) + { + return SeasonRepository.GetFromEpisode(episodeID); + } + public Task> GetLibrariesFromShow(int showID, Expression> where = null, Sort sort = default, diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index bf8fa6a9..9c0676e4 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -61,7 +61,32 @@ namespace Kyoo.Controllers && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } - + + public Task Get(int showID, int seasonNumber, int episodeNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); + } + + public Task Get(int seasonID, int episodeNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID + && x.EpisodeNumber == episodeNumber); + } + + public Task GetAbsolute(int showID, int absoluteNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.AbsoluteNumber == absoluteNumber); + } + + public Task GetAbsolute(string showSlug, int absoluteNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.AbsoluteNumber == absoluteNumber); + } + public override async Task> Search(string query) { return await _database.Episodes diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index d48c340e..4875ed85 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -155,5 +155,10 @@ namespace Kyoo.Controllers if (obj.Episodes != null) await _episodes.Value.DeleteRange(obj.Episodes); } + + public Task GetFromEpisode(int episodeID) + { + return _database.Seasons.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 9ea7906c..a23d6c35 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -244,5 +244,15 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return shows; } + + public Task GetFromSeason(int seasonID) + { + return _database.Shows.FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID)); + } + + public Task GetFromEpisode(int episodeID) + { + return _database.Shows.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 805d5736..f6c81f70 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,22 +1,41 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { public class TrackRepository : LocalRepository, ITrackRepository { private readonly DatabaseContext _database; + private readonly Lazy _episodes; protected override Expression> DefaultSort => x => x.ID; - public TrackRepository(DatabaseContext database) : base(database) + public TrackRepository(DatabaseContext database, IServiceProvider services) : base(database) { _database = database; + _episodes = new Lazy(services.GetRequiredService); + } + + public override void Dispose() + { + _database.Dispose(); + if (_episodes.IsValueCreated) + _episodes.Value.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + await _database.DisposeAsync(); + if (_episodes.IsValueCreated) + await _episodes.Value.DisposeAsync(); } public override Task Get(string slug) @@ -42,14 +61,6 @@ namespace Kyoo.Controllers && x.Language == language && x.IsForced == forced); } - - public Task Get(int episodeID, string languageTag, bool isForced) - { - return _database.Tracks.FirstOrDefaultAsync(x => x.EpisodeID == episodeID - && x.Language == languageTag - && x.IsForced == isForced); - } - public override Task> Search(string query) { throw new InvalidOperationException("Tracks do not support the search method."); @@ -82,5 +93,55 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.EpisodeID == episodeID), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(episodeID) == null) + throw new ItemNotFound(); + return tracks; + } + + public async Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.Episode.ShowID == showID + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(showID, seasonNumber, episodeNumber) == null) + throw new ItemNotFound(); + return tracks; + } + + public async Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.Episode.Show.Slug == showSlug + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(showSlug, seasonNumber, episodeNumber) == null) + throw new ItemNotFound(); + return tracks; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/EpisodesAPI.cs b/Kyoo/Views/API/EpisodesAPI.cs deleted file mode 100644 index 736ffb02..00000000 --- a/Kyoo/Views/API/EpisodesAPI.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Microsoft.AspNetCore.Authorization; - -namespace Kyoo.Api -{ - [Route("api/[controller]")] - [ApiController] - public class EpisodesController : ControllerBase - { - private readonly ILibraryManager _libraryManager; - - public EpisodesController(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - [HttpGet("{showSlug}/season/{seasonNumber}")] - [Authorize(Policy="Read")] - public Task>> GetEpisodesForSeason(string showSlug, int seasonNumber) - { - throw new NotImplementedException(); - } - - [HttpGet("{showSlug}/season/{seasonNumber}/episode/{episodeNumber}")] - [Authorize(Policy="Read")] - [JsonDetailed] - public async Task> GetEpisode(string showSlug, int seasonNumber, int episodeNumber) - { - Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - - if (episode == null) - return NotFound(); - - return episode; - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/EpisodesApi.cs b/Kyoo/Views/API/EpisodesApi.cs new file mode 100644 index 00000000..8dd9fa92 --- /dev/null +++ b/Kyoo/Views/API/EpisodesApi.cs @@ -0,0 +1,173 @@ +using System; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/episode")] + [Route("api/episodes")] + [ApiController] + public class EpisodesApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public EpisodesApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.EpisodeRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{episodeID:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int episodeID) + { + return await _libraryManager.GetShowFromEpisode(episodeID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(string showSlug) + { + return await _libraryManager.GetShow(showSlug); + } + + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int showID, int _) + { + return await _libraryManager.GetShow(showID); + } + + [HttpGet("{episodeID:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(int episodeID) + { + return await _libraryManager.GetSeasonFromEpisode(episodeID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(string showSlug, int seasonNuber) + { + return await _libraryManager.GetSeason(showSlug, seasonNuber); + } + + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(int showID, int seasonNumber) + { + return await _libraryManager.GetSeason(showID, seasonNumber); + } + + [HttpGet("{episodeID:int}/track")] + [HttpGet("{episodeID:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(int episodeID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(episodeID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/track")] + [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(int showID, + int seasonNumber, + int episodeNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(showID, + seasonNumber, + episodeNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/track")] + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(showSlug, + seasonNumber, + episodeNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index c7da45d9..5825b370 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -56,8 +56,8 @@ namespace Kyoo.Api } } - [HttpGet("{showSlug}-{seasonNumber:int}/episode")] - [HttpGet("{showSlug}-{seasonNumber:int}/episodes")] + [HttpGet("{showSlug}-s{seasonNumber:int}/episode")] + [HttpGet("{showSlug}-s{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] public async Task>> GetEpisode(string showSlug, int seasonNumber, @@ -90,8 +90,8 @@ namespace Kyoo.Api } } - [HttpGet("{showID:int}-{seasonNumber:int}/episode")] - [HttpGet("{showID:int}-{seasonNumber:int}/episodes")] + [HttpGet("{showID:int}-s{seasonNumber:int}/episode")] + [HttpGet("{showID:int}-s{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] public async Task>> GetEpisode(int showID, int seasonNumber, @@ -123,5 +123,26 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{seasonID:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int seasonID) + { + return await _libraryManager.GetShowFromSeason(seasonID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(string showSlug, int _) + { + return await _libraryManager.GetShow(showSlug); + } + + [HttpGet("{showID:int}-s{seasonNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int showID, int _) + { + return await _libraryManager.GetShow(showID); + } } } \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 3dad4b29..7e8c7420 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 3dad4b29d6565d08ef0bce1e450b5de65f6fac16 +Subproject commit 7e8c74206356587b06272c5dd7c22fa09420d421