From 873be173f2f3e1e7b63b07f801061973a746622a Mon Sep 17 00:00:00 2001 From: hunternick87 Date: Tue, 30 Sep 2025 15:50:14 +0000 Subject: [PATCH] Upload files to "/" --- openapi.json | 2999 ++++++++++++++++++++++++++++++++++++++++++++++++++ openapi.yaml | 227 ++-- 2 files changed, 3086 insertions(+), 140 deletions(-) create mode 100644 openapi.json diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..17c744c --- /dev/null +++ b/openapi.json @@ -0,0 +1,2999 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Arcfire API", + "version": "0.0.0", + "description": "Public and administrative endpoints exposed by the Arcfire media server.\nAll routes are served from the same origin as the web client. Runtime security\nis handled via bearer tokens (`Authorization: Bearer `) or the\n`auth_token` HTTP-only cookie set by the OpenID Connect login flow.\n", + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "http://localhost:3000", + "description": "Local development server" + } + ], + "security": [ + { + "bearerAuth": [] + }, + { + "cookieAuth": [] + } + ], + "tags": [ + { + "name": "System", + "description": "Service level endpoints that do not require authentication." + }, + { + "name": "Auth" + }, + { + "name": "Discover" + }, + { + "name": "Requests" + }, + { + "name": "Stream" + }, + { + "name": "Admin" + } + ], + "paths": { + "/health": { + "get": { + "tags": [ + "System" + ], + "summary": "Check service health", + "description": "Lightweight readiness probe used by load balancers and monitoring.", + "operationId": "getHealth", + "security": [], + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + }, + "uptime": { + "type": "number", + "format": "float" + }, + "version": { + "type": "string" + }, + "node": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "500": { + "description": "Service is unhealthy", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/auth/login": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Begin OIDC login", + "description": "Initiates the OpenID Connect authorization code flow and redirects to the upstream identity provider.", + "operationId": "beginLogin", + "parameters": [ + { + "in": "query", + "name": "remember", + "schema": { + "type": "string" + }, + "description": "Optional duration string that controls the session lifetime (e.g. `7d`)." + }, + { + "in": "query", + "name": "redirect", + "schema": { + "type": "string" + }, + "description": "Optional URL to redirect the user to once the login completes." + } + ], + "security": [], + "responses": { + "302": { + "description": "Redirect to the OIDC authorization endpoint" + }, + "500": { + "description": "OIDC login cannot be started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/auth/callback": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Complete OIDC callback", + "description": "Handles the authorization code returned by the identity provider, issues a session cookie, and redirects back to the client.", + "operationId": "completeLogin", + "parameters": [ + { + "in": "query", + "name": "code", + "schema": { + "type": "string" + }, + "required": true, + "description": "Authorization code issued by the identity provider." + }, + { + "in": "query", + "name": "state", + "schema": { + "type": "string" + }, + "required": true, + "description": "CSRF protection token that must match the stored cookie." + } + ], + "security": [], + "responses": { + "302": { + "description": "Redirect to the original destination or site root" + }, + "400": { + "description": "Invalid state or authorization code", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Login could not be completed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/auth/me": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Get the current user profile", + "operationId": "getCurrentUser", + "responses": { + "200": { + "description": "Authenticated user details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "No valid session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/auth/logout": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Terminate the active session", + "operationId": "logout", + "responses": { + "302": { + "description": "Session cleared and browser redirected to the site root" + }, + "500": { + "description": "Logout failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/auth/token": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Inspect the active API token", + "operationId": "getToken", + "responses": { + "200": { + "description": "Token retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthTokenResponse" + } + } + } + }, + "401": { + "description": "Token is invalid or expired", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "No session token cookie was found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/discover": { + "get": { + "tags": [ + "Discover" + ], + "summary": "List visible media", + "operationId": "listMedia", + "responses": { + "200": { + "description": "Media collection available to the user", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaItem" + } + } + } + } + } + } + } + }, + "/api/discover/{mediaId}": { + "get": { + "tags": [ + "Discover" + ], + "summary": "Get media details", + "operationId": "getMedia", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Media record including seasons and movies", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaWithLibrary" + } + } + } + }, + "404": { + "description": "Media item not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/discover/{mediaId}/{libraryId}": { + "get": { + "tags": [ + "Discover" + ], + "summary": "Get a library item", + "operationId": "getLibraryItem", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "libraryId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Library item with optional episode metadata", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryItemWithEpisodes" + } + } + } + }, + "404": { + "description": "Library item not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests": { + "get": { + "tags": [ + "Requests" + ], + "summary": "Get cached trending content", + "operationId": "getTrending", + "responses": { + "200": { + "description": "Cached trending payload sourced from TMDB and Rotten Tomatoes.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrendingResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Requests" + ], + "summary": "Submit a new media request", + "operationId": "createRequest", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestCreate" + } + } + } + }, + "responses": { + "200": { + "description": "Request accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Request received" + }, + "id": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid request payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "User is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Server error when creating request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests/my": { + "get": { + "tags": [ + "Requests" + ], + "summary": "List the caller's requests", + "operationId": "listMyRequests", + "parameters": [ + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Page offset (50 items per page)." + } + ], + "responses": { + "200": { + "description": "Requests created by the current user", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Request" + } + } + } + } + }, + "401": { + "description": "Caller is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests/search": { + "get": { + "tags": [ + "Requests" + ], + "summary": "Search TMDB for movies, shows, or people", + "operationId": "searchTmdb", + "parameters": [ + { + "in": "query", + "name": "q", + "required": true, + "schema": { + "type": "string" + }, + "description": "Free text search term forwarded to TMDB." + } + ], + "responses": { + "200": { + "description": "TMDB search results filtered to exclude people", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "object", + "description": "Raw TMDB search response.", + "additionalProperties": true + } + } + } + } + } + }, + "400": { + "description": "Missing query parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests/tv/{id}": { + "get": { + "tags": [ + "Requests" + ], + "summary": "Fetch extended TV information", + "operationId": "getTvDetails", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Combined TMDB data and local metadata", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TvDetailsResponse" + } + } + } + }, + "400": { + "description": "Missing TV id", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Failed to fetch details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests/movie/{id}": { + "get": { + "tags": [ + "Requests" + ], + "summary": "Fetch extended movie information", + "operationId": "getMovieDetails", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Combined TMDB data and local metadata", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MovieDetailsResponse" + } + } + } + }, + "400": { + "description": "Missing movie id", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Failed to fetch details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/requests/{id}": { + "delete": { + "tags": [ + "Requests" + ], + "summary": "Cancel a pending request", + "operationId": "deleteRequest", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Request cancelled", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Request canceled" + } + } + } + } + } + }, + "400": { + "description": "Request cannot be cancelled", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Caller is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Attempt to cancel another user's request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Request not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Cancellation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/stream/{libraryId}": { + "get": { + "tags": [ + "Stream" + ], + "summary": "Retrieve library metadata for playback", + "operationId": "getStreamLibraryItem", + "parameters": [ + { + "name": "libraryId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Library item metadata", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryItem" + } + } + } + }, + "404": { + "description": "Library item not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Stream" + ], + "summary": "Start a new on-demand stream", + "operationId": "startStream", + "parameters": [ + { + "name": "libraryId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StreamStartRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Stream initialized and encoder kicked off", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "$ref": "#/components/schemas/StreamSessionSummary" + }, + "streamId": { + "type": "string" + }, + "playlist": { + "type": "string", + "description": "Relative URL to the generated HLS playlist" + } + } + } + } + } + }, + "400": { + "description": "Request body missing required properties", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Caller is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Library item could not be located", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Stream initialization failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/stream/byStream/{streamId}/hls.m3u8": { + "get": { + "tags": [ + "Stream" + ], + "summary": "Retrieve the generated HLS playlist", + "operationId": "getHlsPlaylist", + "parameters": [ + { + "name": "streamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "M3U8 playlist", + "content": { + "application/vnd.apple.mpegurl": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Caller is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Playlist not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "Stream is not ready yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/stream/byStream/{streamId}/{segment}": { + "get": { + "tags": [ + "Stream" + ], + "summary": "Retrieve an encoded HLS segment", + "operationId": "getHlsSegment", + "parameters": [ + { + "name": "streamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segment", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Transport stream segment", + "content": { + "video/mp2t": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "Invalid segment name", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Caller is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Segment not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/requests": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List all requests (50 per page)", + "operationId": "adminListRequests", + "parameters": [ + { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Page index for pagination." + } + ], + "responses": { + "200": { + "description": "Requests ordered by creation time", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Request" + } + } + } + } + } + } + } + }, + "/api/admin/requests/deny/{id}": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Deny a request", + "operationId": "adminDenyRequest", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Request marked as denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Request" + } + } + } + }, + "404": { + "description": "Request not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Update failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/requests/approve/{id}": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Approve a request", + "operationId": "adminApproveRequest", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Request marked as approved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Request" + } + } + } + }, + "404": { + "description": "Request not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Update failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/users": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List all users", + "operationId": "adminListUsers", + "responses": { + "200": { + "description": "User directory", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + } + }, + "/api/admin/tasks": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Inspect recurring task statuses", + "operationId": "adminListTasks", + "responses": { + "200": { + "description": "Current scheduler state", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskStatus" + } + } + } + } + } + } + } + }, + "/api/admin/tasks/{taskName}": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Control a scheduled task", + "operationId": "adminControlTask", + "parameters": [ + { + "name": "taskName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskActionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Command accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid action or task state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/import/sonarr": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Trigger Sonarr import workflows", + "operationId": "adminSonarrImport", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SonarrImportRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Import started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "400": { + "description": "Invalid input parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Import command failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/media": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List all media including hidden items", + "operationId": "adminListMedia", + "responses": { + "200": { + "description": "Full media catalog", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaItem" + } + } + } + } + } + } + } + }, + "/api/admin/media/{mediaId}/images": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Fetch TMDB imagery for a media item", + "operationId": "adminGetMediaImages", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "TMDB imagery payload", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "404": { + "description": "Media not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Save media artwork", + "operationId": "adminSaveMediaImage", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaImageSelection" + } + } + } + }, + "responses": { + "200": { + "description": "Image persisted to disk", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "400": { + "description": "Missing type or url", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Media not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Image download failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/media/{mediaId}/season/{seasonNumber}/images": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Fetch TMDB season imagery", + "operationId": "adminGetSeasonImages", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seasonNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "TMDB season imagery payload", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "400": { + "description": "Media is not a series", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Media not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Save season artwork", + "operationId": "adminSaveSeasonImage", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seasonNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageUrlRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Season poster downloaded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "400": { + "description": "Missing URL or invalid parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Media or season not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Image download failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/media/{mediaId}/season/{seasonNumber}/episode/{episodeNumber}/images": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Fetch TMDB episode imagery", + "operationId": "adminGetEpisodeImages", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seasonNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "episodeNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "TMDB episode imagery payload", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "400": { + "description": "Media is not a series", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Media not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Save episode artwork", + "operationId": "adminSaveEpisodeImage", + "parameters": [ + { + "name": "mediaId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seasonNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "episodeNumber", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageUrlRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Episode still downloaded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "400": { + "description": "Missing URL or invalid parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Media or season not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Image download failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/database/stats": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Database statistics", + "operationId": "adminDatabaseStats", + "responses": { + "200": { + "description": "Aggregate metrics about the database", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseStats" + } + } + } + } + } + } + }, + "/api/admin/database/table": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List available tables", + "operationId": "adminDatabaseTables", + "responses": { + "200": { + "description": "Table names", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/admin/database/table/{tableName}": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Describe a table", + "operationId": "adminDescribeTable", + "parameters": [ + { + "name": "tableName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Column metadata", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TableColumn" + } + } + } + } + }, + "500": { + "description": "Unable to describe table", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Insert a new row", + "operationId": "adminInsertRow", + "parameters": [ + { + "name": "tableName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "responses": { + "200": { + "description": "Row inserted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "500": { + "description": "Insert failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/database/table/{tableName}/data": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Read table data", + "operationId": "adminGetTableData", + "parameters": [ + { + "name": "tableName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Rows stored in the table", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "500": { + "description": "Unable to fetch data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/admin/database/table/{tableName}/{id}": { + "put": { + "tags": [ + "Admin" + ], + "summary": "Update a row", + "operationId": "adminUpdateRow", + "parameters": [ + { + "name": "tableName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "responses": { + "200": { + "description": "Row updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "500": { + "description": "Update failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Admin" + ], + "summary": "Delete a row", + "operationId": "adminDeleteRow", + "parameters": [ + { + "name": "tableName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Row deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericSuccess" + } + } + } + }, + "500": { + "description": "Delete failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "UUID" + }, + "cookieAuth": { + "type": "apiKey", + "in": "cookie", + "name": "auth_token" + } + }, + "schemas": { + "ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "role": { + "type": "string", + "description": "Either `user` or `admin`." + } + }, + "required": [ + "id", + "username", + "role" + ] + }, + "AuthTokenResponse": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + }, + "MediaItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "movie", + "series" + ] + }, + "sub_type": { + "type": [ + "string", + "null" + ] + }, + "overview": { + "type": [ + "string", + "null" + ] + }, + "genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "parental_rating": { + "type": [ + "string", + "null" + ] + }, + "ratings": { + "type": "object", + "additionalProperties": true + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "tmdb_id": { + "type": "integer" + }, + "tvdb_id": { + "type": [ + "integer", + "null" + ] + }, + "imdb_id": { + "type": [ + "string", + "null" + ] + }, + "arr_id": { + "type": [ + "integer", + "null" + ] + }, + "hidden": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "type", + "tmdb_id" + ] + }, + "SeasonItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "season" + ] + }, + "name": { + "type": "string" + }, + "season_number": { + "type": "integer" + }, + "overview": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "hidden": { + "type": "boolean" + } + }, + "required": [ + "id", + "media_id", + "type", + "name", + "season_number" + ] + }, + "EpisodeItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "season_id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "episode" + ] + }, + "name": { + "type": "string" + }, + "season_number": { + "type": "integer" + }, + "episode_number": { + "type": "integer" + }, + "overview": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "hidden": { + "type": "boolean" + } + }, + "required": [ + "id", + "media_id", + "season_id", + "type", + "name", + "season_number", + "episode_number" + ] + }, + "LibraryItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "season_id": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "episode", + "movie", + "season" + ] + }, + "name": { + "type": "string" + }, + "season_number": { + "type": [ + "integer", + "null" + ] + }, + "episode_number": { + "type": [ + "integer", + "null" + ] + }, + "overview": { + "type": [ + "string", + "null" + ] + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "hidden": { + "type": "boolean" + } + }, + "required": [ + "id", + "media_id", + "type", + "name" + ] + }, + "LibraryItemWithEpisodes": { + "allOf": [ + { + "$ref": "#/components/schemas/LibraryItem" + }, + { + "type": "object", + "properties": { + "episodes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/EpisodeItem" + } + } + } + } + ] + }, + "MediaWithLibrary": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaItem" + }, + { + "type": "object", + "properties": { + "seasons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeasonItem" + } + }, + "movies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryItem" + } + } + } + } + ] + }, + "Request": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "tmdb_id": { + "type": "integer" + }, + "type": { + "type": "string", + "enum": [ + "movie", + "series" + ] + }, + "sub_type": { + "type": [ + "string", + "null" + ] + }, + "seasons": { + "type": "array", + "items": { + "type": "integer" + }, + "default": [] + }, + "status": { + "type": "string" + }, + "requester_id": { + "type": "string" + }, + "media_data": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "year": { + "type": [ + "integer", + "null" + ] + }, + "poster_path": { + "type": [ + "string", + "null" + ] + }, + "backdrop_path": { + "type": [ + "string", + "null" + ] + }, + "overview": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "required": [ + "id", + "tmdb_id", + "type", + "status", + "requester_id", + "media_data" + ] + }, + "RequestCreate": { + "type": "object", + "additionalProperties": false, + "properties": { + "tmdb_id": { + "type": "integer" + }, + "type": { + "type": "string", + "enum": [ + "movie", + "series" + ] + }, + "sub_type": { + "type": [ + "string", + "null" + ] + }, + "seasons": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Required when requesting a series." + }, + "image_paths": { + "type": "object", + "properties": { + "poster_path": { + "type": [ + "string", + "null" + ] + }, + "backdrop_path": { + "type": [ + "string", + "null" + ] + } + } + }, + "title": { + "type": "string" + }, + "overview": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "tmdb_id", + "type" + ] + }, + "TrendingItem": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "media_type": { + "type": "string" + }, + "overview": { + "type": [ + "string", + "null" + ] + }, + "poster_path": { + "type": [ + "string", + "null" + ] + }, + "backdrop_path": { + "type": [ + "string", + "null" + ] + }, + "vote_average": { + "type": [ + "number", + "null" + ], + "format": "float" + }, + "vote_count": { + "type": [ + "integer", + "null" + ] + }, + "popularity": { + "type": [ + "number", + "null" + ], + "format": "float" + }, + "release_date": { + "type": [ + "string", + "null" + ] + }, + "first_air_date": { + "type": [ + "string", + "null" + ] + }, + "genre_ids": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "additionalProperties": true + }, + "TrendingResponse": { + "type": "object", + "properties": { + "trendingShows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TrendingItem" + } + }, + "trendingMovies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TrendingItem" + } + }, + "trendingAnime": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TrendingItem" + } + } + } + }, + "TvDetailsResponse": { + "type": "object", + "properties": { + "details": { + "type": "object", + "additionalProperties": true + }, + "credits": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "recommendations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "similar": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "score": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "localdata": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "keywords": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "MovieDetailsResponse": { + "type": "object", + "properties": { + "details": { + "type": "object", + "additionalProperties": true + }, + "credits": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "recommendations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "similar": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "score": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "localdata": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "keywords": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "StreamStartRequest": { + "type": "object", + "properties": { + "options": { + "type": "object", + "description": "Playback capabilities and preferred qualities reported by the client player.", + "additionalProperties": true + }, + "supportedCodecs": { + "type": "array", + "description": "Video/audio codec identifiers supported by the client.", + "items": { + "type": "string" + } + } + }, + "required": [ + "options", + "supportedCodecs" + ] + }, + "StreamSessionSummary": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "video_streams": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "TaskStatus": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "lastRun": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "nextRun": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "running": { + "type": "boolean" + } + } + }, + "TaskActionRequest": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "run", + "enable", + "disable" + ] + } + }, + "required": [ + "action" + ] + }, + "SonarrImportRequest": { + "type": "object", + "properties": { + "seriesId": { + "type": "integer" + }, + "seasonNumber": { + "type": [ + "integer", + "null" + ] + }, + "episodeNumber": { + "type": [ + "integer", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "season", + "episode", + "series", + "all" + ] + } + }, + "required": [ + "seriesId", + "type" + ] + }, + "MediaImageSelection": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "poster", + "backdrop", + "logo" + ] + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "type", + "url" + ] + }, + "ImageUrlRequest": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ] + }, + "GenericSuccess": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "example": true + }, + "message": { + "type": [ + "string", + "null" + ] + }, + "result": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "path": { + "type": [ + "string", + "null" + ] + } + } + }, + "DatabaseStats": { + "type": "object", + "properties": { + "userCount": { + "type": [ + "integer", + "null" + ] + }, + "mediaCount": { + "type": [ + "integer", + "null" + ] + }, + "requestCount": { + "type": [ + "integer", + "null" + ] + }, + "activeRequests": { + "type": [ + "integer", + "null" + ] + }, + "completedRequests": { + "type": [ + "integer", + "null" + ] + }, + "userTokens": { + "type": [ + "integer", + "null" + ] + } + } + }, + "TableColumn": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "allowNull": { + "type": "boolean" + }, + "defaultValue": { + "type": [ + "string", + "number", + "boolean", + "object", + "null" + ] + }, + "primaryKey": { + "type": "boolean" + }, + "autoIncrement": { + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": true + } + } + } +} \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index 8c6f9f3..5342d88 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -7,6 +7,9 @@ info: All routes are served from the same origin as the web client. Runtime security is handled via bearer tokens (`Authorization: Bearer `) or the `auth_token` HTTP-only cookie set by the OpenID Connect login flow. + license: + name: MIT + url: https://opensource.org/licenses/MIT servers: - url: http://localhost:3000 description: Local development server @@ -1098,34 +1101,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /api/admin/database/table/{tableName}/data: - get: - tags: [Admin] - summary: Read table data - operationId: adminGetTableData - parameters: - - name: tableName - in: path - required: true - schema: - type: string - responses: - '200': - description: Rows stored in the table - content: - application/json: - schema: - type: array - items: - type: object - additionalProperties: true - '500': - description: Unable to fetch data - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - /api/admin/database/table/{tableName}: post: tags: [Admin] summary: Insert a new row @@ -1156,6 +1131,33 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + /api/admin/database/table/{tableName}/data: + get: + tags: [Admin] + summary: Read table data + operationId: adminGetTableData + parameters: + - name: tableName + in: path + required: true + schema: + type: string + responses: + '200': + description: Rows stored in the table + content: + application/json: + schema: + type: array + items: + type: object + additionalProperties: true + '500': + description: Unable to fetch data + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /api/admin/database/table/{tableName}/{id}: put: tags: [Admin] @@ -1248,11 +1250,9 @@ components: username: type: string display_name: - type: string - nullable: true + type: [string, 'null'] email: - type: string - nullable: true + type: [string, 'null'] role: type: string description: Either `user` or `admin`. @@ -1266,9 +1266,8 @@ components: token: type: string expires_at: - type: string + type: [string, 'null'] format: date-time - nullable: true MediaItem: type: object properties: @@ -1280,18 +1279,15 @@ components: type: string enum: [movie, series] sub_type: - type: string - nullable: true + type: [string, 'null'] overview: - type: string - nullable: true + type: [string, 'null'] genres: type: array items: type: string parental_rating: - type: string - nullable: true + type: [string, 'null'] ratings: type: object additionalProperties: true @@ -1301,14 +1297,11 @@ components: tmdb_id: type: integer tvdb_id: - type: integer - nullable: true + type: [integer, 'null'] imdb_id: - type: string - nullable: true + type: [string, 'null'] arr_id: - type: integer - nullable: true + type: [integer, 'null'] hidden: type: boolean required: @@ -1331,8 +1324,7 @@ components: season_number: type: integer overview: - type: string - nullable: true + type: [string, 'null'] metadata: type: object additionalProperties: true @@ -1363,8 +1355,7 @@ components: episode_number: type: integer overview: - type: string - nullable: true + type: [string, 'null'] metadata: type: object additionalProperties: true @@ -1386,22 +1377,18 @@ components: media_id: type: string season_id: - type: string - nullable: true + type: [string, 'null'] type: type: string enum: [episode, movie, season] name: type: string season_number: - type: integer - nullable: true + type: [integer, 'null'] episode_number: - type: integer - nullable: true + type: [integer, 'null'] overview: - type: string - nullable: true + type: [string, 'null'] metadata: type: object additionalProperties: true @@ -1418,10 +1405,9 @@ components: - type: object properties: episodes: - type: array + type: [array, 'null'] items: $ref: '#/components/schemas/EpisodeItem' - nullable: true MediaWithLibrary: allOf: - $ref: '#/components/schemas/MediaItem' @@ -1446,13 +1432,12 @@ components: type: string enum: [movie, series] sub_type: - type: string - nullable: true + type: [string, 'null'] seasons: type: array items: type: integer - nullable: true + default: [] status: type: string requester_id: @@ -1463,17 +1448,13 @@ components: title: type: string year: - type: integer - nullable: true + type: [integer, 'null'] poster_path: - type: string - nullable: true + type: [string, 'null'] backdrop_path: - type: string - nullable: true + type: [string, 'null'] overview: - type: string - nullable: true + type: [string, 'null'] required: - id - tmdb_id @@ -1491,8 +1472,7 @@ components: type: string enum: [movie, series] sub_type: - type: string - nullable: true + type: [string, 'null'] seasons: type: array items: @@ -1502,16 +1482,13 @@ components: type: object properties: poster_path: - type: string - nullable: true + type: [string, 'null'] backdrop_path: - type: string - nullable: true + type: [string, 'null'] title: type: string overview: - type: string - nullable: true + type: [string, 'null'] required: - tmdb_id - type @@ -1521,39 +1498,29 @@ components: id: type: integer title: - type: string - nullable: true + type: [string, 'null'] name: - type: string - nullable: true + type: [string, 'null'] media_type: type: string overview: - type: string - nullable: true + type: [string, 'null'] poster_path: - type: string - nullable: true + type: [string, 'null'] backdrop_path: - type: string - nullable: true + type: [string, 'null'] vote_average: - type: number + type: [number, 'null'] format: float - nullable: true vote_count: - type: integer - nullable: true + type: [integer, 'null'] popularity: - type: number + type: [number, 'null'] format: float - nullable: true release_date: - type: string - nullable: true + type: [string, 'null'] first_air_date: - type: string - nullable: true + type: [string, 'null'] genre_ids: type: array items: @@ -1581,8 +1548,7 @@ components: type: object additionalProperties: true credits: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true recommendations: type: array @@ -1595,12 +1561,10 @@ components: type: object additionalProperties: true score: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true localdata: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true keywords: type: array @@ -1614,8 +1578,7 @@ components: type: object additionalProperties: true credits: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true recommendations: type: array @@ -1628,12 +1591,10 @@ components: type: object additionalProperties: true score: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true localdata: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true keywords: type: array @@ -1675,13 +1636,11 @@ components: enabled: type: boolean lastRun: - type: string + type: [string, 'null'] format: date-time - nullable: true nextRun: - type: string + type: [string, 'null'] format: date-time - nullable: true running: type: boolean TaskActionRequest: @@ -1698,11 +1657,9 @@ components: seriesId: type: integer seasonNumber: - type: integer - nullable: true + type: [integer, 'null'] episodeNumber: - type: integer - nullable: true + type: [integer, 'null'] type: type: string enum: [season, episode, series, all] @@ -1736,36 +1693,27 @@ components: type: boolean example: true message: - type: string - nullable: true + type: [string, 'null'] result: - type: object - nullable: true + type: [object, 'null'] additionalProperties: true path: - type: string - nullable: true + type: [string, 'null'] DatabaseStats: type: object properties: userCount: - type: integer - nullable: true + type: [integer, 'null'] mediaCount: - type: integer - nullable: true + type: [integer, 'null'] requestCount: - type: integer - nullable: true + type: [integer, 'null'] activeRequests: - type: integer - nullable: true + type: [integer, 'null'] completedRequests: - type: integer - nullable: true + type: [integer, 'null'] userTokens: - type: integer - nullable: true + type: [integer, 'null'] TableColumn: type: object properties: @@ -1774,10 +1722,9 @@ components: allowNull: type: boolean defaultValue: - nullable: true + type: [string, number, boolean, object, 'null'] primaryKey: type: boolean autoIncrement: - type: boolean - nullable: true + type: [boolean, 'null'] additionalProperties: true