docs(swagger): add JSDoc @openapi for status and history endpoints
- GET /api/status/status: admin-only, server/cache/polling/webhook metrics - GET /api/history/recent: filtered by user tag, deduplication logic - Document deduplication rules (imported suppresses failed) - Document availableForUpgrade flag - Include query parameters (days, showAll)
This commit is contained in:
+127
-29
@@ -195,38 +195,136 @@ function getRadarrLink(movie) {
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/history/recent
|
||||
* @openapi
|
||||
* /api/history/recent:
|
||||
* get:
|
||||
* tags: [History]
|
||||
* summary: Get recent history
|
||||
* description: |
|
||||
* Returns Sonarr/Radarr history records (imported + failed) for the authenticated user,
|
||||
* filtered to the last N days (default 7, max 90).
|
||||
*
|
||||
* Returns Sonarr/Radarr history records (imported + failed) for the
|
||||
* authenticated user, filtered to the last RECENT_COMPLETED_DAYS days
|
||||
* (default 7, overridable via env or ?days= query param).
|
||||
* **Authentication:** Requires valid `emby_user` cookie.
|
||||
*
|
||||
* Response shape:
|
||||
* {
|
||||
* user: string,
|
||||
* isAdmin: boolean,
|
||||
* days: number,
|
||||
* history: HistoryItem[]
|
||||
* }
|
||||
* **Filtering:**
|
||||
* - Non-admin users: Only see history items tagged with their username
|
||||
* - Admin users: Can see all history by setting query parameter `showAll=true`
|
||||
* - Date range: Configurable via `?days=` query parameter (default: 7, max: 90)
|
||||
*
|
||||
* HistoryItem shape:
|
||||
* {
|
||||
* type: 'series'|'movie',
|
||||
* outcome: 'imported'|'failed',
|
||||
* title: string, // sourceTitle from arr record
|
||||
* seriesName?: string, // series.title (Sonarr)
|
||||
* movieName?: string, // movie.title (Radarr)
|
||||
* coverArt: string|null,
|
||||
* completedAt: string, // ISO date string from arr record
|
||||
* quality: string|null,
|
||||
* instanceName: string, // arr instance name
|
||||
* arrLink: string|null, // link to item in Sonarr/Radarr UI
|
||||
* allTags: string[],
|
||||
* matchedUserTag: string|null,
|
||||
* // admin-only:
|
||||
* arrRecordId?: number,
|
||||
* failureMessage?: string,
|
||||
* }
|
||||
* **Deduplication Rules:**
|
||||
* For each unique content item (episode or movie), only the most recent record is shown:
|
||||
* - If the most recent event is "imported" → show it; suppress older failures
|
||||
* - If the most recent event is "failed" and the item has a file on disk → show with `availableForUpgrade=true`
|
||||
* - If the most recent event is "failed" and no file exists → show normally
|
||||
*
|
||||
* **Event Classification:**
|
||||
* - Sonarr: DownloadFolderImported, ImportFailed → included
|
||||
* - Radarr: DownloadFolderImported, ImportFailed → included
|
||||
* - Other event types (Rename, Health, etc.) → excluded
|
||||
*
|
||||
* **Response Structure:**
|
||||
* - `type`: "series" or "movie"
|
||||
* - `outcome`: "imported" or "failed"
|
||||
* - `title`: Source title from *arr record
|
||||
* - `seriesName`/`movieName`: Friendly media title
|
||||
* - `coverArt`: Poster URL
|
||||
* - `completedAt`: ISO 8601 timestamp
|
||||
* - `quality`: Quality string (e.g., "HDTV-1080p")
|
||||
* - `instanceName`: *arr instance name
|
||||
* - `arrLink`: Link to item in *arr UI
|
||||
* - `allTags`: All tags on the series/movie
|
||||
* - `matchedUserTag`: Tag matching the requesting user
|
||||
* - `availableForUpgrade`: True if failed but content is on disk (admin-only)
|
||||
* - `failureMessage`: Failure details (admin-only)
|
||||
*
|
||||
* **x-integration-notes:** Used by the history tab to show recently completed downloads.
|
||||
* Episodes are gathered from all history records sharing the same source title.
|
||||
* security:
|
||||
* - CookieAuth: []
|
||||
* parameters:
|
||||
* - name: days
|
||||
* in: query
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* maximum: 90
|
||||
* default: 7
|
||||
* description: Number of days to look back (max 90)
|
||||
* example: 7
|
||||
* - name: showAll
|
||||
* in: query
|
||||
* schema:
|
||||
* type: boolean
|
||||
* default: false
|
||||
* description: Admin-only: show all users' history
|
||||
* responses:
|
||||
* '200':
|
||||
* description: History items
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* type: string
|
||||
* example: "john"
|
||||
* isAdmin:
|
||||
* type: boolean
|
||||
* example: false
|
||||
* days:
|
||||
* type: integer
|
||||
* example: 7
|
||||
* history:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/HistoryItem'
|
||||
* example:
|
||||
* user: "john"
|
||||
* isAdmin: false
|
||||
* days: 7
|
||||
* history:
|
||||
* - type: "series"
|
||||
* outcome: "imported"
|
||||
* title: "Show.Name.S01E01.1080p.WEB-DL"
|
||||
* seriesName: "Show Name"
|
||||
* episodes:
|
||||
* - season: 1
|
||||
* episode: 1
|
||||
* title: "Pilot"
|
||||
* coverArt: "http://sonarr:8989/media/poster.jpg"
|
||||
* completedAt: "2026-05-21T10:00:00.000Z"
|
||||
* quality: "HDTV-1080p"
|
||||
* instanceName: "Main Sonarr"
|
||||
* arrLink: "http://sonarr:8989/series/show-slug"
|
||||
* allTags: ["user-john"]
|
||||
* matchedUserTag: "user-john"
|
||||
* '401':
|
||||
* description: Not authenticated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* '500':
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* x-code-samples:
|
||||
* - lang: curl
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl -X GET "http://localhost:3001/api/history/recent?days=7" \
|
||||
* -b cookies.txt
|
||||
* - lang: JavaScript
|
||||
* label: JavaScript (fetch)
|
||||
* source: |
|
||||
* const response = await fetch('http://localhost:3001/api/history/recent?days=7', {
|
||||
* method: 'GET',
|
||||
* credentials: 'include'
|
||||
* });
|
||||
* const data = await response.json();
|
||||
* console.log('History items:', data.history.length);
|
||||
*/
|
||||
router.get('/recent', requireAuth, async (req, res) => {
|
||||
try {
|
||||
|
||||
+97
-1
@@ -8,7 +8,103 @@ const { getSonarrInstances, getRadarrInstances } = require('../utils/config');
|
||||
const { getGlobalWebhookMetrics } = require('../utils/cache');
|
||||
const { checkWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus');
|
||||
|
||||
// Admin-only status page with cache stats
|
||||
/**
|
||||
* @openapi
|
||||
* /api/status/status:
|
||||
* get:
|
||||
* tags: [Status]
|
||||
* summary: Get server status (admin-only)
|
||||
* description: |
|
||||
* Admin-only endpoint returning server metrics, cache statistics, polling information,
|
||||
* and webhook metrics. Used by the admin status panel to monitor sofarr health.
|
||||
*
|
||||
* **Authentication:** Requires valid `emby_user` cookie (admin only).
|
||||
*
|
||||
* **Response Structure:**
|
||||
* - `server`: Uptime, Node version, memory usage
|
||||
* - `polling`: Polling enabled status, interval, last poll timings
|
||||
* - `cache`: Cache statistics (item count, sizes, TTLs)
|
||||
* - `webhooks`: Webhook configuration and metrics for Sonarr/Radarr
|
||||
*
|
||||
* **Webhook Metrics:**
|
||||
* - `configured`: Whether webhook is configured in Sonarr/Radarr
|
||||
* - `eventsReceived`: Total webhook events received
|
||||
* - `lastWebhookTimestamp`: Last webhook event time
|
||||
* - `pollsSkipped`: Number of poll cycles skipped due to recent webhook activity
|
||||
*
|
||||
* **x-integration-notes:** This endpoint is used by the admin status panel to display:
|
||||
* - Server health and resource usage
|
||||
* - Polling performance and timing
|
||||
* - Cache hit rates and sizes
|
||||
* - Webhook activity and smart polling effectiveness
|
||||
* security:
|
||||
* - CookieAuth: []
|
||||
* responses:
|
||||
* '200':
|
||||
* description: Status data
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/StatusResponse'
|
||||
* example:
|
||||
* server:
|
||||
* uptimeSeconds: 3600
|
||||
* nodeVersion: "v22.0.0"
|
||||
* memoryUsageMB: 128.5
|
||||
* heapUsedMB: 64.2
|
||||
* heapTotalMB: 128.0
|
||||
* polling:
|
||||
* enabled: true
|
||||
* intervalMs: 5000
|
||||
* lastPoll:
|
||||
* sabnzbdQueue: 150
|
||||
* sonarrQueue: 200
|
||||
* cache:
|
||||
* "poll:sab-queue":
|
||||
* size: 2456
|
||||
* items: 1
|
||||
* ttlRemaining: 12000
|
||||
* webhooks:
|
||||
* sonarr:
|
||||
* configured: true
|
||||
* eventsReceived: 42
|
||||
* lastWebhookTimestamp: "2026-05-21T10:00:00.000Z"
|
||||
* pollsSkipped: 15
|
||||
* radarr:
|
||||
* configured: true
|
||||
* eventsReceived: 38
|
||||
* lastWebhookTimestamp: "2026-05-21T09:55:00.000Z"
|
||||
* pollsSkipped: 12
|
||||
* '403':
|
||||
* description: Admin access required
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* example:
|
||||
* error: "Admin access required"
|
||||
* '500':
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* x-code-samples:
|
||||
* - lang: curl
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl -X GET http://localhost:3001/api/status/status \
|
||||
* -b cookies.txt
|
||||
* - lang: JavaScript
|
||||
* label: JavaScript (fetch)
|
||||
* source: |
|
||||
* const response = await fetch('http://localhost:3001/api/status/status', {
|
||||
* method: 'GET',
|
||||
* credentials: 'include'
|
||||
* });
|
||||
* const data = await response.json();
|
||||
* console.log('Uptime:', data.server.uptimeSeconds);
|
||||
*/
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = req.user;
|
||||
|
||||
Reference in New Issue
Block a user