diff --git a/back/src/Kyoo.Core/Controllers/MiscRepository.cs b/back/src/Kyoo.Core/Controllers/MiscRepository.cs index 06afb8ad..eb32ae8d 100644 --- a/back/src/Kyoo.Core/Controllers/MiscRepository.cs +++ b/back/src/Kyoo.Core/Controllers/MiscRepository.cs @@ -84,6 +84,27 @@ public class MiscRepository( .ToListAsync(); } + public async Task DeletePath(string path, bool recurse) + { + // Make sure to include a path separator to prevents deletions from things like: + // DeletePath("/video/abc", true) -> /video/abdc (should not be deleted) + string dirPath = path.EndsWith("/") ? path : $"{path}/"; + + int count = await context + .Episodes.Where(x => x.Path == path || (recurse && x.Path.StartsWith(dirPath))) + .ExecuteDeleteAsync(); + count += await context + .Movies.Where(x => x.Path == path || (recurse && x.Path.StartsWith(dirPath))) + .ExecuteDeleteAsync(); + await context + .Issues.Where(x => + x.Domain == "scanner" + && (x.Cause == path || (recurse && x.Cause.StartsWith(dirPath))) + ) + .ExecuteDeleteAsync(); + return count; + } + public async Task> GetRefreshableItems(DateTime end) { IQueryable GetItems() diff --git a/back/src/Kyoo.Core/Views/Admin/Misc.cs b/back/src/Kyoo.Core/Views/Admin/Misc.cs index b2a35991..3f0f9c96 100644 --- a/back/src/Kyoo.Core/Views/Admin/Misc.cs +++ b/back/src/Kyoo.Core/Views/Admin/Misc.cs @@ -30,7 +30,7 @@ namespace Kyoo.Core.Api; /// Private APIs only used for other services. Can change at any time without notice. /// [ApiController] -[Permission(nameof(Misc), Kind.Read, Group = Group.Admin)] +[PartialPermission(nameof(Misc), Group = Group.Admin)] public class Misc(MiscRepository repo) : BaseApi { /// @@ -38,18 +38,40 @@ public class Misc(MiscRepository repo) : BaseApi /// /// The list of paths known to Kyoo. [HttpGet("/paths")] + [PartialPermission(Kind.Read)] [ProducesResponseType(StatusCodes.Status200OK)] public Task> GetAllPaths() { return repo.GetRegisteredPaths(); } + /// + /// Delete item at path. + /// + /// The path to delete. + /// + /// If true, the path will be considered as a directory and every children will be removed. + /// + /// Nothing + [HttpDelete("/paths")] + [PartialPermission(Kind.Delete)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeletePath( + [FromQuery] string path, + [FromQuery] bool recursive = false + ) + { + await repo.DeletePath(path, recursive); + return NoContent(); + } + /// /// List items to refresh. /// /// The upper limit for the refresh date. /// The items that should be refreshed before the given date [HttpGet("/refreshables")] + [PartialPermission(Kind.Read)] [ProducesResponseType(StatusCodes.Status200OK)] public Task> GetAllPaths([FromQuery] DateTime? date) { diff --git a/scanner/providers/kyoo_client.py b/scanner/providers/kyoo_client.py index e718c434..85156798 100644 --- a/scanner/providers/kyoo_client.py +++ b/scanner/providers/kyoo_client.py @@ -104,29 +104,16 @@ class KyooClient: async def delete( self, path: str, - type: Literal["episode", "movie"] | None = None, ): logger.info("Deleting %s", path) - if type is None or type == "movie": - async with self.client.delete( - f'{self._url}/movies?filter=path eq "{quote(path)}"', - headers={"X-API-Key": self._api_key}, - ) as r: - if not r.ok: - logger.error(f"Request error: {await r.text()}") - r.raise_for_status() - - if type is None or type == "episode": - async with self.client.delete( - f'{self._url}/episodes?filter=path eq "{quote(path)}"', - headers={"X-API-Key": self._api_key}, - ) as r: - if not r.ok: - logger.error(f"Request error: {await r.text()}") - r.raise_for_status() - - await self.delete_issue(path) + async with self.client.delete( + f'{self._url}/paths?recursive=true&path={quote(path)}', + headers={"X-API-Key": self._api_key}, + ) as r: + if not r.ok: + logger.error(f"Request error: {await r.text()}") + r.raise_for_status() async def get(self, path: str): async with self.client.get(