diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index b010e8e2c..3c7cf1475 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -97,6 +97,7 @@ namespace NzbDrone.Host services.AddSwaggerGen(c => { + c.EnableAnnotations(); c.SwaggerDoc("v3", new OpenApiInfo { Version = "3.0.0", diff --git a/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs b/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs index c1f69974b..4fb3cdffb 100644 --- a/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs +++ b/src/Sonarr.Api.V3/Blocklist/BlocklistController.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Indexers; using Sonarr.Http; using Sonarr.Http.Extensions; using Sonarr.Http.REST.Attributes; +using Swashbuckle.AspNetCore.Annotations; namespace Sonarr.Api.V3.Blocklist { @@ -25,6 +26,7 @@ namespace Sonarr.Api.V3.Blocklist [HttpGet] [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a list of blocklists.")] public PagingResource GetBlocklist([FromQuery] PagingRequestResource paging, [FromQuery] int[] seriesIds = null, [FromQuery] DownloadProtocol[] protocols = null) { var pagingResource = new PagingResource(paging); @@ -44,6 +46,7 @@ namespace Sonarr.Api.V3.Blocklist } [RestDeleteById] + [SwaggerOperation(Summary = "Deletes a blocklist by id.")] public void DeleteBlocklist(int id) { _blocklistService.Delete(id); @@ -51,6 +54,7 @@ namespace Sonarr.Api.V3.Blocklist [HttpDelete("bulk")] [Produces("application/json")] + [SwaggerOperation(Summary = "Deletes a list of blocklists in bulk.")] public object Remove([FromBody] BlocklistBulkResource resource) { _blocklistService.Delete(resource.Ids); diff --git a/src/Sonarr.Api.V3/Calendar/CalendarController.cs b/src/Sonarr.Api.V3/Calendar/CalendarController.cs index 41a175435..5c8fe8f1d 100644 --- a/src/Sonarr.Api.V3/Calendar/CalendarController.cs +++ b/src/Sonarr.Api.V3/Calendar/CalendarController.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Tv; using NzbDrone.SignalR; using Sonarr.Api.V3.Episodes; using Sonarr.Http; +using Swashbuckle.AspNetCore.Annotations; namespace Sonarr.Api.V3.Calendar { @@ -29,8 +30,15 @@ namespace Sonarr.Api.V3.Calendar _tagService = tagService; } + [NonAction] + public override ActionResult GetResourceByIdWithErrorHandler(int id) + { + throw new NotImplementedException(); + } + [HttpGet] [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a list of episodes.")] public List GetCalendar(DateTime? start, DateTime? end, bool unmonitored = false, bool includeSeries = false, bool includeEpisodeFile = false, bool includeEpisodeImages = false, string tags = "") { var startUse = start ?? DateTime.Today; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseController.cs b/src/Sonarr.Api.V3/Indexers/ReleaseController.cs index 6de121914..578838d75 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentValidation; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using NLog; using NzbDrone.Common.Cache; @@ -18,6 +19,7 @@ using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Validation; using Sonarr.Http; +using Swashbuckle.AspNetCore.Annotations; using HttpStatusCode = System.Net.HttpStatusCode; namespace Sonarr.Api.V3.Indexers @@ -68,7 +70,8 @@ namespace Sonarr.Api.V3.Indexers [HttpPost] [Consumes("application/json")] - public async Task DownloadRelease([FromBody] ReleaseResource release) + [SwaggerOperation(Summary = "Sends release to the download client.")] + public async Task DownloadRelease([FromBody, SwaggerRequestBody(@"Requires ""guid"" and ""indexerId""", Required = true)] ReleaseResource release) { var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release)); @@ -89,7 +92,7 @@ namespace Sonarr.Api.V3.Indexers Ensure.That(release.Quality, () => release.Quality).IsNotNull(); Ensure.That(release.Languages, () => release.Languages).IsNotNull(); - // Clone the remote episode so we don't overwrite anything on the original + // Clone the remote episode, so we don't overwrite anything on the original remoteEpisode = new RemoteEpisode { Release = remoteEpisode.Release, @@ -170,6 +173,8 @@ namespace Sonarr.Api.V3.Indexers [HttpGet] [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a list of releases for an interactive search.")] + [SwaggerResponse(StatusCodes.Status200OK, type: typeof(List))] public async Task> GetReleases(int? seriesId, int? episodeId, int? seasonNumber) { if (episodeId.HasValue) diff --git a/src/Sonarr.Api.V3/Profiles/Delay/DelayProfileController.cs b/src/Sonarr.Api.V3/Profiles/Delay/DelayProfileController.cs index b632bdf08..a10b1ebfe 100644 --- a/src/Sonarr.Api.V3/Profiles/Delay/DelayProfileController.cs +++ b/src/Sonarr.Api.V3/Profiles/Delay/DelayProfileController.cs @@ -6,6 +6,7 @@ using Sonarr.Http; using Sonarr.Http.REST; using Sonarr.Http.REST.Attributes; using Sonarr.Http.Validation; +using Swashbuckle.AspNetCore.Annotations; namespace Sonarr.Api.V3.Profiles.Delay { @@ -35,6 +36,7 @@ namespace Sonarr.Api.V3.Profiles.Delay [RestPostById] [Consumes("application/json")] + [SwaggerOperation(Summary = "Creates a delay profile.")] public ActionResult Create([FromBody] DelayProfileResource resource) { var model = resource.ToModel(); @@ -44,6 +46,7 @@ namespace Sonarr.Api.V3.Profiles.Delay } [RestDeleteById] + [SwaggerOperation(Summary = "Deletes a delay profile.")] public void DeleteProfile(int id) { if (id == 1) @@ -56,6 +59,7 @@ namespace Sonarr.Api.V3.Profiles.Delay [RestPutById] [Consumes("application/json")] + [SwaggerOperation(Summary = "Updates a delay profile.")] public ActionResult Update([FromBody] DelayProfileResource resource) { var model = resource.ToModel(); @@ -70,12 +74,14 @@ namespace Sonarr.Api.V3.Profiles.Delay [HttpGet] [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a list of delay profiles.")] public List GetAll() { return _delayProfileService.All().ToResource(); } [HttpPut("reorder/{id}")] + [SwaggerOperation(Summary = "Updates order for delay profile.")] public List Reorder([FromRoute] int id, [FromQuery] int? after) { ValidateId(id); diff --git a/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj b/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj index 332f902ee..55e9f1458 100644 --- a/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj +++ b/src/Sonarr.Api.V3/Sonarr.Api.V3.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Sonarr.Api.V3/System/Backup/BackupController.cs b/src/Sonarr.Api.V3/System/Backup/BackupController.cs index 23d499b4e..c6577daf1 100644 --- a/src/Sonarr.Api.V3/System/Backup/BackupController.cs +++ b/src/Sonarr.Api.V3/System/Backup/BackupController.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Backup; using Sonarr.Http; using Sonarr.Http.REST; using Sonarr.Http.REST.Attributes; +using Swashbuckle.AspNetCore.Annotations; namespace Sonarr.Api.V3.System.Backup { @@ -32,6 +33,8 @@ namespace Sonarr.Api.V3.System.Backup } [HttpGet] + [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a list of backups.")] public List GetBackupFiles() { var backups = _backupService.GetBackups(); @@ -50,6 +53,7 @@ namespace Sonarr.Api.V3.System.Backup } [RestDeleteById] + [SwaggerOperation(Summary = "Deletes a backup.")] public void DeleteBackup(int id) { var backup = GetBackup(id); @@ -70,6 +74,8 @@ namespace Sonarr.Api.V3.System.Backup } [HttpPost("restore/{id:int}")] + [Produces("application/json")] + [SwaggerOperation(Summary = "Restores a backup.")] public object Restore([FromRoute] int id) { var backup = GetBackup(id); @@ -91,6 +97,8 @@ namespace Sonarr.Api.V3.System.Backup [HttpPost("restore/upload")] [RequestFormLimits(MultipartBodyLengthLimit = 500000000)] + [Produces("application/json")] + [SwaggerOperation(Summary = "Restores a backup by uploading.")] public object UploadAndRestore() { var files = Request.Form.Files; diff --git a/src/Sonarr.Api.V3/Wanted/CutoffController.cs b/src/Sonarr.Api.V3/Wanted/CutoffController.cs index 22be80366..caa189ea9 100644 --- a/src/Sonarr.Api.V3/Wanted/CutoffController.cs +++ b/src/Sonarr.Api.V3/Wanted/CutoffController.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; @@ -26,6 +27,12 @@ namespace Sonarr.Api.V3.Wanted _episodeCutoffService = episodeCutoffService; } + [NonAction] + public override ActionResult GetResourceByIdWithErrorHandler(int id) + { + throw new NotImplementedException(); + } + [HttpGet] [Produces("application/json")] public PagingResource GetCutoffUnmetEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeEpisodeFile = false, bool includeImages = false, bool monitored = true) diff --git a/src/Sonarr.Api.V3/Wanted/MissingController.cs b/src/Sonarr.Api.V3/Wanted/MissingController.cs index f7444f7a3..28d8daaa3 100644 --- a/src/Sonarr.Api.V3/Wanted/MissingController.cs +++ b/src/Sonarr.Api.V3/Wanted/MissingController.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; @@ -22,6 +23,12 @@ namespace Sonarr.Api.V3.Wanted { } + [NonAction] + public override ActionResult GetResourceByIdWithErrorHandler(int id) + { + throw new NotImplementedException(); + } + [HttpGet] [Produces("application/json")] public PagingResource GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeImages = false, bool monitored = true) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 83c0e8740..d51f27549 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -32,6 +32,7 @@ "tags": [ "ApiInfo" ], + "summary": "Get information about the API.", "responses": { "200": { "description": "Success" @@ -243,6 +244,7 @@ "tags": [ "AutoTagging" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -285,18 +287,11 @@ "tags": [ "Backup" ], + "summary": "Gets a list of backups.", "responses": { "200": { "description": "Success", "content": { - "text/plain": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BackupResource" - } - } - }, "application/json": { "schema": { "type": "array", @@ -304,14 +299,6 @@ "$ref": "#/components/schemas/BackupResource" } } - }, - "text/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BackupResource" - } - } } } } @@ -323,6 +310,7 @@ "tags": [ "Backup" ], + "summary": "Deletes a backup.", "parameters": [ { "name": "id", @@ -346,6 +334,7 @@ "tags": [ "Backup" ], + "summary": "Restores a backup.", "parameters": [ { "name": "id", @@ -369,6 +358,7 @@ "tags": [ "Backup" ], + "summary": "Restores a backup by uploading.", "responses": { "200": { "description": "Success" @@ -381,6 +371,7 @@ "tags": [ "Blocklist" ], + "summary": "Gets a list of blocklists.", "parameters": [ { "name": "page", @@ -455,6 +446,7 @@ "tags": [ "Blocklist" ], + "summary": "Deletes a blocklist by id.", "parameters": [ { "name": "id", @@ -478,6 +470,7 @@ "tags": [ "Blocklist" ], + "summary": "Deletes a list of blocklists in bulk.", "requestBody": { "content": { "application/json": { @@ -509,6 +502,7 @@ "tags": [ "Calendar" ], + "summary": "Gets a list of episodes.", "parameters": [ { "name": "start", @@ -584,36 +578,6 @@ } } }, - "/api/v3/calendar/{id}": { - "get": { - "tags": [ - "Calendar" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EpisodeResource" - } - } - } - } - } - } - }, "/feed/v3/calendar/sonarr.ics": { "get": { "tags": [ @@ -752,6 +716,7 @@ "tags": [ "Command" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -907,6 +872,7 @@ "tags": [ "CustomFilter" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1062,6 +1028,7 @@ "tags": [ "CustomFormat" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1189,6 +1156,7 @@ "tags": [ "Cutoff" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1219,6 +1187,7 @@ "tags": [ "DelayProfile" ], + "summary": "Creates a delay profile.", "requestBody": { "content": { "application/json": { @@ -1255,6 +1224,7 @@ "tags": [ "DelayProfile" ], + "summary": "Gets a list of delay profiles.", "responses": { "200": { "description": "Success", @@ -1277,6 +1247,7 @@ "tags": [ "DelayProfile" ], + "summary": "Deletes a delay profile.", "parameters": [ { "name": "id", @@ -1298,6 +1269,7 @@ "tags": [ "DelayProfile" ], + "summary": "Updates a delay profile.", "parameters": [ { "name": "id", @@ -1344,6 +1316,7 @@ "tags": [ "DelayProfile" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1374,6 +1347,7 @@ "tags": [ "DelayProfile" ], + "summary": "Updates order for delay profile.", "parameters": [ { "name": "id", @@ -1576,6 +1550,7 @@ "tags": [ "DownloadClient" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1814,6 +1789,7 @@ "tags": [ "DownloadClientConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -1958,6 +1934,7 @@ "tags": [ "Episode" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -2129,6 +2106,7 @@ "tags": [ "EpisodeFile" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -2676,6 +2654,7 @@ "tags": [ "HostConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -2829,6 +2808,7 @@ "tags": [ "ImportList" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3067,6 +3047,7 @@ "tags": [ "ImportListConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3276,6 +3257,7 @@ "tags": [ "ImportListExclusion" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3429,6 +3411,7 @@ "tags": [ "Indexer" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3667,6 +3650,7 @@ "tags": [ "IndexerConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3773,6 +3757,7 @@ "tags": [ "Language" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -3912,6 +3897,7 @@ "tags": [ "LanguageProfile" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -4000,6 +3986,7 @@ "tags": [ "Localization" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -4316,6 +4303,7 @@ "tags": [ "MediaManagementConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -4469,6 +4457,7 @@ "tags": [ "Metadata" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -4672,6 +4661,7 @@ "tags": [ "Missing" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -4787,6 +4777,7 @@ "tags": [ "NamingConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -5041,6 +5032,7 @@ "tags": [ "Notification" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -5294,6 +5286,7 @@ "tags": [ "QualityDefinition" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -5527,6 +5520,7 @@ "tags": [ "QualityProfile" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -5942,14 +5936,17 @@ "tags": [ "Release" ], + "summary": "Sends release to the download client.", "requestBody": { + "description": "Requires \"guid\" and \"indexerId\"", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReleaseResource" } } - } + }, + "required": true }, "responses": { "200": { @@ -5961,6 +5958,7 @@ "tags": [ "Release" ], + "summary": "Gets a list of releases for an interactive search.", "parameters": [ { "name": "seriesId", @@ -6170,6 +6168,7 @@ "tags": [ "ReleaseProfile" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -6382,6 +6381,7 @@ "tags": [ "RemotePathMapping" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -6531,6 +6531,7 @@ "tags": [ "RootFolder" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -7168,6 +7169,7 @@ "tags": [ "Tag" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -7220,6 +7222,7 @@ "tags": [ "TagDetails" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -7288,6 +7291,7 @@ "tags": [ "Task" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", @@ -7364,6 +7368,7 @@ "tags": [ "UiConfig" ], + "summary": "Gets a resource by id.", "parameters": [ { "name": "id", diff --git a/src/Sonarr.Http/ApiInfoController.cs b/src/Sonarr.Http/ApiInfoController.cs index aebdce4a7..16b96cabe 100644 --- a/src/Sonarr.Http/ApiInfoController.cs +++ b/src/Sonarr.Http/ApiInfoController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace NzbDrone.Http { @@ -7,6 +8,7 @@ namespace NzbDrone.Http { [HttpGet("/api")] [Produces("application/json")] + [SwaggerOperation(Summary = "Get information about the API.")] public object GetApiInfo() { return new ApiInfoResource diff --git a/src/Sonarr.Http/REST/RestController.cs b/src/Sonarr.Http/REST/RestController.cs index 7632d8b7f..04ee11d26 100644 --- a/src/Sonarr.Http/REST/RestController.cs +++ b/src/Sonarr.Http/REST/RestController.cs @@ -12,6 +12,7 @@ using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Datastore; using Sonarr.Http.REST.Attributes; using Sonarr.Http.Validation; +using Swashbuckle.AspNetCore.Annotations; namespace Sonarr.Http.REST { @@ -48,6 +49,7 @@ namespace Sonarr.Http.REST [RestGetById] [Produces("application/json")] + [SwaggerOperation(Summary = "Gets a resource by id.")] public virtual ActionResult GetResourceByIdWithErrorHandler(int id) { try diff --git a/src/Sonarr.Http/Sonarr.Http.csproj b/src/Sonarr.Http/Sonarr.Http.csproj index 6c0adc7d8..1a375fbab 100644 --- a/src/Sonarr.Http/Sonarr.Http.csproj +++ b/src/Sonarr.Http/Sonarr.Http.csproj @@ -6,6 +6,7 @@ +