diff --git a/server/RdtClient.Web/Controllers/TorrentsController.cs b/server/RdtClient.Web/Controllers/TorrentsController.cs index d6c6ec0..1783c05 100644 --- a/server/RdtClient.Web/Controllers/TorrentsController.cs +++ b/server/RdtClient.Web/Controllers/TorrentsController.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MonoTorrent; @@ -6,15 +7,26 @@ using RdtClient.Data.Models.TorrentClient; using RdtClient.Service.Helpers; using RdtClient.Service.Services; using Torrent = RdtClient.Data.Models.Data.Torrent; +using System.Text.Json.Serialization; +using NSwag.Annotations; namespace RdtClient.Web.Controllers; +/// +/// Controller for managing torrents and their downloads +/// [Authorize(Policy = "AuthSetting")] [Route("Api/Torrents")] public class TorrentsController(ILogger logger, Torrents torrents, TorrentRunner torrentRunner) : Controller { + /// + /// Retrieves all torrents and their associated downloads + /// + /// List of all torrents with their download status + /// Returns the list of torrents [HttpGet] [Route("")] + [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] public async Task>> GetAll() { var results = await torrents.Get(); @@ -28,8 +40,17 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(results); } + /// + /// Retrieves a specific torrent by its ID + /// + /// The unique identifier of the torrent + /// The requested torrent details + /// Returns the requested torrent + /// Torrent not found [HttpGet] [Route("Get/{torrentId:guid}")] + [ProducesResponseType(typeof(Torrent), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetById(Guid torrentId) { var torrent = await torrents.GetById(torrentId); @@ -46,11 +67,13 @@ public class TorrentsController(ILogger logger, Torrents tor } /// - /// Used for debugging only. Force a tick. + /// Forces an immediate processing cycle for debugging purposes /// - /// + /// Success status + /// Processing cycle completed successfully [HttpGet] [Route("Tick")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task Tick() { await torrentRunner.Tick(); @@ -58,11 +81,21 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Adds a new torrent file with configuration + /// + /// The .torrent file to add + /// Configuration for the torrent download + /// Success status + /// Torrent added successfully + /// Invalid file or configuration provided [HttpPost] [Route("UploadFile")] - public async Task UploadFile([FromForm] IFormFile? file, + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(String), StatusCodes.Status400BadRequest)] + public async Task UploadFile([OpenApiFile] IFormFile? file, [ModelBinder(BinderType = typeof(JsonModelBinder))] - TorrentControllerUploadFileRequest? formData) + [FromForm] TorrentControllerUploadFileRequest? formData) { if (file == null || file.Length <= 0) { @@ -89,15 +122,24 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Adds a new torrent using a magnet link + /// + /// The magnet link and torrent configuration + /// Success status + /// Magnet link processed successfully + /// Invalid magnet link or configuration [HttpPost] [Route("UploadMagnet")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(String), StatusCodes.Status400BadRequest)] public async Task UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest? request) { if (request == null) { return BadRequest(); } - + if (String.IsNullOrEmpty(request.MagnetLink)) { return BadRequest("Invalid magnet link"); @@ -115,9 +157,18 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Checks available files in a torrent file + /// + /// The .torrent file to analyze + /// List of available files in the torrent + /// Returns the list of available files + /// Invalid torrent file provided [HttpPost] [Route("CheckFiles")] - public async Task CheckFiles([FromForm] IFormFile? file) + [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(String), StatusCodes.Status400BadRequest)] + public async Task>> CheckFiles([FromForm] IFormFile? file) { if (file == null || file.Length <= 0) { @@ -139,9 +190,18 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(result); } + /// + /// Checks available files from a magnet link + /// + /// The magnet link to analyze + /// List of available files in the torrent + /// Returns the list of available files + /// Invalid magnet link provided [HttpPost] [Route("CheckFilesMagnet")] - public async Task CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest? request) + [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(String), StatusCodes.Status400BadRequest)] + public async Task>> CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest? request) { if (request == null) { @@ -160,8 +220,18 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(result); } + /// + /// Deletes a torrent and optionally its associated data + /// + /// The unique identifier of the torrent to delete + /// Delete options specifying what should be removed + /// Success status + /// Torrent deleted successfully + /// Invalid request parameters [HttpPost] [Route("Delete/{torrentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest? request) { if (request == null) @@ -176,8 +246,15 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Retries a failed torrent download + /// + /// The unique identifier of the torrent to retry + /// Success status + /// Retry initiated successfully [HttpPost] [Route("Retry/{torrentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task Retry(Guid torrentId) { logger.LogDebug("Retry {torrentId}", torrentId); @@ -188,8 +265,15 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Retries a failed download within a torrent + /// + /// The unique identifier of the download to retry + /// Success status + /// Retry initiated successfully [HttpPost] [Route("RetryDownload/{downloadId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task RetryDownload(Guid downloadId) { logger.LogDebug("Retry download {downloadId}", downloadId); @@ -198,9 +282,18 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } - + + /// + /// Updates torrent configuration + /// + /// The updated torrent configuration + /// Success status + /// Torrent updated successfully + /// Invalid torrent configuration [HttpPut] [Route("Update")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Update([FromBody] Torrent? torrent) { if (torrent == null) @@ -213,9 +306,17 @@ public class TorrentsController(ILogger logger, Torrents tor return Ok(); } + /// + /// Tests regex patterns against torrent files + /// + /// The regex patterns and magnet link to test + /// Matching files and any regex errors + /// Returns the regex test results + /// Invalid request parameters [HttpPost] [Route("VerifyRegex")] - public async Task VerifyRegex([FromForm] IFormFile? file, [FromBody] TorrentControllerVerifyRegexRequest? request) + [ProducesResponseType(typeof(RegexVerificationResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> VerifyRegex([FromBody] TorrentControllerVerifyRegexRequest? request) { if (request == null) @@ -280,41 +381,119 @@ public class TorrentsController(ILogger logger, Torrents tor selectedFiles = [.. availableFiles]; } - return Ok(new + return Ok(new RegexVerificationResult { - includeError, - excludeError, - selectedFiles + IncludeError = includeError, + ExcludeError = excludeError, + SelectedFiles = selectedFiles }); } } +/// +/// Request model for uploading a torrent file +/// public class TorrentControllerUploadFileRequest { + /// + /// Configuration for the torrent download + /// + [Required] public Torrent? Torrent { get; set; } } +/// +/// Request model for adding a magnet link +/// public class TorrentControllerUploadMagnetRequest { + /// + /// The magnet URI to process + /// + [Required] public String? MagnetLink { get; set; } + + /// + /// Configuration for the torrent download + /// + [Required] public Torrent? Torrent { get; set; } } +/// +/// Request model for deleting a torrent +/// public class TorrentControllerDeleteRequest { + /// + /// Whether to delete the downloaded data + /// public Boolean DeleteData { get; set; } + + /// + /// Whether to remove the torrent from the Debrid service + /// public Boolean DeleteRdTorrent { get; set; } + + /// + /// Whether to delete local torrent files + /// public Boolean DeleteLocalFiles { get; set; } } +/// +/// Request model for checking files in a magnet link +/// public class TorrentControllerCheckFilesRequest { + /// + /// The magnet URI to analyze + /// + [Required] public String? MagnetLink { get; set; } } +/// +/// Request model for verifying regex patterns +/// public class TorrentControllerVerifyRegexRequest { + /// + /// Pattern for including files + /// public String? IncludeRegex { get; set; } + + /// + /// Pattern for excluding files + /// public String? ExcludeRegex { get; set; } - public String? MagnetLink { get; set;} + + /// + /// Magnet link to test patterns against + /// + public String? MagnetLink { get; set; } +} + +/// +/// Response model for regex verification results +/// +public class RegexVerificationResult +{ + /// + /// Error message for the include regex pattern, if any + /// + [JsonPropertyName("includeError")] + public String IncludeError { get; set; } = String.Empty; + + /// + /// Error message for the exclude regex pattern, if any + /// + [JsonPropertyName("excludeError")] + public String ExcludeError { get; set; } = String.Empty; + + /// + /// Files that match the specified patterns + /// + [JsonPropertyName("selectedFiles")] + public IList SelectedFiles { get; set; } = new List(); } \ No newline at end of file