From 7a0a009cedb4d31500bc270c97cda817b4359e25 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 21 Mar 2026 02:18:48 +0300 Subject: [PATCH] docs: auto-generate API route documentation Signed-off-by: NotAShelf Change-Id: Id0d1f9769b7ccdbf83d5fa78adef62e46a6a6964 --- Cargo.lock | Bin 243791 -> 245898 bytes Cargo.toml | 5 +- docs/api/analytics.md | 117 + docs/api/audit.md | 27 + docs/api/auth.md | 103 + docs/api/backup.md | 27 + docs/api/books.md | 208 + docs/api/collections.md | 157 + docs/api/config.md | 120 + docs/api/database.md | 51 + docs/api/duplicates.md | 20 + docs/api/enrichment.md | 71 + docs/api/export.md | 42 + docs/api/health.md | 63 + docs/api/integrity.md | 99 + docs/api/jobs.md | 62 + docs/api/media.md | 640 ++ docs/api/notes.md | 142 + docs/api/openapi.json | 12810 ++++++++++++++++++++++++++++++++++ docs/api/photos.md | 57 + docs/api/playlists.md | 229 + docs/api/plugins.md | 209 + docs/api/saved_searches.md | 62 + docs/api/scan.md | 42 + docs/api/scheduled_tasks.md | 62 + docs/api/search.md | 51 + docs/api/shares.md | 282 + docs/api/social.md | 196 + docs/api/statistics.md | 20 + docs/api/streaming.md | 115 + docs/api/subtitles.md | 120 + docs/api/sync.md | 412 ++ docs/api/tags.md | 157 + docs/api/transcode.md | 86 + docs/api/upload.md | 89 + docs/api/users.md | 207 + docs/api/webhooks.md | 34 + xtask/Cargo.toml | 17 + xtask/src/docs.rs | 215 + xtask/src/main.rs | 19 + 40 files changed, 17444 insertions(+), 1 deletion(-) create mode 100644 docs/api/analytics.md create mode 100644 docs/api/audit.md create mode 100644 docs/api/auth.md create mode 100644 docs/api/backup.md create mode 100644 docs/api/books.md create mode 100644 docs/api/collections.md create mode 100644 docs/api/config.md create mode 100644 docs/api/database.md create mode 100644 docs/api/duplicates.md create mode 100644 docs/api/enrichment.md create mode 100644 docs/api/export.md create mode 100644 docs/api/health.md create mode 100644 docs/api/integrity.md create mode 100644 docs/api/jobs.md create mode 100644 docs/api/media.md create mode 100644 docs/api/notes.md create mode 100644 docs/api/openapi.json create mode 100644 docs/api/photos.md create mode 100644 docs/api/playlists.md create mode 100644 docs/api/plugins.md create mode 100644 docs/api/saved_searches.md create mode 100644 docs/api/scan.md create mode 100644 docs/api/scheduled_tasks.md create mode 100644 docs/api/search.md create mode 100644 docs/api/shares.md create mode 100644 docs/api/social.md create mode 100644 docs/api/statistics.md create mode 100644 docs/api/streaming.md create mode 100644 docs/api/subtitles.md create mode 100644 docs/api/sync.md create mode 100644 docs/api/tags.md create mode 100644 docs/api/transcode.md create mode 100644 docs/api/upload.md create mode 100644 docs/api/users.md create mode 100644 docs/api/webhooks.md create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/docs.rs create mode 100644 xtask/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index e0cc2f5ba81f77ac62674b97c5005c1f4a3a32d5..fa3e1de05565c5e0ad4fc9f9b26b7b727eca831a 100644 GIT binary patch delta 1020 zcmZvb%WG6g6oU0_*iZ;~y!C-%X4V-j(Wp{hl>$aE5Y!}Gv(VgMo;5Sz015ihKlxWaK=L}O0ys$FrKodHKoHJom z($+~KHAF^Dk|tDN1tRokE==}+zkKBIH}YE_i;4Wp0y%MP$HTzbcluBsLrm01ouWxP zfe^x1LIRz1hC6Pgrz$ZnfI=`ap|>#wYiv{~@~i8`)JS<9_#pGfrY9$#O$>LoJFv>d z7`b#7kvp^$IYUvEYdJTOI>tQ@CWYWZnMy-@O{>gQbK}9rXq=W+Kl$dIqs-Qx{ zCd-A)H=mQKu`j5tLD`LY{0*7?VJMVm{wLE@Lm(I9%AQ^aSr_JYFfLFV9Iu#yBvC~R zIsm6A4MdSN1DaR>kE~ighQzDm+#6*OsO=Z)gYD+uE|MSf$m^o<^LEA^;P=S7{O_Y; zVLni4q_T;5<+b9X>LKZXI(R0ulS=D~Aqqxo`?+5s9raEz0U}5Ns$aAx$G_zEP)05D zpM=!wowyk9e1&_48aY1J>kO~K&|m+_R&#^wt9^Jp(uMLy{^wUx&oAB=^+W9yxDm@% a89UXj%v-ZXeY?&6x#Gyie=lm6CO!d*>p9H; delta 42 ycmeBb diff --git a/docs/api/analytics.md b/docs/api/analytics.md new file mode 100644 index 0000000..f706d2f --- /dev/null +++ b/docs/api/analytics.md @@ -0,0 +1,117 @@ +# Analytics + +Usage analytics and viewing history + +## Endpoints + +### POST /api/v1/analytics/events + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Event recorded | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/analytics/most-viewed + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `limit` | query | No | Maximum number of results | +| `offset` | query | No | Pagination offset | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Most viewed media | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/analytics/recently-viewed + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `limit` | query | No | Maximum number of results | +| `offset` | query | No | Pagination offset | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Recently viewed media | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/progress + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Watch progress | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### PUT /api/v1/media/{id}/progress + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Progress updated | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/audit.md b/docs/api/audit.md new file mode 100644 index 0000000..adcf493 --- /dev/null +++ b/docs/api/audit.md @@ -0,0 +1,27 @@ +# Audit + +Audit log entries + +## Endpoints + +### GET /api/v1/audit + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Page size | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Audit log entries | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/auth.md b/docs/api/auth.md new file mode 100644 index 0000000..f62c099 --- /dev/null +++ b/docs/api/auth.md @@ -0,0 +1,103 @@ +# Auth + +Authentication and session management + +## Endpoints + +### POST /api/v1/auth/login + +**Authentication:** Not required + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Login successful | +| 400 | Bad request | +| 401 | Invalid credentials | +| 500 | Internal server error | + +--- + +### POST /api/v1/auth/logout + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Logged out | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/auth/me + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Current user info | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/auth/refresh + +Refresh the current session, extending its expiry by the configured +duration. + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Session refreshed | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/auth/revoke-all + +Revoke all sessions for the current user + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | All sessions revoked | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/auth/sessions + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Active sessions | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/backup.md b/docs/api/backup.md new file mode 100644 index 0000000..e7c7884 --- /dev/null +++ b/docs/api/backup.md @@ -0,0 +1,27 @@ +# Backup + +Database backup + +## Endpoints + +### POST /api/v1/admin/backup + +Create a database backup and return it as a downloadable file. +POST /api/v1/admin/backup + +For `SQLite`: creates a backup via VACUUM INTO and returns the file. +For `PostgreSQL`: returns unsupported error (use `pg_dump` instead). + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Backup file download | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/books.md b/docs/api/books.md new file mode 100644 index 0000000..6af55ad --- /dev/null +++ b/docs/api/books.md @@ -0,0 +1,208 @@ +# Books + +Book metadata, series, authors, and reading progress + +## Endpoints + +### GET /api/v1/books + +List all books with optional search filters + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `isbn` | query | No | Filter by ISBN | +| `author` | query | No | Filter by author | +| `series` | query | No | Filter by series | +| `publisher` | query | No | Filter by publisher | +| `language` | query | No | Filter by language | +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of books | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/books/authors + +List all authors with book counts + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Authors with book counts | +| 401 | Unauthorized | + +--- + +### GET /api/v1/books/authors/{name}/books + +Get books by a specific author + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `name` | path | Yes | Author name | +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Books by author | +| 401 | Unauthorized | + +--- + +### GET /api/v1/books/reading-list + +Get user's reading list + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `status` | query | No | Filter by reading status | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Reading list | +| 401 | Unauthorized | + +--- + +### GET /api/v1/books/series + +List all series with book counts + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of series with counts | +| 401 | Unauthorized | + +--- + +### GET /api/v1/books/series/{name} + +Get books in a specific series + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `name` | path | Yes | Series name | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Books in series | +| 401 | Unauthorized | + +--- + +### GET /api/v1/books/{id}/metadata + +Get book metadata by media ID + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Book metadata | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### GET /api/v1/books/{id}/progress + +Get reading progress for a book + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Reading progress | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### PUT /api/v1/books/{id}/progress + +Update reading progress for a book + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 204 | Progress updated | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + diff --git a/docs/api/collections.md b/docs/api/collections.md new file mode 100644 index 0000000..f86b5e0 --- /dev/null +++ b/docs/api/collections.md @@ -0,0 +1,157 @@ +# Collections + +Media collections + +## Endpoints + +### GET /api/v1/collections + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of collections | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/collections + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Collection created | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/collections/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Collection ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Collection | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/collections/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Collection ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Collection deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/collections/{id}/members + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Collection ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Collection members | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/collections/{id}/members + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Collection ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Member added | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/collections/{id}/members/{media_id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Collection ID | +| `media_id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Member removed | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/config.md b/docs/api/config.md new file mode 100644 index 0000000..f88299f --- /dev/null +++ b/docs/api/config.md @@ -0,0 +1,120 @@ +# Config + +Server configuration + +## Endpoints + +### GET /api/v1/config + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Current server configuration | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/config/roots + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Updated configuration | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/config/roots + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Updated configuration | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### PATCH /api/v1/config/scanning + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Updated configuration | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/config/ui + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | UI configuration | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### PATCH /api/v1/config/ui + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Updated UI configuration | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/database.md b/docs/api/database.md new file mode 100644 index 0000000..373df31 --- /dev/null +++ b/docs/api/database.md @@ -0,0 +1,51 @@ +# Database + +Database administration + +## Endpoints + +### POST /api/v1/admin/database/clear + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Database cleared | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/admin/database/stats + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Database statistics | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/admin/database/vacuum + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Database vacuumed | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/duplicates.md b/docs/api/duplicates.md new file mode 100644 index 0000000..b1005b7 --- /dev/null +++ b/docs/api/duplicates.md @@ -0,0 +1,20 @@ +# Duplicates + +Duplicate media detection + +## Endpoints + +### GET /api/v1/media/duplicates + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Duplicate groups | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/enrichment.md b/docs/api/enrichment.md new file mode 100644 index 0000000..5012641 --- /dev/null +++ b/docs/api/enrichment.md @@ -0,0 +1,71 @@ +# Enrichment + +External metadata enrichment + +## Endpoints + +### POST /api/v1/media/enrich/batch + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Enrichment job submitted | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/enrich + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Enrichment job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/metadata/external + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | External metadata | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/export.md b/docs/api/export.md new file mode 100644 index 0000000..3410a9f --- /dev/null +++ b/docs/api/export.md @@ -0,0 +1,42 @@ +# Export + +Media library export + +## Endpoints + +### POST /api/v1/export + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Export job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/export/options + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Export job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/health.md b/docs/api/health.md new file mode 100644 index 0000000..44f6d4a --- /dev/null +++ b/docs/api/health.md @@ -0,0 +1,63 @@ +# Health + +Server health checks + +## Endpoints + +### GET /api/v1/health + +Comprehensive health check - includes database, filesystem, and cache status + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Health status | + +--- + +### GET /api/v1/health/detailed + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Detailed health status | + +--- + +### GET /api/v1/health/live + +Liveness probe - just checks if the server is running +Returns 200 OK if the server process is alive + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Server is alive | + +--- + +### GET /api/v1/health/ready + +Readiness probe - checks if the server can serve requests +Returns 200 OK if database is accessible + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Server is ready | +| 503 | Server not ready | + +--- + diff --git a/docs/api/integrity.md b/docs/api/integrity.md new file mode 100644 index 0000000..fd22c23 --- /dev/null +++ b/docs/api/integrity.md @@ -0,0 +1,99 @@ +# Integrity + +Library integrity checks and repairs + +## Endpoints + +### POST /api/v1/admin/integrity/orphans/detect + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Orphan detection job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/admin/integrity/orphans/resolve + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Orphans resolved | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/admin/integrity/thumbnails/cleanup + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Thumbnail cleanup job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/admin/integrity/thumbnails/generate + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Thumbnail generation job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/admin/integrity/verify + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Integrity verification job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + diff --git a/docs/api/jobs.md b/docs/api/jobs.md new file mode 100644 index 0000000..a46b7fd --- /dev/null +++ b/docs/api/jobs.md @@ -0,0 +1,62 @@ +# Jobs + +Background job management + +## Endpoints + +### GET /api/v1/jobs + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of jobs | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### GET /api/v1/jobs/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Job ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Job details | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/jobs/{id}/cancel + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Job ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Job cancelled | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + diff --git a/docs/api/media.md b/docs/api/media.md new file mode 100644 index 0000000..b612729 --- /dev/null +++ b/docs/api/media.md @@ -0,0 +1,640 @@ +# Media + +Media item management + +## Endpoints + +### GET /api/v1/media + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Page size | +| `sort` | query | No | Sort field | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of media items | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | All media deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/batch/collection + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch collection result | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/batch/delete + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch delete result | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/batch/move + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch move result | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/batch/tag + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch tag result | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/batch/update + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch update result | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/count + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media count | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/import + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media imported | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/import/batch + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Batch import results | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/import/directory + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Directory import results | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/import/options + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media imported | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/import/preview + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Directory preview | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/trash + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Page size | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Trashed media items | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/trash + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Trash emptied | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/trash/info + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Trash info | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media item | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### PATCH /api/v1/media/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Updated media item | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### PUT /api/v1/media/{id}/custom-fields + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Custom field set | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/{id}/custom-fields/{name} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | +| `name` | path | Yes | Custom field name | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Custom field deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/move + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Moved media item | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/open + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media opened | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/{id}/permanent + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | +| `permanent` | query | No | Set to 'true' for permanent deletion | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/rename + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Renamed media item | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/restore + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media restored | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/stream + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media stream | +| 206 | Partial content | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/thumbnail + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Thumbnail image | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/{id}/trash + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media moved to trash | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/notes.md b/docs/api/notes.md new file mode 100644 index 0000000..330c8f3 --- /dev/null +++ b/docs/api/notes.md @@ -0,0 +1,142 @@ +# Notes + +Markdown notes link graph + +## Endpoints + +### GET /api/v1/media/{id}/backlinks + +Get backlinks (incoming links) to a media item. + +GET /api/v1/media/{id}/backlinks + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Backlinks | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/outgoing-links + +Get outgoing links from a media item. + +GET /api/v1/media/{id}/outgoing-links + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Outgoing links | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/reindex-links + +Re-extract links from a media item. + +POST /api/v1/media/{id}/reindex-links + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Links reindexed | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/notes/graph + +Get graph data for visualization. + +GET /api/v1/notes/graph?center={uuid}&depth={n} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `center` | query | No | Center node ID | +| `depth` | query | No | Traversal depth (max 5, default 2) | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Graph data | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/notes/resolve-links + +Resolve all unresolved links in the database. + +POST /api/v1/notes/resolve-links + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Links resolved | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/notes/unresolved-count + +Get count of unresolved links. + +GET /api/v1/notes/unresolved-count + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Unresolved link count | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/openapi.json b/docs/api/openapi.json new file mode 100644 index 0000000..3f86923 --- /dev/null +++ b/docs/api/openapi.json @@ -0,0 +1,12810 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Pinakes API", + "description": "Media cataloging and library management API", + "license": { + "name": "EUPL-1.2", + "identifier": "EUPL-1.2" + }, + "version": "0.3.0-dev" + }, + "paths": { + "/api/v1/admin/backup": { + "post": { + "tags": [ + "backup" + ], + "summary": "Create a database backup and return it as a downloadable file.\nPOST /api/v1/admin/backup", + "description": "For `SQLite`: creates a backup via VACUUM INTO and returns the file.\nFor `PostgreSQL`: returns unsupported error (use `pg_dump` instead).", + "operationId": "create_backup", + "responses": { + "200": { + "description": "Backup file download" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/database/clear": { + "post": { + "tags": [ + "database" + ], + "operationId": "clear_database", + "responses": { + "200": { + "description": "Database cleared" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/database/stats": { + "get": { + "tags": [ + "database" + ], + "operationId": "database_stats", + "responses": { + "200": { + "description": "Database statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseStatsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/database/vacuum": { + "post": { + "tags": [ + "database" + ], + "operationId": "vacuum_database", + "responses": { + "200": { + "description": "Database vacuumed" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/integrity/orphans/detect": { + "post": { + "tags": [ + "integrity" + ], + "operationId": "trigger_orphan_detection", + "responses": { + "200": { + "description": "Orphan detection job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/integrity/orphans/resolve": { + "post": { + "tags": [ + "integrity" + ], + "operationId": "resolve_orphans", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrphanResolveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Orphans resolved" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/integrity/thumbnails/cleanup": { + "post": { + "tags": [ + "integrity" + ], + "operationId": "trigger_cleanup_thumbnails", + "responses": { + "200": { + "description": "Thumbnail cleanup job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/integrity/thumbnails/generate": { + "post": { + "tags": [ + "integrity" + ], + "operationId": "generate_all_thumbnails", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateThumbnailsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Thumbnail generation job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/integrity/verify": { + "post": { + "tags": [ + "integrity" + ], + "operationId": "trigger_verify_integrity", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerifyIntegrityRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Integrity verification job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users (admin only)", + "operationId": "list_users", + "responses": { + "200": { + "description": "List of users", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user (admin only)", + "operationId": "create_user", + "requestBody": { + "description": "username, password, role, and optional profile fields", + "content": { + "application/json": { + "schema": {} + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/users/{id}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get a specific user by ID", + "operationId": "get_user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user (admin only)", + "operationId": "delete_user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "users" + ], + "summary": "Update a user", + "operationId": "update_user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Optional password, role, or profile fields to update", + "content": { + "application/json": { + "schema": {} + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/admin/users/{id}/libraries": { + "get": { + "tags": [ + "users" + ], + "summary": "Get user's accessible libraries", + "operationId": "get_user_libraries", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User libraries", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserLibraryResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Grant library access to a user (admin only)", + "operationId": "grant_library_access", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GrantLibraryAccessRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Access granted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Revoke library access from a user (admin only)", + "description": "Uses a JSON body instead of a path parameter because `root_path` may contain\nslashes that conflict with URL routing.", + "operationId": "revoke_library_access", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "User ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevokeLibraryAccessRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Access revoked" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/analytics/events": { + "post": { + "tags": [ + "analytics" + ], + "operationId": "record_event", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RecordUsageEventRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Event recorded" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/analytics/most-viewed": { + "get": { + "tags": [ + "analytics" + ], + "operationId": "get_most_viewed", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximum number of results", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Most viewed media", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MostViewedResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/analytics/recently-viewed": { + "get": { + "tags": [ + "analytics" + ], + "operationId": "get_recently_viewed", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximum number of results", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Recently viewed media", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/audit": { + "get": { + "tags": [ + "audit" + ], + "operationId": "list_audit", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Page size", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Audit log entries", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditEntryResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/auth/login": { + "post": { + "tags": [ + "auth" + ], + "operationId": "login", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Login successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Invalid credentials" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [] + } + }, + "/api/v1/auth/logout": { + "post": { + "tags": [ + "auth" + ], + "operationId": "logout", + "responses": { + "200": { + "description": "Logged out" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/auth/me": { + "get": { + "tags": [ + "auth" + ], + "operationId": "me", + "responses": { + "200": { + "description": "Current user info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserInfoResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/auth/refresh": { + "post": { + "tags": [ + "auth" + ], + "summary": "Refresh the current session, extending its expiry by the configured\nduration.", + "operationId": "refresh", + "responses": { + "200": { + "description": "Session refreshed" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/auth/revoke-all": { + "post": { + "tags": [ + "auth" + ], + "summary": "Revoke all sessions for the current user", + "operationId": "revoke_all_sessions", + "responses": { + "200": { + "description": "All sessions revoked" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/auth/sessions": { + "get": { + "tags": [ + "auth" + ], + "operationId": "list_active_sessions", + "responses": { + "200": { + "description": "Active sessions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books": { + "get": { + "tags": [ + "books" + ], + "summary": "List all books with optional search filters", + "operationId": "list_books", + "parameters": [ + { + "name": "isbn", + "in": "query", + "description": "Filter by ISBN", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "author", + "in": "query", + "description": "Filter by author", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "series", + "in": "query", + "description": "Filter by series", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "publisher", + "in": "query", + "description": "Filter by publisher", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "description": "Filter by language", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "List of books", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/authors": { + "get": { + "tags": [ + "books" + ], + "summary": "List all authors with book counts", + "operationId": "list_authors", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Authors with book counts", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthorSummary" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/authors/{name}/books": { + "get": { + "tags": [ + "books" + ], + "summary": "Get books by a specific author", + "operationId": "get_author_books", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Author name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Books by author", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/reading-list": { + "get": { + "tags": [ + "books" + ], + "summary": "Get user's reading list", + "operationId": "get_reading_list", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Filter by reading status", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Reading list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/series": { + "get": { + "tags": [ + "books" + ], + "summary": "List all series with book counts", + "operationId": "list_series", + "responses": { + "200": { + "description": "List of series with counts", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesSummary" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/series/{name}": { + "get": { + "tags": [ + "books" + ], + "summary": "Get books in a specific series", + "operationId": "get_series_books", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Series name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Books in series", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/{id}/metadata": { + "get": { + "tags": [ + "books" + ], + "summary": "Get book metadata by media ID", + "operationId": "get_book_metadata", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Book metadata", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BookMetadataResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/books/{id}/progress": { + "get": { + "tags": [ + "books" + ], + "summary": "Get reading progress for a book", + "operationId": "get_reading_progress", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Reading progress", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadingProgressResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "put": { + "tags": [ + "books" + ], + "summary": "Update reading progress for a book", + "operationId": "update_reading_progress", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProgressRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Progress updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/collections": { + "get": { + "tags": [ + "collections" + ], + "operationId": "list_collections", + "responses": { + "200": { + "description": "List of collections", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CollectionResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "collections" + ], + "operationId": "create_collection", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCollectionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Collection created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/collections/{id}": { + "get": { + "tags": [ + "collections" + ], + "operationId": "get_collection", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Collection ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Collection", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "collections" + ], + "operationId": "delete_collection", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Collection ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Collection deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/collections/{id}/members": { + "get": { + "tags": [ + "collections" + ], + "operationId": "get_members", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Collection ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Collection members", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "collections" + ], + "operationId": "add_member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Collection ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddMemberRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Member added" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/collections/{id}/members/{media_id}": { + "delete": { + "tags": [ + "collections" + ], + "operationId": "remove_member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Collection ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Member removed" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/config": { + "get": { + "tags": [ + "config" + ], + "operationId": "get_config", + "responses": { + "200": { + "description": "Current server configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/config/roots": { + "post": { + "tags": [ + "config" + ], + "operationId": "add_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RootDirRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "config" + ], + "operationId": "remove_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RootDirRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/config/scanning": { + "patch": { + "tags": [ + "config" + ], + "operationId": "update_scanning_config", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScanningRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/config/ui": { + "get": { + "tags": [ + "config" + ], + "operationId": "get_ui_config", + "responses": { + "200": { + "description": "UI configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UiConfigResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "config" + ], + "operationId": "update_ui_config", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUiConfigRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated UI configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UiConfigResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/export": { + "post": { + "tags": [ + "export" + ], + "operationId": "trigger_export", + "responses": { + "200": { + "description": "Export job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/export/options": { + "post": { + "tags": [ + "export" + ], + "operationId": "trigger_export_with_options", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExportRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Export job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/favorites": { + "get": { + "tags": [ + "social" + ], + "operationId": "list_favorites", + "responses": { + "200": { + "description": "User favorites", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "social" + ], + "operationId": "add_favorite", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FavoriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Added to favorites" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/favorites/{media_id}": { + "delete": { + "tags": [ + "social" + ], + "operationId": "remove_favorite", + "parameters": [ + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Removed from favorites" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/health": { + "get": { + "tags": [ + "health" + ], + "summary": "Comprehensive health check - includes database, filesystem, and cache status", + "operationId": "health", + "responses": { + "200": { + "description": "Health status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + } + } + } + } + }, + "/api/v1/health/detailed": { + "get": { + "tags": [ + "health" + ], + "operationId": "health_detailed", + "responses": { + "200": { + "description": "Detailed health status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DetailedHealthResponse" + } + } + } + } + } + } + }, + "/api/v1/health/live": { + "get": { + "tags": [ + "health" + ], + "summary": "Liveness probe - just checks if the server is running\nReturns 200 OK if the server process is alive", + "operationId": "liveness", + "responses": { + "200": { + "description": "Server is alive" + } + } + } + }, + "/api/v1/health/ready": { + "get": { + "tags": [ + "health" + ], + "summary": "Readiness probe - checks if the server can serve requests\nReturns 200 OK if database is accessible", + "operationId": "readiness", + "responses": { + "200": { + "description": "Server is ready" + }, + "503": { + "description": "Server not ready" + } + } + } + }, + "/api/v1/jobs": { + "get": { + "tags": [ + "jobs" + ], + "operationId": "list_jobs", + "responses": { + "200": { + "description": "List of jobs" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/jobs/{id}": { + "get": { + "tags": [ + "jobs" + ], + "operationId": "get_job", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Job ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Job details" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/jobs/{id}/cancel": { + "post": { + "tags": [ + "jobs" + ], + "operationId": "cancel_job", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Job ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Job cancelled" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/managed/stats": { + "get": { + "tags": [ + "upload" + ], + "summary": "Get managed storage statistics\nGET /api/managed/stats", + "operationId": "managed_stats", + "responses": { + "200": { + "description": "Managed storage statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ManagedStorageStatsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media": { + "get": { + "tags": [ + "media" + ], + "operationId": "list_media", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Page size", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort field", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of media items", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "media" + ], + "operationId": "delete_all_media", + "responses": { + "200": { + "description": "All media deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/batch/collection": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_add_to_collection", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchCollectionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch collection result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/batch/delete": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_delete", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchDeleteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch delete result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/batch/move": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_move_media", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchMoveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch move result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/batch/tag": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_tag", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchTagRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch tag result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/batch/update": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_update", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchUpdateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch update result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchOperationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/count": { + "get": { + "tags": [ + "media" + ], + "operationId": "get_media_count", + "responses": { + "200": { + "description": "Media count", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaCountResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/duplicates": { + "get": { + "tags": [ + "duplicates" + ], + "operationId": "list_duplicates", + "responses": { + "200": { + "description": "Duplicate groups", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DuplicateGroupResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/enrich/batch": { + "post": { + "tags": [ + "enrichment" + ], + "operationId": "batch_enrich", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchDeleteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Enrichment job submitted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/import": { + "post": { + "tags": [ + "media" + ], + "operationId": "import_media", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Media imported", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/import/batch": { + "post": { + "tags": [ + "media" + ], + "operationId": "batch_import", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchImportRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Batch import results", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchImportResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/import/directory": { + "post": { + "tags": [ + "media" + ], + "operationId": "import_directory_endpoint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectoryImportRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Directory import results", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchImportResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/import/options": { + "post": { + "tags": [ + "media" + ], + "operationId": "import_with_options", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportWithOptionsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Media imported", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/import/preview": { + "post": { + "tags": [ + "media" + ], + "operationId": "preview_directory", + "requestBody": { + "content": { + "application/json": { + "schema": {} + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Directory preview", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectoryPreviewResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/share": { + "post": { + "tags": [ + "social" + ], + "operationId": "create_share_link", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateShareLinkRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Share link created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareLinkResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/trash": { + "get": { + "tags": [ + "media" + ], + "operationId": "list_trash", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Page size", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Trashed media items", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrashResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "media" + ], + "operationId": "empty_trash", + "responses": { + "200": { + "description": "Trash emptied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyTrashResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/trash/info": { + "get": { + "tags": [ + "media" + ], + "operationId": "trash_info", + "responses": { + "200": { + "description": "Trash info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrashInfoResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}": { + "get": { + "tags": [ + "media" + ], + "operationId": "get_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "media" + ], + "operationId": "delete_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "media" + ], + "operationId": "update_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMediaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated media item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/backlinks": { + "get": { + "tags": [ + "notes" + ], + "summary": "Get backlinks (incoming links) to a media item.", + "description": "GET /api/v1/media/{id}/backlinks", + "operationId": "get_backlinks", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Backlinks", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BacklinksResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/comments": { + "get": { + "tags": [ + "social" + ], + "operationId": "get_media_comments", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media comments", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CommentResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "social" + ], + "operationId": "add_comment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCommentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Comment added", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CommentResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/custom-fields": { + "put": { + "tags": [ + "media" + ], + "operationId": "set_custom_field", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCustomFieldRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Custom field set" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/custom-fields/{name}": { + "delete": { + "tags": [ + "media" + ], + "operationId": "delete_custom_field", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "name", + "in": "path", + "description": "Custom field name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Custom field deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/download": { + "get": { + "tags": [ + "upload" + ], + "summary": "Download a managed file\nGET /api/media/{id}/download", + "operationId": "download_file", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "File content" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/enrich": { + "post": { + "tags": [ + "enrichment" + ], + "operationId": "trigger_enrichment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Enrichment job submitted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/metadata/external": { + "get": { + "tags": [ + "enrichment" + ], + "operationId": "get_external_metadata", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "External metadata", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalMetadataResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/move": { + "post": { + "tags": [ + "media" + ], + "operationId": "move_media_endpoint", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveMediaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Moved media item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/move-to-managed": { + "post": { + "tags": [ + "upload" + ], + "summary": "Migrate an external file to managed storage\nPOST /api/media/{id}/move-to-managed", + "operationId": "move_to_managed", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "File migrated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/open": { + "post": { + "tags": [ + "media" + ], + "operationId": "open_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media opened" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/outgoing-links": { + "get": { + "tags": [ + "notes" + ], + "summary": "Get outgoing links from a media item.", + "description": "GET /api/v1/media/{id}/outgoing-links", + "operationId": "get_outgoing_links", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Outgoing links", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OutgoingLinksResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/permanent": { + "delete": { + "tags": [ + "media" + ], + "operationId": "permanent_delete_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "permanent", + "in": "query", + "description": "Set to 'true' for permanent deletion", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Media deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/progress": { + "get": { + "tags": [ + "analytics" + ], + "operationId": "get_watch_progress", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Watch progress", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WatchProgressResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "put": { + "tags": [ + "analytics" + ], + "operationId": "update_watch_progress", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WatchProgressRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Progress updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/rate": { + "post": { + "tags": [ + "social" + ], + "operationId": "rate_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRatingRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Rating saved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RatingResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/ratings": { + "get": { + "tags": [ + "social" + ], + "operationId": "get_media_ratings", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media ratings", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RatingResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/reindex-links": { + "post": { + "tags": [ + "notes" + ], + "summary": "Re-extract links from a media item.", + "description": "POST /api/v1/media/{id}/reindex-links", + "operationId": "reindex_links", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Links reindexed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReindexResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/rename": { + "post": { + "tags": [ + "media" + ], + "operationId": "rename_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RenameMediaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Renamed media item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/restore": { + "post": { + "tags": [ + "media" + ], + "operationId": "restore_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media restored", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream": { + "get": { + "tags": [ + "media" + ], + "operationId": "stream_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media stream" + }, + "206": { + "description": "Partial content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream/dash/manifest.mpd": { + "get": { + "tags": [ + "streaming" + ], + "operationId": "dash_manifest", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "DASH manifest" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream/dash/{profile}/{segment}": { + "get": { + "tags": [ + "streaming" + ], + "operationId": "dash_segment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "profile", + "in": "path", + "description": "Transcode profile name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segment", + "in": "path", + "description": "Segment filename", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "DASH segment data" + }, + "202": { + "description": "Segment not yet available" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream/hls/master.m3u8": { + "get": { + "tags": [ + "streaming" + ], + "operationId": "hls_master_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "HLS master playlist" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream/hls/{profile}/playlist.m3u8": { + "get": { + "tags": [ + "streaming" + ], + "operationId": "hls_variant_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "profile", + "in": "path", + "description": "Transcode profile name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "HLS variant playlist" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/stream/hls/{profile}/{segment}": { + "get": { + "tags": [ + "streaming" + ], + "operationId": "hls_segment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "profile", + "in": "path", + "description": "Transcode profile name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segment", + "in": "path", + "description": "Segment filename", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "HLS segment data" + }, + "202": { + "description": "Segment not yet available" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/subtitles": { + "get": { + "tags": [ + "subtitles" + ], + "operationId": "list_subtitles", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Subtitles", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SubtitleResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "subtitles" + ], + "operationId": "add_subtitle", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSubtitleRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Subtitle added", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubtitleResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/thumbnail": { + "get": { + "tags": [ + "media" + ], + "operationId": "get_thumbnail", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Thumbnail image" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/transcode": { + "post": { + "tags": [ + "transcode" + ], + "operationId": "start_transcode", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTranscodeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Transcode job submitted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{id}/trash": { + "delete": { + "tags": [ + "media" + ], + "operationId": "soft_delete_media", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media moved to trash" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{media_id}/subtitles/{subtitle_id}/content": { + "get": { + "tags": [ + "subtitles" + ], + "operationId": "get_subtitle_content", + "parameters": [ + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "subtitle_id", + "in": "path", + "description": "Subtitle ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Subtitle content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{media_id}/tags": { + "get": { + "tags": [ + "tags" + ], + "operationId": "get_media_tags", + "parameters": [ + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media tags", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "tags" + ], + "operationId": "tag_media", + "parameters": [ + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagMediaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Tag applied" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/media/{media_id}/tags/{tag_id}": { + "delete": { + "tags": [ + "tags" + ], + "operationId": "untag_media", + "parameters": [ + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "tag_id", + "in": "path", + "description": "Tag ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Tag removed" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notes/graph": { + "get": { + "tags": [ + "notes" + ], + "summary": "Get graph data for visualization.", + "description": "GET /api/v1/notes/graph?center={uuid}&depth={n}", + "operationId": "get_graph", + "parameters": [ + { + "name": "center", + "in": "query", + "description": "Center node ID", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "depth", + "in": "query", + "description": "Traversal depth (max 5, default 2)", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Graph data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GraphResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notes/resolve-links": { + "post": { + "tags": [ + "notes" + ], + "summary": "Resolve all unresolved links in the database.", + "description": "POST /api/v1/notes/resolve-links", + "operationId": "resolve_links", + "responses": { + "200": { + "description": "Links resolved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveLinksResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notes/unresolved-count": { + "get": { + "tags": [ + "notes" + ], + "summary": "Get count of unresolved links.", + "description": "GET /api/v1/notes/unresolved-count", + "operationId": "get_unresolved_count", + "responses": { + "200": { + "description": "Unresolved link count", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnresolvedLinksResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notifications/shares": { + "get": { + "tags": [ + "shares" + ], + "summary": "Get unread share notifications\nGET /api/notifications/shares", + "operationId": "get_notifications", + "responses": { + "200": { + "description": "Unread notifications", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareNotificationResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notifications/shares/read-all": { + "post": { + "tags": [ + "shares" + ], + "summary": "Mark all notifications as read\nPOST /api/notifications/shares/read-all", + "operationId": "mark_all_read", + "responses": { + "200": { + "description": "All notifications marked as read" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notifications/shares/{id}/read": { + "post": { + "tags": [ + "shares" + ], + "summary": "Mark a notification as read\nPOST /api/notifications/shares/{id}/read", + "operationId": "mark_notification_read", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Notification ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Notification marked as read" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/photos/map": { + "get": { + "tags": [ + "photos" + ], + "summary": "Get photos in a bounding box for map view", + "operationId": "get_map_photos", + "parameters": [ + { + "name": "lat1", + "in": "query", + "description": "Bounding box latitude 1", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "lon1", + "in": "query", + "description": "Bounding box longitude 1", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "lat2", + "in": "query", + "description": "Bounding box latitude 2", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "lon2", + "in": "query", + "description": "Bounding box longitude 2", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + } + ], + "responses": { + "200": { + "description": "Map markers", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MapMarker" + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/photos/timeline": { + "get": { + "tags": [ + "photos" + ], + "summary": "Get timeline of photos grouped by date", + "operationId": "get_timeline", + "parameters": [ + { + "name": "group_by", + "in": "query", + "description": "Grouping: day, month, year", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "year", + "in": "query", + "description": "Filter by year", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "month", + "in": "query", + "description": "Filter by month", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Max items (default 10000)", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Photo timeline groups", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineGroup" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists": { + "get": { + "tags": [ + "playlists" + ], + "operationId": "list_playlists", + "responses": { + "200": { + "description": "List of playlists", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "playlists" + ], + "operationId": "create_playlist", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePlaylistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Playlist created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists/{id}": { + "get": { + "tags": [ + "playlists" + ], + "operationId": "get_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Playlist details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "playlists" + ], + "operationId": "delete_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Playlist deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "playlists" + ], + "operationId": "update_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePlaylistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Playlist updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists/{id}/items": { + "get": { + "tags": [ + "playlists" + ], + "operationId": "list_items", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Playlist items", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "playlists" + ], + "operationId": "add_item", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistItemRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Item added" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists/{id}/items/reorder": { + "patch": { + "tags": [ + "playlists" + ], + "operationId": "reorder_item", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReorderPlaylistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Item reordered" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists/{id}/items/{media_id}": { + "delete": { + "tags": [ + "playlists" + ], + "operationId": "remove_item", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "media_id", + "in": "path", + "description": "Media item ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item removed" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/playlists/{id}/shuffle": { + "post": { + "tags": [ + "playlists" + ], + "operationId": "shuffle_playlist", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Playlist ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Shuffled playlist items", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins": { + "get": { + "tags": [ + "plugins" + ], + "summary": "List all installed plugins", + "operationId": "list_plugins", + "responses": { + "200": { + "description": "List of plugins", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "plugins" + ], + "summary": "Install a plugin from URL or file path", + "operationId": "install_plugin", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstallPluginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Plugin installed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PluginResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/events": { + "post": { + "tags": [ + "plugins" + ], + "summary": "Receive a plugin event emitted from the UI and dispatch it to interested\nserver-side event-handler plugins via the pipeline.", + "operationId": "emit_plugin_event", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PluginEventRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Event received" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/ui/pages": { + "get": { + "tags": [ + "plugins" + ], + "summary": "List all UI pages provided by loaded plugins", + "operationId": "list_plugin_ui_pages", + "responses": { + "200": { + "description": "Plugin UI pages", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginUiPageEntry" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/ui/theme": { + "get": { + "tags": [ + "plugins" + ], + "summary": "List merged CSS custom property overrides from all enabled plugins", + "operationId": "list_plugin_ui_theme_extensions", + "responses": { + "200": { + "description": "Plugin UI theme extensions" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/ui/widgets": { + "get": { + "tags": [ + "plugins" + ], + "summary": "List all UI widgets provided by loaded plugins", + "operationId": "list_plugin_ui_widgets", + "responses": { + "200": { + "description": "Plugin UI widgets", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginUiWidgetEntry" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/{id}": { + "get": { + "tags": [ + "plugins" + ], + "summary": "Get a specific plugin by ID", + "operationId": "get_plugin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Plugin ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Plugin details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PluginResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "plugins" + ], + "summary": "Uninstall a plugin", + "operationId": "uninstall_plugin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Plugin ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Plugin uninstalled" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/{id}/reload": { + "post": { + "tags": [ + "plugins" + ], + "summary": "Reload a plugin (for development)", + "operationId": "reload_plugin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Plugin ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Plugin reloaded" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/{id}/toggle": { + "patch": { + "tags": [ + "plugins" + ], + "summary": "Enable or disable a plugin", + "operationId": "toggle_plugin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Plugin ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TogglePluginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Plugin toggled" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/scan": { + "post": { + "tags": [ + "scan" + ], + "summary": "Trigger a scan as a background job. Returns the job ID immediately.", + "operationId": "trigger_scan", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScanRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Scan job submitted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScanJobResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/scan/status": { + "get": { + "tags": [ + "scan" + ], + "operationId": "scan_status", + "responses": { + "200": { + "description": "Scan status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScanStatusResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/scheduled-tasks": { + "get": { + "tags": [ + "scheduled_tasks" + ], + "operationId": "list_scheduled_tasks", + "responses": { + "200": { + "description": "List of scheduled tasks", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduledTaskResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/scheduled-tasks/{id}/run": { + "post": { + "tags": [ + "scheduled_tasks" + ], + "operationId": "run_scheduled_task_now", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Task ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Task triggered" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/scheduled-tasks/{id}/toggle": { + "post": { + "tags": [ + "scheduled_tasks" + ], + "operationId": "toggle_scheduled_task", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Task ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Task toggled" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/search": { + "get": { + "tags": [ + "search" + ], + "operationId": "search", + "parameters": [ + { + "name": "q", + "in": "query", + "description": "Search query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Sort order", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Search results", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "search" + ], + "operationId": "search_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchRequestBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Search results", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/searches": { + "get": { + "tags": [ + "saved_searches" + ], + "operationId": "list_saved_searches", + "responses": { + "200": { + "description": "List of saved searches", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SavedSearchResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "saved_searches" + ], + "operationId": "create_saved_search", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSavedSearchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Search saved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SavedSearchResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/searches/{id}": { + "delete": { + "tags": [ + "saved_searches" + ], + "operationId": "delete_saved_search", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Saved search ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Saved search deleted" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shared/media/{token}": { + "get": { + "tags": [ + "social" + ], + "operationId": "access_shared_media", + "parameters": [ + { + "name": "token", + "in": "path", + "description": "Share token", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "Share password", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Shared media", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, + "/api/v1/shared/{token}": { + "get": { + "tags": [ + "shares" + ], + "summary": "Access a public shared resource\nGET /api/shared/{token}", + "operationId": "access_shared", + "parameters": [ + { + "name": "token", + "in": "path", + "description": "Share token", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "Share password if required", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Shared content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SharedContentResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, + "/api/v1/shares": { + "post": { + "tags": [ + "shares" + ], + "summary": "Create a new share\nPOST /api/shares", + "operationId": "create_share", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateShareRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Share created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shares/batch/delete": { + "post": { + "tags": [ + "shares" + ], + "summary": "Batch delete shares\nPOST /api/shares/batch/delete", + "operationId": "batch_delete", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchDeleteSharesRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Shares deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shares/incoming": { + "get": { + "tags": [ + "shares" + ], + "summary": "List incoming shares (shares shared with me)\nGET /api/shares/incoming", + "operationId": "list_incoming", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Incoming shares", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shares/outgoing": { + "get": { + "tags": [ + "shares" + ], + "summary": "List outgoing shares (shares I created)\nGET /api/shares/outgoing", + "operationId": "list_outgoing", + "parameters": [ + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Outgoing shares", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shares/{id}": { + "get": { + "tags": [ + "shares" + ], + "summary": "Get share details\nGET /api/shares/{id}", + "operationId": "get_share", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Share details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "shares" + ], + "summary": "Delete (revoke) a share\nDELETE /api/shares/{id}", + "operationId": "delete_share", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Share deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "shares" + ], + "summary": "Update a share\nPATCH /api/shares/{id}", + "operationId": "update_share", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateShareRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Share updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/shares/{id}/activity": { + "get": { + "tags": [ + "shares" + ], + "summary": "Get share activity log\nGET /api/shares/{id}/activity", + "operationId": "get_activity", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "offset", + "in": "query", + "description": "Pagination offset", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Pagination limit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Share activity", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShareActivityResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/statistics": { + "get": { + "tags": [ + "statistics" + ], + "operationId": "library_statistics", + "responses": { + "200": { + "description": "Library statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryStatisticsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/subtitles/{id}": { + "delete": { + "tags": [ + "subtitles" + ], + "operationId": "delete_subtitle", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Subtitle ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Subtitle deleted" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/subtitles/{id}/offset": { + "patch": { + "tags": [ + "subtitles" + ], + "operationId": "update_offset", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Subtitle ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSubtitleOffsetRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Offset updated" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/ack": { + "post": { + "tags": [ + "sync" + ], + "summary": "Acknowledge processed changes\nPOST /api/sync/ack", + "operationId": "acknowledge_changes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcknowledgeChangesRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Changes acknowledged" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/changes": { + "get": { + "tags": [ + "sync" + ], + "summary": "Get changes since cursor\nGET /api/sync/changes", + "operationId": "get_changes", + "parameters": [ + { + "name": "cursor", + "in": "query", + "description": "Sync cursor", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "description": "Max changes (max 1000)", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Changes since cursor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/conflicts": { + "get": { + "tags": [ + "sync" + ], + "summary": "List unresolved conflicts\nGET /api/sync/conflicts", + "operationId": "list_conflicts", + "responses": { + "200": { + "description": "Unresolved conflicts", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConflictResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/conflicts/{id}/resolve": { + "post": { + "tags": [ + "sync" + ], + "summary": "Resolve a sync conflict\nPOST /api/sync/conflicts/{id}/resolve", + "operationId": "resolve_conflict", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Conflict ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveConflictRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Conflict resolved" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/devices": { + "get": { + "tags": [ + "sync" + ], + "summary": "List user's sync devices\nGET /api/sync/devices", + "operationId": "list_devices", + "responses": { + "200": { + "description": "List of devices", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "sync" + ], + "summary": "Register a new sync device\nPOST /api/sync/devices", + "operationId": "register_device", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterDeviceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Device registered", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceRegistrationResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/devices/{id}": { + "get": { + "tags": [ + "sync" + ], + "summary": "Get device details\nGET /api/sync/devices/{id}", + "operationId": "get_device", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Device ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Device details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "put": { + "tags": [ + "sync" + ], + "summary": "Update a device\nPUT /api/sync/devices/{id}", + "operationId": "update_device", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Device ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDeviceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Device updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "sync" + ], + "summary": "Delete a device\nDELETE /api/sync/devices/{id}", + "operationId": "delete_device", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Device ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Device deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/devices/{id}/token": { + "post": { + "tags": [ + "sync" + ], + "summary": "Regenerate device token\nPOST /api/sync/devices/{id}/token", + "operationId": "regenerate_token", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Device ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Token regenerated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceRegistrationResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/download/{path}": { + "get": { + "tags": [ + "sync" + ], + "summary": "Download a file for sync (supports Range header)\nGET /api/sync/download/{*path}", + "operationId": "download_file", + "parameters": [ + { + "name": "path", + "in": "path", + "description": "File path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "File content" + }, + "206": { + "description": "Partial content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/report": { + "post": { + "tags": [ + "sync" + ], + "summary": "Report local changes from client\nPOST /api/sync/report", + "operationId": "report_changes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportChangesRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Changes processed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportChangesResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/upload": { + "post": { + "tags": [ + "sync" + ], + "summary": "Create an upload session for chunked upload\nPOST /api/sync/upload", + "operationId": "create_upload", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadSessionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Upload session created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadSessionResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/upload/{id}": { + "get": { + "tags": [ + "sync" + ], + "summary": "Get upload session status\nGET /api/sync/upload/{id}", + "operationId": "get_upload_status", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Upload session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Upload session status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadSessionResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "sync" + ], + "summary": "Cancel an upload session\nDELETE /api/sync/upload/{id}", + "operationId": "cancel_upload", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Upload session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Upload cancelled" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/upload/{id}/chunks/{index}": { + "put": { + "tags": [ + "sync" + ], + "summary": "Upload a chunk\nPUT /api/sync/upload/{id}/chunks/{index}", + "operationId": "upload_chunk", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Upload session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "index", + "in": "path", + "description": "Chunk index", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + ], + "requestBody": { + "description": "Chunk binary data", + "content": { + "application/octet-stream": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Chunk received", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChunkUploadedResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/sync/upload/{id}/complete": { + "post": { + "tags": [ + "sync" + ], + "summary": "Complete an upload session\nPOST /api/sync/upload/{id}/complete", + "operationId": "complete_upload", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Upload session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Upload completed" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/tags": { + "get": { + "tags": [ + "tags" + ], + "operationId": "list_tags", + "responses": { + "200": { + "description": "List of tags", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "tags" + ], + "operationId": "create_tag", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Tag created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/tags/{id}": { + "get": { + "tags": [ + "tags" + ], + "operationId": "get_tag", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Tag ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Tag", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "tags" + ], + "operationId": "delete_tag", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Tag ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Tag deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/transcode": { + "get": { + "tags": [ + "transcode" + ], + "operationId": "list_sessions", + "responses": { + "200": { + "description": "List of transcode sessions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TranscodeSessionResponse" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/transcode/{id}": { + "get": { + "tags": [ + "transcode" + ], + "operationId": "get_session", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Transcode session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Transcode session details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscodeSessionResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "transcode" + ], + "operationId": "cancel_session", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Transcode session ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Transcode session cancelled" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/upload": { + "post": { + "tags": [ + "upload" + ], + "summary": "Upload a file to managed storage\nPOST /api/upload", + "operationId": "upload_file", + "responses": { + "200": { + "description": "File uploaded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/webhooks": { + "get": { + "tags": [ + "webhooks" + ], + "operationId": "list_webhooks", + "responses": { + "200": { + "description": "List of configured webhooks", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookInfo" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/webhooks/test": { + "post": { + "tags": [ + "webhooks" + ], + "operationId": "test_webhook", + "responses": { + "200": { + "description": "Test webhook sent" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "AccessSharedRequest": { + "type": "object", + "properties": { + "password": { + "type": [ + "string", + "null" + ] + } + } + }, + "AcknowledgeChangesRequest": { + "type": "object", + "required": [ + "cursor" + ], + "properties": { + "cursor": { + "type": "integer", + "format": "int64" + } + } + }, + "AddMemberRequest": { + "type": "object", + "required": [ + "media_id" + ], + "properties": { + "media_id": { + "type": "string", + "format": "uuid" + }, + "position": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "AddSubtitleRequest": { + "type": "object", + "required": [ + "format" + ], + "properties": { + "file_path": { + "type": [ + "string", + "null" + ] + }, + "format": { + "type": "string" + }, + "is_embedded": { + "type": [ + "boolean", + "null" + ] + }, + "language": { + "type": [ + "string", + "null" + ] + }, + "offset_ms": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "track_index": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + } + } + }, + "AuditEntryResponse": { + "type": "object", + "required": [ + "id", + "action", + "timestamp" + ], + "properties": { + "action": { + "type": "string" + }, + "details": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "media_id": { + "type": [ + "string", + "null" + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "AuthorResponse": { + "type": "object", + "description": "Author response DTO", + "required": [ + "name", + "role", + "position" + ], + "properties": { + "file_as": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "role": { + "type": "string" + } + } + }, + "AuthorSummary": { + "type": "object", + "description": "Author summary DTO", + "required": [ + "name", + "book_count" + ], + "properties": { + "book_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "name": { + "type": "string" + } + } + }, + "BacklinkItem": { + "type": "object", + "description": "Individual backlink item", + "required": [ + "link_id", + "source_id", + "source_path", + "link_type" + ], + "properties": { + "context": { + "type": [ + "string", + "null" + ] + }, + "line_number": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "link_id": { + "type": "string", + "format": "uuid" + }, + "link_text": { + "type": [ + "string", + "null" + ] + }, + "link_type": { + "type": "string" + }, + "source_id": { + "type": "string", + "format": "uuid" + }, + "source_path": { + "type": "string" + }, + "source_title": { + "type": [ + "string", + "null" + ] + } + } + }, + "BacklinksResponse": { + "type": "object", + "description": "Response for backlinks query", + "required": [ + "backlinks", + "count" + ], + "properties": { + "backlinks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BacklinkItem" + } + }, + "count": { + "type": "integer", + "minimum": 0 + } + } + }, + "BatchCollectionRequest": { + "type": "object", + "required": [ + "media_ids", + "collection_id" + ], + "properties": { + "collection_id": { + "type": "string", + "format": "uuid" + }, + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchDeleteRequest": { + "type": "object", + "required": [ + "media_ids" + ], + "properties": { + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchDeleteSharesRequest": { + "type": "object", + "required": [ + "share_ids" + ], + "properties": { + "share_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchImportItemResult": { + "type": "object", + "required": [ + "path", + "was_duplicate" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "media_id": { + "type": [ + "string", + "null" + ] + }, + "path": { + "type": "string" + }, + "was_duplicate": { + "type": "boolean" + } + } + }, + "BatchImportRequest": { + "type": "object", + "required": [ + "paths" + ], + "properties": { + "collection_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "new_tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "tag_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchImportResponse": { + "type": "object", + "required": [ + "results", + "total", + "imported", + "duplicates", + "errors" + ], + "properties": { + "duplicates": { + "type": "integer", + "minimum": 0 + }, + "errors": { + "type": "integer", + "minimum": 0 + }, + "imported": { + "type": "integer", + "minimum": 0 + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BatchImportItemResult" + } + }, + "total": { + "type": "integer", + "minimum": 0 + } + } + }, + "BatchMoveRequest": { + "type": "object", + "required": [ + "media_ids", + "destination" + ], + "properties": { + "destination": { + "type": "string" + }, + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchOperationResponse": { + "type": "object", + "required": [ + "processed", + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + }, + "processed": { + "type": "integer", + "minimum": 0 + } + } + }, + "BatchTagRequest": { + "type": "object", + "required": [ + "media_ids", + "tag_ids" + ], + "properties": { + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "tag_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "BatchUpdateRequest": { + "type": "object", + "required": [ + "media_ids" + ], + "properties": { + "album": { + "type": [ + "string", + "null" + ] + }, + "artist": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "genre": { + "type": [ + "string", + "null" + ] + }, + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "year": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "BookMetadataResponse": { + "type": "object", + "description": "Book metadata response DTO", + "required": [ + "media_id", + "authors", + "identifiers" + ], + "properties": { + "authors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthorResponse" + } + }, + "format": { + "type": [ + "string", + "null" + ] + }, + "identifiers": { + "type": "object" + }, + "isbn": { + "type": [ + "string", + "null" + ] + }, + "isbn13": { + "type": [ + "string", + "null" + ] + }, + "language": { + "type": [ + "string", + "null" + ] + }, + "media_id": { + "type": "string", + "format": "uuid" + }, + "page_count": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "publication_date": { + "type": [ + "string", + "null" + ] + }, + "publisher": { + "type": [ + "string", + "null" + ] + }, + "series_index": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "series_name": { + "type": [ + "string", + "null" + ] + } + } + }, + "CacheHealth": { + "type": "object", + "required": [ + "hit_rate", + "total_entries", + "responses_size", + "queries_size", + "media_size" + ], + "properties": { + "hit_rate": { + "type": "number", + "format": "double" + }, + "media_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "queries_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "responses_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_entries": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "ChangesResponse": { + "type": "object", + "required": [ + "changes", + "cursor", + "has_more" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncChangeResponse" + } + }, + "cursor": { + "type": "integer", + "format": "int64" + }, + "has_more": { + "type": "boolean" + } + } + }, + "ChunkUploadedResponse": { + "type": "object", + "required": [ + "chunk_index", + "received" + ], + "properties": { + "chunk_index": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "received": { + "type": "boolean" + } + } + }, + "ClientChangeReport": { + "type": "object", + "required": [ + "path", + "change_type" + ], + "properties": { + "change_type": { + "type": "string" + }, + "content_hash": { + "type": [ + "string", + "null" + ] + }, + "file_size": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "local_mtime": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "path": { + "type": "string" + } + } + }, + "CollectionResponse": { + "type": "object", + "required": [ + "id", + "name", + "kind", + "created_at", + "updated_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "filter_query": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CommentResponse": { + "type": "object", + "required": [ + "id", + "user_id", + "media_id", + "text", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "parent_comment_id": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, + "ConfigResponse": { + "type": "object", + "required": [ + "backend", + "roots", + "scanning", + "server", + "ui", + "config_writable" + ], + "properties": { + "backend": { + "type": "string" + }, + "config_path": { + "type": [ + "string", + "null" + ] + }, + "config_writable": { + "type": "boolean" + }, + "database_path": { + "type": [ + "string", + "null" + ] + }, + "roots": { + "type": "array", + "items": { + "type": "string" + } + }, + "scanning": { + "$ref": "#/components/schemas/ScanningConfigResponse" + }, + "server": { + "$ref": "#/components/schemas/ServerConfigResponse" + }, + "ui": { + "$ref": "#/components/schemas/UiConfigResponse" + } + } + }, + "ConflictResponse": { + "type": "object", + "required": [ + "id", + "path", + "local_hash", + "server_hash", + "detected_at" + ], + "properties": { + "detected_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "local_hash": { + "type": "string" + }, + "path": { + "type": "string" + }, + "server_hash": { + "type": "string" + } + } + }, + "CreateCollectionRequest": { + "type": "object", + "required": [ + "name", + "kind" + ], + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "filter_query": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "CreateCommentRequest": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "parent_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "text": { + "type": "string" + } + } + }, + "CreatePlaylistRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "filter_query": { + "type": [ + "string", + "null" + ] + }, + "is_public": { + "type": [ + "boolean", + "null" + ] + }, + "is_smart": { + "type": [ + "boolean", + "null" + ] + }, + "name": { + "type": "string" + } + } + }, + "CreateRatingRequest": { + "type": "object", + "required": [ + "stars" + ], + "properties": { + "review_text": { + "type": [ + "string", + "null" + ] + }, + "stars": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "CreateSavedSearchRequest": { + "type": "object", + "required": [ + "name", + "query" + ], + "properties": { + "name": { + "type": "string" + }, + "query": { + "type": "string" + }, + "sort_order": { + "type": [ + "string", + "null" + ] + } + } + }, + "CreateShareLinkRequest": { + "type": "object", + "required": [ + "media_id" + ], + "properties": { + "expires_in_hours": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "media_id": { + "type": "string", + "format": "uuid" + }, + "password": { + "type": [ + "string", + "null" + ] + } + } + }, + "CreateShareRequest": { + "type": "object", + "required": [ + "target_type", + "target_id", + "recipient_type" + ], + "properties": { + "expires_in_hours": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "inherit_to_children": { + "type": [ + "boolean", + "null" + ] + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "password": { + "type": [ + "string", + "null" + ] + }, + "permissions": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/SharePermissionsRequest" + } + ] + }, + "recipient_group_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "recipient_type": { + "type": "string" + }, + "recipient_user_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + } + } + }, + "CreateTagRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "parent_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + } + } + }, + "CreateTranscodeRequest": { + "type": "object", + "required": [ + "profile" + ], + "properties": { + "profile": { + "type": "string" + } + } + }, + "CreateUploadSessionRequest": { + "type": "object", + "required": [ + "target_path", + "expected_hash", + "expected_size" + ], + "properties": { + "chunk_size": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "expected_hash": { + "type": "string" + }, + "expected_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "target_path": { + "type": "string" + } + } + }, + "CustomFieldResponse": { + "type": "object", + "required": [ + "field_type", + "value" + ], + "properties": { + "field_type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "DatabaseHealth": { + "type": "object", + "required": [ + "status", + "latency_ms" + ], + "properties": { + "latency_ms": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "media_count": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "status": { + "type": "string" + } + } + }, + "DatabaseStatsResponse": { + "type": "object", + "required": [ + "media_count", + "tag_count", + "collection_count", + "audit_count", + "database_size_bytes", + "backend_name" + ], + "properties": { + "audit_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "backend_name": { + "type": "string" + }, + "collection_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "database_size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "media_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "tag_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "DetailedHealthResponse": { + "type": "object", + "description": "Detailed health check for monitoring dashboards", + "required": [ + "status", + "version", + "uptime_seconds", + "database", + "filesystem", + "cache", + "jobs" + ], + "properties": { + "cache": { + "$ref": "#/components/schemas/CacheHealth" + }, + "database": { + "$ref": "#/components/schemas/DatabaseHealth" + }, + "filesystem": { + "$ref": "#/components/schemas/FilesystemHealth" + }, + "jobs": { + "$ref": "#/components/schemas/JobsHealth" + }, + "status": { + "type": "string" + }, + "uptime_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "version": { + "type": "string" + } + } + }, + "DeviceRegistrationResponse": { + "type": "object", + "required": [ + "device", + "device_token" + ], + "properties": { + "device": { + "$ref": "#/components/schemas/DeviceResponse" + }, + "device_token": { + "type": "string" + } + } + }, + "DeviceResponse": { + "type": "object", + "required": [ + "id", + "name", + "device_type", + "client_version", + "last_seen_at", + "enabled", + "created_at" + ], + "properties": { + "client_version": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "device_type": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "last_seen_at": { + "type": "string", + "format": "date-time" + }, + "last_sync_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "name": { + "type": "string" + }, + "os_info": { + "type": [ + "string", + "null" + ] + }, + "sync_cursor": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "DirectoryImportRequest": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "collection_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "new_tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "tag_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "DirectoryPreviewFile": { + "type": "object", + "required": [ + "path", + "file_name", + "media_type", + "file_size" + ], + "properties": { + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "media_type": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "DirectoryPreviewResponse": { + "type": "object", + "required": [ + "files", + "total_count", + "total_size" + ], + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectoryPreviewFile" + } + }, + "total_count": { + "type": "integer", + "minimum": 0 + }, + "total_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "DuplicateGroupResponse": { + "type": "object", + "required": [ + "content_hash", + "items" + ], + "properties": { + "content_hash": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "EmptyTrashResponse": { + "type": "object", + "required": [ + "deleted_count" + ], + "properties": { + "deleted_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "ExportRequest": { + "type": "object", + "required": [ + "format", + "destination" + ], + "properties": { + "destination": { + "type": "string" + }, + "format": { + "type": "string" + } + } + }, + "ExternalMetadataResponse": { + "type": "object", + "required": [ + "id", + "media_id", + "source", + "metadata", + "confidence", + "last_updated" + ], + "properties": { + "confidence": { + "type": "number", + "format": "double" + }, + "external_id": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "last_updated": { + "type": "string", + "format": "date-time" + }, + "media_id": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "source": { + "type": "string" + } + } + }, + "FavoriteRequest": { + "type": "object", + "required": [ + "media_id" + ], + "properties": { + "media_id": { + "type": "string", + "format": "uuid" + } + } + }, + "FilesystemHealth": { + "type": "object", + "required": [ + "status", + "roots_configured", + "roots_accessible" + ], + "properties": { + "roots_accessible": { + "type": "integer", + "minimum": 0 + }, + "roots_configured": { + "type": "integer", + "minimum": 0 + }, + "status": { + "type": "string" + } + } + }, + "GenerateThumbnailsRequest": { + "type": "object", + "properties": { + "only_missing": { + "type": "boolean", + "description": "When true, only generate thumbnails for items that don't have one yet.\nWhen false (default), regenerate all thumbnails." + } + } + }, + "GetChangesParams": { + "type": "object", + "properties": { + "cursor": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + } + } + }, + "GrantLibraryAccessRequest": { + "type": "object", + "required": [ + "root_path", + "permission" + ], + "properties": { + "permission": { + "type": "string" + }, + "root_path": { + "type": "string" + } + } + }, + "GraphEdgeResponse": { + "type": "object", + "description": "Graph edge for visualization", + "required": [ + "source", + "target", + "link_type" + ], + "properties": { + "link_type": { + "type": "string" + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + } + } + }, + "GraphNodeResponse": { + "type": "object", + "description": "Graph node for visualization", + "required": [ + "id", + "label", + "media_type", + "link_count", + "backlink_count" + ], + "properties": { + "backlink_count": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "id": { + "type": "string" + }, + "label": { + "type": "string" + }, + "link_count": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "media_type": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + } + } + }, + "GraphResponse": { + "type": "object", + "description": "Response for graph visualization", + "required": [ + "nodes", + "edges", + "node_count", + "edge_count" + ], + "properties": { + "edge_count": { + "type": "integer", + "minimum": 0 + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphEdgeResponse" + } + }, + "node_count": { + "type": "integer", + "minimum": 0 + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphNodeResponse" + } + } + } + }, + "HealthResponse": { + "type": "object", + "description": "Basic health check response", + "required": [ + "status", + "version" + ], + "properties": { + "cache": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/CacheHealth" + } + ] + }, + "database": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/DatabaseHealth" + } + ] + }, + "filesystem": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/FilesystemHealth" + } + ] + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "ImportRequest": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + } + } + }, + "ImportResponse": { + "type": "object", + "required": [ + "media_id", + "was_duplicate" + ], + "properties": { + "media_id": { + "type": "string" + }, + "was_duplicate": { + "type": "boolean" + } + } + }, + "ImportWithOptionsRequest": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "collection_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "new_tags": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "path": { + "type": "string" + }, + "tag_ids": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "InstallPluginRequest": { + "type": "object", + "required": [ + "source" + ], + "properties": { + "source": { + "type": "string" + } + } + }, + "JobsHealth": { + "type": "object", + "required": [ + "pending", + "running" + ], + "properties": { + "pending": { + "type": "integer", + "minimum": 0 + }, + "running": { + "type": "integer", + "minimum": 0 + } + } + }, + "LibraryStatisticsResponse": { + "type": "object", + "required": [ + "total_media", + "total_size_bytes", + "avg_file_size_bytes", + "media_by_type", + "storage_by_type", + "top_tags", + "top_collections", + "total_tags", + "total_collections", + "total_duplicates" + ], + "properties": { + "avg_file_size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "media_by_type": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypeCountResponse" + } + }, + "newest_item": { + "type": [ + "string", + "null" + ] + }, + "oldest_item": { + "type": [ + "string", + "null" + ] + }, + "storage_by_type": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypeCountResponse" + } + }, + "top_collections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypeCountResponse" + } + }, + "top_tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypeCountResponse" + } + }, + "total_collections": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_duplicates": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_media": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_tags": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "LoginRequest": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "LoginResponse": { + "type": "object", + "required": [ + "token", + "username", + "role" + ], + "properties": { + "role": { + "type": "string" + }, + "token": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "ManagedStorageStatsResponse": { + "type": "object", + "required": [ + "total_blobs", + "total_size_bytes", + "orphaned_blobs", + "deduplication_ratio" + ], + "properties": { + "deduplication_ratio": { + "type": "number", + "format": "double" + }, + "orphaned_blobs": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_blobs": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "total_size_bytes": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "MapMarker": { + "type": "object", + "description": "Map marker response", + "required": [ + "id", + "latitude", + "longitude" + ], + "properties": { + "date_taken": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "id": { + "type": "string" + }, + "latitude": { + "type": "number", + "format": "double" + }, + "longitude": { + "type": "number", + "format": "double" + }, + "thumbnail_url": { + "type": [ + "string", + "null" + ] + } + } + }, + "MediaCountResponse": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "MediaResponse": { + "type": "object", + "required": [ + "id", + "path", + "file_name", + "media_type", + "content_hash", + "file_size", + "has_thumbnail", + "custom_fields", + "created_at", + "updated_at" + ], + "properties": { + "album": { + "type": [ + "string", + "null" + ] + }, + "artist": { + "type": [ + "string", + "null" + ] + }, + "camera_make": { + "type": [ + "string", + "null" + ] + }, + "camera_model": { + "type": [ + "string", + "null" + ] + }, + "content_hash": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "custom_fields": { + "type": "object" + }, + "date_taken": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "duration_secs": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "genre": { + "type": [ + "string", + "null" + ] + }, + "has_thumbnail": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "latitude": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "links_extracted_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "longitude": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "media_type": { + "type": "string" + }, + "path": { + "type": "string" + }, + "rating": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "year": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "MostViewedResponse": { + "type": "object", + "required": [ + "media", + "view_count" + ], + "properties": { + "media": { + "$ref": "#/components/schemas/MediaResponse" + }, + "view_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "MoveMediaRequest": { + "type": "object", + "required": [ + "destination" + ], + "properties": { + "destination": { + "type": "string" + } + } + }, + "OpenRequest": { + "type": "object", + "required": [ + "media_id" + ], + "properties": { + "media_id": { + "type": "string", + "format": "uuid" + } + } + }, + "OrphanResolveRequest": { + "type": "object", + "required": [ + "action", + "ids" + ], + "properties": { + "action": { + "type": "string" + }, + "ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "OutgoingLinkItem": { + "type": "object", + "description": "Individual outgoing link item", + "required": [ + "id", + "target_path", + "link_type", + "is_resolved" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "is_resolved": { + "type": "boolean" + }, + "line_number": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "link_text": { + "type": [ + "string", + "null" + ] + }, + "link_type": { + "type": "string" + }, + "target_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "target_path": { + "type": "string" + } + } + }, + "OutgoingLinksResponse": { + "type": "object", + "description": "Response for outgoing links query", + "required": [ + "links", + "count" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OutgoingLinkItem" + } + } + } + }, + "PaginationParams": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "offset": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "sort": { + "type": [ + "string", + "null" + ] + } + } + }, + "PlaylistItemRequest": { + "type": "object", + "required": [ + "media_id" + ], + "properties": { + "media_id": { + "type": "string", + "format": "uuid" + }, + "position": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "PlaylistResponse": { + "type": "object", + "required": [ + "id", + "owner_id", + "name", + "is_public", + "is_smart", + "created_at", + "updated_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "filter_query": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "is_public": { + "type": "boolean" + }, + "is_smart": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "PluginEventRequest": { + "type": "object", + "description": "Request body for emitting a plugin event", + "required": [ + "event" + ], + "properties": { + "event": { + "type": "string" + }, + "payload": { + "type": "object" + } + } + }, + "PluginResponse": { + "type": "object", + "required": [ + "id", + "name", + "version", + "author", + "description", + "api_version", + "enabled" + ], + "properties": { + "api_version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "PluginUiPageEntry": { + "type": "object", + "description": "A single plugin UI page entry in the list response", + "required": [ + "plugin_id", + "page", + "allowed_endpoints" + ], + "properties": { + "allowed_endpoints": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Endpoint paths this plugin is allowed to fetch (empty means no\nrestriction)" + }, + "page": { + "type": "object", + "description": "Full page definition" + }, + "plugin_id": { + "type": "string", + "description": "Plugin ID that provides this page" + } + } + }, + "PluginUiWidgetEntry": { + "type": "object", + "description": "A single plugin UI widget entry in the list response", + "required": [ + "plugin_id", + "widget" + ], + "properties": { + "plugin_id": { + "type": "string", + "description": "Plugin ID that provides this widget" + }, + "widget": { + "type": "object", + "description": "Full widget definition" + } + } + }, + "RatingResponse": { + "type": "object", + "required": [ + "id", + "user_id", + "media_id", + "stars", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "review_text": { + "type": [ + "string", + "null" + ] + }, + "stars": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "user_id": { + "type": "string" + } + } + }, + "ReadingProgressResponse": { + "type": "object", + "description": "Reading progress response DTO", + "required": [ + "media_id", + "user_id", + "current_page", + "progress_percent", + "last_read_at" + ], + "properties": { + "current_page": { + "type": "integer", + "format": "int32" + }, + "last_read_at": { + "type": "string" + }, + "media_id": { + "type": "string", + "format": "uuid" + }, + "progress_percent": { + "type": "number", + "format": "double" + }, + "total_pages": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "user_id": { + "type": "string", + "format": "uuid" + } + } + }, + "RecordUsageEventRequest": { + "type": "object", + "required": [ + "event_type" + ], + "properties": { + "context": { + "type": [ + "object", + "null" + ] + }, + "duration_secs": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "event_type": { + "type": "string" + }, + "media_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + } + } + }, + "RegisterDeviceRequest": { + "type": "object", + "required": [ + "name", + "device_type", + "client_version" + ], + "properties": { + "client_version": { + "type": "string" + }, + "device_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "os_info": { + "type": [ + "string", + "null" + ] + } + } + }, + "ReindexResponse": { + "type": "object", + "description": "Response for reindex operation", + "required": [ + "message", + "links_extracted" + ], + "properties": { + "links_extracted": { + "type": "integer", + "minimum": 0 + }, + "message": { + "type": "string" + } + } + }, + "RenameMediaRequest": { + "type": "object", + "required": [ + "new_name" + ], + "properties": { + "new_name": { + "type": "string" + } + } + }, + "ReorderPlaylistRequest": { + "type": "object", + "required": [ + "media_id", + "new_position" + ], + "properties": { + "media_id": { + "type": "string", + "format": "uuid" + }, + "new_position": { + "type": "integer", + "format": "int32" + } + } + }, + "ReportChangesRequest": { + "type": "object", + "required": [ + "changes" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClientChangeReport" + } + } + } + }, + "ReportChangesResponse": { + "type": "object", + "required": [ + "accepted", + "conflicts", + "upload_required" + ], + "properties": { + "accepted": { + "type": "array", + "items": { + "type": "string" + } + }, + "conflicts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConflictResponse" + } + }, + "upload_required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ResolveConflictRequest": { + "type": "object", + "required": [ + "resolution" + ], + "properties": { + "resolution": { + "type": "string" + } + } + }, + "ResolveLinksResponse": { + "type": "object", + "description": "Response for link resolution", + "required": [ + "resolved_count" + ], + "properties": { + "resolved_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "RevokeLibraryAccessRequest": { + "type": "object", + "required": [ + "root_path" + ], + "properties": { + "root_path": { + "type": "string" + } + } + }, + "RootDirRequest": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string" + } + } + }, + "SavedSearchResponse": { + "type": "object", + "required": [ + "id", + "name", + "query", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "query": { + "type": "string" + }, + "sort_order": { + "type": [ + "string", + "null" + ] + } + } + }, + "ScanJobResponse": { + "type": "object", + "required": [ + "job_id" + ], + "properties": { + "job_id": { + "type": "string" + } + } + }, + "ScanRequest": { + "type": "object", + "properties": { + "path": { + "type": [ + "string", + "null" + ] + } + } + }, + "ScanResponse": { + "type": "object", + "required": [ + "files_found", + "files_processed", + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + }, + "files_found": { + "type": "integer", + "minimum": 0 + }, + "files_processed": { + "type": "integer", + "minimum": 0 + } + } + }, + "ScanStatusResponse": { + "type": "object", + "required": [ + "scanning", + "files_found", + "files_processed", + "error_count", + "errors" + ], + "properties": { + "error_count": { + "type": "integer", + "minimum": 0 + }, + "errors": { + "type": "array", + "items": { + "type": "string" + } + }, + "files_found": { + "type": "integer", + "minimum": 0 + }, + "files_processed": { + "type": "integer", + "minimum": 0 + }, + "scanning": { + "type": "boolean" + } + } + }, + "ScanningConfigResponse": { + "type": "object", + "required": [ + "watch", + "poll_interval_secs", + "ignore_patterns" + ], + "properties": { + "ignore_patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "poll_interval_secs": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "watch": { + "type": "boolean" + } + } + }, + "ScheduledTaskResponse": { + "type": "object", + "required": [ + "id", + "name", + "schedule", + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "last_run": { + "type": [ + "string", + "null" + ] + }, + "last_status": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "next_run": { + "type": [ + "string", + "null" + ] + }, + "schedule": { + "type": "string" + } + } + }, + "SearchParams": { + "type": "object", + "required": [ + "q" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "offset": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "q": { + "type": "string" + }, + "sort": { + "type": [ + "string", + "null" + ] + } + } + }, + "SearchRequestBody": { + "type": "object", + "required": [ + "q" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "offset": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "q": { + "type": "string" + }, + "sort": { + "type": [ + "string", + "null" + ] + } + } + }, + "SearchResponse": { + "type": "object", + "required": [ + "items", + "total_count" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + }, + "total_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "SeriesSummary": { + "type": "object", + "description": "Series summary DTO", + "required": [ + "name", + "book_count" + ], + "properties": { + "book_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "name": { + "type": "string" + } + } + }, + "ServerConfigResponse": { + "type": "object", + "required": [ + "host", + "port" + ], + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "SessionInfo": { + "type": "object", + "required": [ + "username", + "role", + "created_at", + "last_accessed", + "expires_at" + ], + "properties": { + "created_at": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "last_accessed": { + "type": "string" + }, + "role": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "SessionListResponse": { + "type": "object", + "description": "List all active sessions (admin only)", + "required": [ + "sessions" + ], + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInfo" + } + } + } + }, + "SetCustomFieldRequest": { + "type": "object", + "required": [ + "name", + "field_type", + "value" + ], + "properties": { + "field_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "ShareActivityResponse": { + "type": "object", + "required": [ + "id", + "share_id", + "action", + "timestamp" + ], + "properties": { + "action": { + "type": "string" + }, + "actor_id": { + "type": [ + "string", + "null" + ] + }, + "actor_ip": { + "type": [ + "string", + "null" + ] + }, + "details": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "share_id": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "ShareLinkResponse": { + "type": "object", + "required": [ + "id", + "media_id", + "token", + "view_count", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "token": { + "type": "string" + }, + "view_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "ShareNotificationResponse": { + "type": "object", + "required": [ + "id", + "share_id", + "notification_type", + "is_read", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "is_read": { + "type": "boolean" + }, + "notification_type": { + "type": "string" + }, + "share_id": { + "type": "string" + } + } + }, + "SharePermissionsRequest": { + "type": "object", + "properties": { + "can_add": { + "type": [ + "boolean", + "null" + ] + }, + "can_delete": { + "type": [ + "boolean", + "null" + ] + }, + "can_download": { + "type": [ + "boolean", + "null" + ] + }, + "can_edit": { + "type": [ + "boolean", + "null" + ] + }, + "can_reshare": { + "type": [ + "boolean", + "null" + ] + }, + "can_view": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "SharePermissionsResponse": { + "type": "object", + "required": [ + "can_view", + "can_download", + "can_edit", + "can_delete", + "can_reshare", + "can_add" + ], + "properties": { + "can_add": { + "type": "boolean" + }, + "can_delete": { + "type": "boolean" + }, + "can_download": { + "type": "boolean" + }, + "can_edit": { + "type": "boolean" + }, + "can_reshare": { + "type": "boolean" + }, + "can_view": { + "type": "boolean" + } + } + }, + "ShareResponse": { + "type": "object", + "required": [ + "id", + "target_type", + "target_id", + "owner_id", + "recipient_type", + "permissions", + "access_count", + "inherit_to_children", + "created_at", + "updated_at" + ], + "properties": { + "access_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "id": { + "type": "string" + }, + "inherit_to_children": { + "type": "boolean" + }, + "last_accessed": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "owner_id": { + "type": "string" + }, + "permissions": { + "$ref": "#/components/schemas/SharePermissionsResponse" + }, + "public_token": { + "type": [ + "string", + "null" + ] + }, + "recipient_group_id": { + "type": [ + "string", + "null" + ] + }, + "recipient_type": { + "type": "string" + }, + "recipient_user_id": { + "type": [ + "string", + "null" + ] + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SharedContentResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaResponse" + }, + { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + } + ], + "description": "Response for accessing shared content.\nSingle-media shares return the media object directly (backwards compatible).\nCollection/Tag/SavedSearch shares return a list of items." + }, + "SubtitleResponse": { + "type": "object", + "required": [ + "id", + "media_id", + "format", + "is_embedded", + "offset_ms", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "format": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_embedded": { + "type": "boolean" + }, + "language": { + "type": [ + "string", + "null" + ] + }, + "media_id": { + "type": "string" + }, + "offset_ms": { + "type": "integer", + "format": "int64" + }, + "track_index": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + } + } + }, + "SyncChangeResponse": { + "type": "object", + "required": [ + "id", + "sequence", + "change_type", + "path", + "timestamp" + ], + "properties": { + "change_type": { + "type": "string" + }, + "content_hash": { + "type": [ + "string", + "null" + ] + }, + "file_size": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "id": { + "type": "string" + }, + "media_id": { + "type": [ + "string", + "null" + ] + }, + "path": { + "type": "string" + }, + "sequence": { + "type": "integer", + "format": "int64" + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "TagMediaRequest": { + "type": "object", + "required": [ + "tag_id" + ], + "properties": { + "tag_id": { + "type": "string", + "format": "uuid" + } + } + }, + "TagResponse": { + "type": "object", + "required": [ + "id", + "name", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": [ + "string", + "null" + ] + } + } + }, + "TimelineGroup": { + "type": "object", + "description": "Timeline group response", + "required": [ + "date", + "count", + "items" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "cover_id": { + "type": [ + "string", + "null" + ] + }, + "date": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + } + } + }, + "TogglePluginRequest": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "TranscodeSessionResponse": { + "type": "object", + "required": [ + "id", + "media_id", + "profile", + "status", + "progress", + "created_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "id": { + "type": "string" + }, + "media_id": { + "type": "string" + }, + "profile": { + "type": "string" + }, + "progress": { + "type": "number", + "format": "float" + }, + "status": { + "type": "string" + } + } + }, + "TrashInfoResponse": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "TrashResponse": { + "type": "object", + "required": [ + "items", + "total_count" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaResponse" + } + }, + "total_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "TypeCountResponse": { + "type": "object", + "required": [ + "name", + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "name": { + "type": "string" + } + } + }, + "UiConfigResponse": { + "type": "object", + "required": [ + "theme", + "default_view", + "default_page_size", + "default_view_mode", + "auto_play_media", + "show_thumbnails", + "sidebar_collapsed" + ], + "properties": { + "auto_play_media": { + "type": "boolean" + }, + "default_page_size": { + "type": "integer", + "minimum": 0 + }, + "default_view": { + "type": "string" + }, + "default_view_mode": { + "type": "string" + }, + "show_thumbnails": { + "type": "boolean" + }, + "sidebar_collapsed": { + "type": "boolean" + }, + "theme": { + "type": "string" + } + } + }, + "UnresolvedLinksResponse": { + "type": "object", + "description": "Response for unresolved links count", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "UpdateDeviceRequest": { + "type": "object", + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + } + } + }, + "UpdateMediaFullRequest": { + "type": "object", + "properties": { + "album": { + "type": [ + "string", + "null" + ] + }, + "artist": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "genre": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "year": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "UpdateMediaRequest": { + "type": "object", + "properties": { + "album": { + "type": [ + "string", + "null" + ] + }, + "artist": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "genre": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "year": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "UpdatePlaylistRequest": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "is_public": { + "type": [ + "boolean", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + } + } + }, + "UpdateProgressRequest": { + "type": "object", + "description": "Update reading progress request", + "required": [ + "current_page" + ], + "properties": { + "current_page": { + "type": "integer", + "format": "int32" + } + } + }, + "UpdateScanningRequest": { + "type": "object", + "properties": { + "ignore_patterns": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "poll_interval_secs": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "watch": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "UpdateShareRequest": { + "type": "object", + "properties": { + "expires_at": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "inherit_to_children": { + "type": [ + "boolean", + "null" + ] + }, + "note": { + "type": [ + "string", + "null" + ] + }, + "permissions": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/SharePermissionsRequest" + } + ] + } + } + }, + "UpdateSubtitleOffsetRequest": { + "type": "object", + "required": [ + "offset_ms" + ], + "properties": { + "offset_ms": { + "type": "integer", + "format": "int64" + } + } + }, + "UpdateUiConfigRequest": { + "type": "object", + "properties": { + "auto_play_media": { + "type": [ + "boolean", + "null" + ] + }, + "default_page_size": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "default_view": { + "type": [ + "string", + "null" + ] + }, + "default_view_mode": { + "type": [ + "string", + "null" + ] + }, + "show_thumbnails": { + "type": [ + "boolean", + "null" + ] + }, + "sidebar_collapsed": { + "type": [ + "boolean", + "null" + ] + }, + "theme": { + "type": [ + "string", + "null" + ] + } + } + }, + "UploadResponse": { + "type": "object", + "required": [ + "media_id", + "content_hash", + "was_duplicate", + "file_size" + ], + "properties": { + "content_hash": { + "type": "string" + }, + "file_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "media_id": { + "type": "string" + }, + "was_duplicate": { + "type": "boolean" + } + } + }, + "UploadSessionResponse": { + "type": "object", + "required": [ + "id", + "target_path", + "expected_hash", + "expected_size", + "chunk_size", + "chunk_count", + "status", + "created_at", + "expires_at" + ], + "properties": { + "chunk_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "chunk_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "expected_hash": { + "type": "string" + }, + "expected_size": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "target_path": { + "type": "string" + } + } + }, + "UsageEventResponse": { + "type": "object", + "required": [ + "id", + "event_type", + "timestamp" + ], + "properties": { + "duration_secs": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "event_type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "media_id": { + "type": [ + "string", + "null" + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": [ + "string", + "null" + ] + } + } + }, + "UserInfoResponse": { + "type": "object", + "required": [ + "username", + "role" + ], + "properties": { + "role": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "UserLibraryResponse": { + "type": "object", + "required": [ + "user_id", + "root_path", + "permission", + "granted_at" + ], + "properties": { + "granted_at": { + "type": "string", + "format": "date-time" + }, + "permission": { + "type": "string" + }, + "root_path": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, + "UserPreferencesResponse": { + "type": "object", + "required": [ + "auto_play" + ], + "properties": { + "auto_play": { + "type": "boolean" + }, + "default_video_quality": { + "type": [ + "string", + "null" + ] + }, + "language": { + "type": [ + "string", + "null" + ] + }, + "theme": { + "type": [ + "string", + "null" + ] + } + } + }, + "UserProfileResponse": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "avatar_path": { + "type": [ + "string", + "null" + ] + }, + "bio": { + "type": [ + "string", + "null" + ] + }, + "preferences": { + "$ref": "#/components/schemas/UserPreferencesResponse" + } + } + }, + "UserResponse": { + "type": "object", + "required": [ + "id", + "username", + "role", + "profile", + "created_at", + "updated_at" + ], + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "profile": { + "$ref": "#/components/schemas/UserProfileResponse" + }, + "role": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "username": { + "type": "string" + } + } + }, + "VerifyIntegrityRequest": { + "type": "object", + "required": [ + "media_ids" + ], + "properties": { + "media_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "WatchProgressRequest": { + "type": "object", + "required": [ + "progress_secs" + ], + "properties": { + "progress_secs": { + "type": "number", + "format": "double" + } + } + }, + "WatchProgressResponse": { + "type": "object", + "required": [ + "progress_secs" + ], + "properties": { + "progress_secs": { + "type": "number", + "format": "double" + } + } + }, + "WebhookInfo": { + "type": "object", + "required": [ + "url", + "events" + ], + "properties": { + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "url": { + "type": "string" + } + } + } + }, + "securitySchemes": { + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "tags": [ + { + "name": "analytics", + "description": "Usage analytics and viewing history" + }, + { + "name": "audit", + "description": "Audit log entries" + }, + { + "name": "auth", + "description": "Authentication and session management" + }, + { + "name": "backup", + "description": "Database backup" + }, + { + "name": "books", + "description": "Book metadata, series, authors, and reading progress" + }, + { + "name": "collections", + "description": "Media collections" + }, + { + "name": "config", + "description": "Server configuration" + }, + { + "name": "database", + "description": "Database administration" + }, + { + "name": "duplicates", + "description": "Duplicate media detection" + }, + { + "name": "enrichment", + "description": "External metadata enrichment" + }, + { + "name": "export", + "description": "Media library export" + }, + { + "name": "health", + "description": "Server health checks" + }, + { + "name": "integrity", + "description": "Library integrity checks and repairs" + }, + { + "name": "jobs", + "description": "Background job management" + }, + { + "name": "media", + "description": "Media item management" + }, + { + "name": "notes", + "description": "Markdown notes link graph" + }, + { + "name": "photos", + "description": "Photo timeline and map view" + }, + { + "name": "playlists", + "description": "Media playlists" + }, + { + "name": "plugins", + "description": "Plugin management" + }, + { + "name": "saved_searches", + "description": "Saved search queries" + }, + { + "name": "scan", + "description": "Directory scanning" + }, + { + "name": "scheduled_tasks", + "description": "Scheduled background tasks" + }, + { + "name": "search", + "description": "Full-text media search" + }, + { + "name": "shares", + "description": "Media sharing and notifications" + }, + { + "name": "social", + "description": "Ratings, comments, favorites, and share links" + }, + { + "name": "statistics", + "description": "Library statistics" + }, + { + "name": "streaming", + "description": "HLS and DASH adaptive streaming" + }, + { + "name": "subtitles", + "description": "Media subtitle management" + }, + { + "name": "sync", + "description": "Multi-device library synchronization" + }, + { + "name": "tags", + "description": "Media tag management" + }, + { + "name": "transcode", + "description": "Video transcoding sessions" + }, + { + "name": "upload", + "description": "File upload and managed storage" + }, + { + "name": "users", + "description": "User and library access management" + }, + { + "name": "webhooks", + "description": "Webhook configuration" + } + ] +} \ No newline at end of file diff --git a/docs/api/photos.md b/docs/api/photos.md new file mode 100644 index 0000000..5afdba3 --- /dev/null +++ b/docs/api/photos.md @@ -0,0 +1,57 @@ +# Photos + +Photo timeline and map view + +## Endpoints + +### GET /api/v1/photos/map + +Get photos in a bounding box for map view + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `lat1` | query | Yes | Bounding box latitude 1 | +| `lon1` | query | Yes | Bounding box longitude 1 | +| `lat2` | query | Yes | Bounding box latitude 2 | +| `lon2` | query | Yes | Bounding box longitude 2 | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Map markers | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/photos/timeline + +Get timeline of photos grouped by date + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `group_by` | query | No | Grouping: day, month, year | +| `year` | query | No | Filter by year | +| `month` | query | No | Filter by month | +| `limit` | query | No | Max items (default 10000) | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Photo timeline groups | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/playlists.md b/docs/api/playlists.md new file mode 100644 index 0000000..2f97cde --- /dev/null +++ b/docs/api/playlists.md @@ -0,0 +1,229 @@ +# Playlists + +Media playlists + +## Endpoints + +### GET /api/v1/playlists + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of playlists | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/playlists + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Playlist created | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/playlists/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Playlist details | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PATCH /api/v1/playlists/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Playlist updated | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### DELETE /api/v1/playlists/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Playlist deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### GET /api/v1/playlists/{id}/items + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Playlist items | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/playlists/{id}/items + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Item added | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PATCH /api/v1/playlists/{id}/items/reorder + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Item reordered | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### DELETE /api/v1/playlists/{id}/items/{media_id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | +| `media_id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Item removed | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/playlists/{id}/shuffle + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Playlist ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Shuffled playlist items | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + diff --git a/docs/api/plugins.md b/docs/api/plugins.md new file mode 100644 index 0000000..eaab41e --- /dev/null +++ b/docs/api/plugins.md @@ -0,0 +1,209 @@ +# Plugins + +Plugin management + +## Endpoints + +### GET /api/v1/plugins + +List all installed plugins + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of plugins | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/plugins + +Install a plugin from URL or file path + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin installed | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### POST /api/v1/plugins/events + +Receive a plugin event emitted from the UI and dispatch it to interested +server-side event-handler plugins via the pipeline. + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Event received | +| 401 | Unauthorized | + +--- + +### GET /api/v1/plugins/ui/pages + +List all UI pages provided by loaded plugins + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin UI pages | +| 401 | Unauthorized | + +--- + +### GET /api/v1/plugins/ui/theme + +List merged CSS custom property overrides from all enabled plugins + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin UI theme extensions | +| 401 | Unauthorized | + +--- + +### GET /api/v1/plugins/ui/widgets + +List all UI widgets provided by loaded plugins + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin UI widgets | +| 401 | Unauthorized | + +--- + +### GET /api/v1/plugins/{id} + +Get a specific plugin by ID + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Plugin ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin details | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### DELETE /api/v1/plugins/{id} + +Uninstall a plugin + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Plugin ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin uninstalled | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/plugins/{id}/reload + +Reload a plugin (for development) + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Plugin ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin reloaded | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PATCH /api/v1/plugins/{id}/toggle + +Enable or disable a plugin + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Plugin ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Plugin toggled | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + diff --git a/docs/api/saved_searches.md b/docs/api/saved_searches.md new file mode 100644 index 0000000..12e374d --- /dev/null +++ b/docs/api/saved_searches.md @@ -0,0 +1,62 @@ +# Saved_searches + +Saved search queries + +## Endpoints + +### GET /api/v1/searches + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of saved searches | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/searches + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Search saved | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/searches/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Saved search ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Saved search deleted | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/scan.md b/docs/api/scan.md new file mode 100644 index 0000000..9c2af4b --- /dev/null +++ b/docs/api/scan.md @@ -0,0 +1,42 @@ +# Scan + +Directory scanning + +## Endpoints + +### POST /api/v1/scan + +Trigger a scan as a background job. Returns the job ID immediately. + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Scan job submitted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/scan/status + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Scan status | +| 401 | Unauthorized | + +--- + diff --git a/docs/api/scheduled_tasks.md b/docs/api/scheduled_tasks.md new file mode 100644 index 0000000..2367493 --- /dev/null +++ b/docs/api/scheduled_tasks.md @@ -0,0 +1,62 @@ +# Scheduled_tasks + +Scheduled background tasks + +## Endpoints + +### GET /api/v1/scheduled-tasks + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of scheduled tasks | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### POST /api/v1/scheduled-tasks/{id}/run + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Task ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Task triggered | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/scheduled-tasks/{id}/toggle + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Task ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Task toggled | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + diff --git a/docs/api/search.md b/docs/api/search.md new file mode 100644 index 0000000..102d2fb --- /dev/null +++ b/docs/api/search.md @@ -0,0 +1,51 @@ +# Search + +Full-text media search + +## Endpoints + +### GET /api/v1/search + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `q` | query | Yes | Search query | +| `sort` | query | No | Sort order | +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Search results | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/search + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Search results | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/shares.md b/docs/api/shares.md new file mode 100644 index 0000000..9702f41 --- /dev/null +++ b/docs/api/shares.md @@ -0,0 +1,282 @@ +# Shares + +Media sharing and notifications + +## Endpoints + +### GET /api/v1/notifications/shares + +Get unread share notifications +GET /api/notifications/shares + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Unread notifications | +| 401 | Unauthorized | + +--- + +### POST /api/v1/notifications/shares/read-all + +Mark all notifications as read +POST /api/notifications/shares/read-all + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | All notifications marked as read | +| 401 | Unauthorized | + +--- + +### POST /api/v1/notifications/shares/{id}/read + +Mark a notification as read +POST /api/notifications/shares/{id}/read + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Notification ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Notification marked as read | +| 401 | Unauthorized | + +--- + +### GET /api/v1/shared/{token} + +Access a public shared resource +GET /api/shared/{token} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `token` | path | Yes | Share token | +| `password` | query | No | Share password if required | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Shared content | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### POST /api/v1/shares + +Create a new share +POST /api/shares + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Share created | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/shares/batch/delete + +Batch delete shares +POST /api/shares/batch/delete + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Shares deleted | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### GET /api/v1/shares/incoming + +List incoming shares (shares shared with me) +GET /api/shares/incoming + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Incoming shares | +| 401 | Unauthorized | + +--- + +### GET /api/v1/shares/outgoing + +List outgoing shares (shares I created) +GET /api/shares/outgoing + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Outgoing shares | +| 401 | Unauthorized | + +--- + +### GET /api/v1/shares/{id} + +Get share details +GET /api/shares/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Share ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Share details | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PATCH /api/v1/shares/{id} + +Update a share +PATCH /api/shares/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Share ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Share updated | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### DELETE /api/v1/shares/{id} + +Delete (revoke) a share +DELETE /api/shares/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Share ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 204 | Share deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### GET /api/v1/shares/{id}/activity + +Get share activity log +GET /api/shares/{id}/activity + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Share ID | +| `offset` | query | No | Pagination offset | +| `limit` | query | No | Pagination limit | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Share activity | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + diff --git a/docs/api/social.md b/docs/api/social.md new file mode 100644 index 0000000..e706183 --- /dev/null +++ b/docs/api/social.md @@ -0,0 +1,196 @@ +# Social + +Ratings, comments, favorites, and share links + +## Endpoints + +### GET /api/v1/favorites + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User favorites | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/favorites + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Added to favorites | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/favorites/{media_id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `media_id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Removed from favorites | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/share + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Share link created | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/comments + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media comments | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/comments + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Comment added | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{id}/rate + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Rating saved | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/ratings + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media ratings | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/shared/media/{token} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `token` | path | Yes | Share token | +| `password` | query | No | Share password | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Shared media | +| 401 | Unauthorized | +| 404 | Not found | + +--- + diff --git a/docs/api/statistics.md b/docs/api/statistics.md new file mode 100644 index 0000000..270ad62 --- /dev/null +++ b/docs/api/statistics.md @@ -0,0 +1,20 @@ +# Statistics + +Library statistics + +## Endpoints + +### GET /api/v1/statistics + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Library statistics | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/streaming.md b/docs/api/streaming.md new file mode 100644 index 0000000..11a3352 --- /dev/null +++ b/docs/api/streaming.md @@ -0,0 +1,115 @@ +# Streaming + +HLS and DASH adaptive streaming + +## Endpoints + +### GET /api/v1/media/{id}/stream/dash/manifest.mpd + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | DASH manifest | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### GET /api/v1/media/{id}/stream/dash/{profile}/{segment} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | +| `profile` | path | Yes | Transcode profile name | +| `segment` | path | Yes | Segment filename | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | DASH segment data | +| 202 | Segment not yet available | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### GET /api/v1/media/{id}/stream/hls/master.m3u8 + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | HLS master playlist | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### GET /api/v1/media/{id}/stream/hls/{profile}/playlist.m3u8 + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | +| `profile` | path | Yes | Transcode profile name | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | HLS variant playlist | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### GET /api/v1/media/{id}/stream/hls/{profile}/{segment} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | +| `profile` | path | Yes | Transcode profile name | +| `segment` | path | Yes | Segment filename | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | HLS segment data | +| 202 | Segment not yet available | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + diff --git a/docs/api/subtitles.md b/docs/api/subtitles.md new file mode 100644 index 0000000..ce36e05 --- /dev/null +++ b/docs/api/subtitles.md @@ -0,0 +1,120 @@ +# Subtitles + +Media subtitle management + +## Endpoints + +### GET /api/v1/media/{id}/subtitles + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Subtitles | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### POST /api/v1/media/{id}/subtitles + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Subtitle added | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{media_id}/subtitles/{subtitle_id}/content + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `media_id` | path | Yes | Media item ID | +| `subtitle_id` | path | Yes | Subtitle ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Subtitle content | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### DELETE /api/v1/subtitles/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Subtitle ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Subtitle deleted | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### PATCH /api/v1/subtitles/{id}/offset + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Subtitle ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Offset updated | +| 401 | Unauthorized | +| 404 | Not found | + +--- + diff --git a/docs/api/sync.md b/docs/api/sync.md new file mode 100644 index 0000000..165d4c0 --- /dev/null +++ b/docs/api/sync.md @@ -0,0 +1,412 @@ +# Sync + +Multi-device library synchronization + +## Endpoints + +### POST /api/v1/sync/ack + +Acknowledge processed changes +POST /api/sync/ack + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Changes acknowledged | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### GET /api/v1/sync/changes + +Get changes since cursor +GET /api/sync/changes + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `cursor` | query | No | Sync cursor | +| `limit` | query | No | Max changes (max 1000) | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Changes since cursor | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### GET /api/v1/sync/conflicts + +List unresolved conflicts +GET /api/sync/conflicts + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Unresolved conflicts | +| 401 | Unauthorized | + +--- + +### POST /api/v1/sync/conflicts/{id}/resolve + +Resolve a sync conflict +POST /api/sync/conflicts/{id}/resolve + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Conflict ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Conflict resolved | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### GET /api/v1/sync/devices + +List user's sync devices +GET /api/sync/devices + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of devices | +| 401 | Unauthorized | + +--- + +### POST /api/v1/sync/devices + +Register a new sync device +POST /api/sync/devices + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Device registered | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/sync/devices/{id} + +Get device details +GET /api/sync/devices/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Device ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Device details | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PUT /api/v1/sync/devices/{id} + +Update a device +PUT /api/sync/devices/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Device ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Device updated | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### DELETE /api/v1/sync/devices/{id} + +Delete a device +DELETE /api/sync/devices/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Device ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 204 | Device deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### POST /api/v1/sync/devices/{id}/token + +Regenerate device token +POST /api/sync/devices/{id}/token + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Device ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Token regenerated | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### GET /api/v1/sync/download/{path} + +Download a file for sync (supports Range header) +GET /api/sync/download/{*path} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `path` | path | Yes | File path | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | File content | +| 206 | Partial content | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### POST /api/v1/sync/report + +Report local changes from client +POST /api/sync/report + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Changes processed | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### POST /api/v1/sync/upload + +Create an upload session for chunked upload +POST /api/sync/upload + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Upload session created | +| 400 | Bad request | +| 401 | Unauthorized | + +--- + +### GET /api/v1/sync/upload/{id} + +Get upload session status +GET /api/sync/upload/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Upload session ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Upload session status | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### DELETE /api/v1/sync/upload/{id} + +Cancel an upload session +DELETE /api/sync/upload/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Upload session ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 204 | Upload cancelled | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### PUT /api/v1/sync/upload/{id}/chunks/{index} + +Upload a chunk +PUT /api/sync/upload/{id}/chunks/{index} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Upload session ID | +| `index` | path | Yes | Chunk index | + +#### Request Body + +Chunk binary data +`Content-Type: application/octet-stream` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Chunk received | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### POST /api/v1/sync/upload/{id}/complete + +Complete an upload session +POST /api/sync/upload/{id}/complete + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Upload session ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Upload completed | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | + +--- + diff --git a/docs/api/tags.md b/docs/api/tags.md new file mode 100644 index 0000000..a9a71c0 --- /dev/null +++ b/docs/api/tags.md @@ -0,0 +1,157 @@ +# Tags + +Media tag management + +## Endpoints + +### GET /api/v1/media/{media_id}/tags + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `media_id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Media tags | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### POST /api/v1/media/{media_id}/tags + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `media_id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Tag applied | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/media/{media_id}/tags/{tag_id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `media_id` | path | Yes | Media item ID | +| `tag_id` | path | Yes | Tag ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Tag removed | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### GET /api/v1/tags + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of tags | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/tags + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Tag created | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/tags/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Tag ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Tag | +| 401 | Unauthorized | +| 404 | Not found | +| 500 | Internal server error | + +--- + +### DELETE /api/v1/tags/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Tag ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Tag deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | +| 500 | Internal server error | + +--- + diff --git a/docs/api/transcode.md b/docs/api/transcode.md new file mode 100644 index 0000000..126135e --- /dev/null +++ b/docs/api/transcode.md @@ -0,0 +1,86 @@ +# Transcode + +Video transcoding sessions + +## Endpoints + +### POST /api/v1/media/{id}/transcode + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Transcode job submitted | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/transcode + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of transcode sessions | +| 401 | Unauthorized | + +--- + +### GET /api/v1/transcode/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Transcode session ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Transcode session details | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### DELETE /api/v1/transcode/{id} + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Transcode session ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Transcode session cancelled | +| 401 | Unauthorized | +| 404 | Not found | + +--- + diff --git a/docs/api/upload.md b/docs/api/upload.md new file mode 100644 index 0000000..da8a61b --- /dev/null +++ b/docs/api/upload.md @@ -0,0 +1,89 @@ +# Upload + +File upload and managed storage + +## Endpoints + +### GET /api/v1/managed/stats + +Get managed storage statistics +GET /api/managed/stats + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Managed storage statistics | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### GET /api/v1/media/{id}/download + +Download a managed file +GET /api/media/{id}/download + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | File content | +| 400 | Bad request | +| 401 | Unauthorized | +| 404 | Not found | + +--- + +### POST /api/v1/media/{id}/move-to-managed + +Migrate an external file to managed storage +POST /api/media/{id}/move-to-managed + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | Media item ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 204 | File migrated | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + +### POST /api/v1/upload + +Upload a file to managed storage +POST /api/upload + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | File uploaded | +| 400 | Bad request | +| 401 | Unauthorized | +| 500 | Internal server error | + +--- + diff --git a/docs/api/users.md b/docs/api/users.md new file mode 100644 index 0000000..0cd7087 --- /dev/null +++ b/docs/api/users.md @@ -0,0 +1,207 @@ +# Users + +User and library access management + +## Endpoints + +### GET /api/v1/admin/users + +List all users (admin only) + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of users | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### POST /api/v1/admin/users + +Create a new user (admin only) + +**Authentication:** Required (Bearer JWT) + +#### Request Body + +username, password, role, and optional profile fields +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User created | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 500 | Internal server error | + +--- + +### GET /api/v1/admin/users/{id} + +Get a specific user by ID + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User details | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### PATCH /api/v1/admin/users/{id} + +Update a user + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Request Body + +Optional password, role, or profile fields to update +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User updated | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### DELETE /api/v1/admin/users/{id} + +Delete a user (admin only) + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User deleted | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not found | + +--- + +### GET /api/v1/admin/users/{id}/libraries + +Get user's accessible libraries + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | User libraries | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### POST /api/v1/admin/users/{id}/libraries + +Grant library access to a user (admin only) + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Access granted | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### DELETE /api/v1/admin/users/{id}/libraries + +Revoke library access from a user (admin only) + +Uses a JSON body instead of a path parameter because `root_path` may contain +slashes that conflict with URL routing. + +**Authentication:** Required (Bearer JWT) + +#### Parameters + +| Name | In | Required | Description | +|------|----|----------|-------------| +| `id` | path | Yes | User ID | + +#### Request Body + +`Content-Type: application/json` + +See `docs/api/openapi.json` for the full schema. + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Access revoked | +| 400 | Bad request | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + diff --git a/docs/api/webhooks.md b/docs/api/webhooks.md new file mode 100644 index 0000000..9005323 --- /dev/null +++ b/docs/api/webhooks.md @@ -0,0 +1,34 @@ +# Webhooks + +Webhook configuration + +## Endpoints + +### GET /api/v1/webhooks + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | List of configured webhooks | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + +### POST /api/v1/webhooks/test + +**Authentication:** Required (Bearer JWT) + +#### Responses + +| Status | Description | +|--------|-------------| +| 200 | Test webhook sent | +| 401 | Unauthorized | +| 403 | Forbidden | + +--- + diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..58559dc --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2024" +publish = false + +[[bin]] +name = "xtask" +path = "src/main.rs" + +[dependencies] +pinakes-server = { workspace = true } +utoipa = { workspace = true } +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/xtask/src/docs.rs b/xtask/src/docs.rs new file mode 100644 index 0000000..c8d84ee --- /dev/null +++ b/xtask/src/docs.rs @@ -0,0 +1,215 @@ +use std::{collections::BTreeMap, fmt::Write as _}; + +use pinakes_server::api_doc::ApiDoc; +use utoipa::{ + OpenApi, + openapi::{RefOr, Required, path::ParameterIn}, +}; + +#[expect( + clippy::expect_used, + clippy::print_stdout, + reason = "Panics are acceptable here." +)] +pub fn run() { + let api = ApiDoc::openapi(); + + let out_dir = std::path::Path::new("docs/api"); + std::fs::create_dir_all(out_dir).expect("create docs/api dir"); + + let json = serde_json::to_string_pretty(&api).expect("serialize openapi"); + std::fs::write(out_dir.join("openapi.json"), &json) + .expect("write openapi.json"); + println!("Written docs/api/openapi.json"); + + // Collect all operations grouped by tag. + let mut tag_ops: BTreeMap< + String, + Vec<(String, String, &utoipa::openapi::path::Operation)>, + > = BTreeMap::new(); + + for (path, item) in &api.paths.paths { + let method_ops: &[(&str, Option<&utoipa::openapi::path::Operation>)] = &[ + ("GET", item.get.as_ref()), + ("POST", item.post.as_ref()), + ("PUT", item.put.as_ref()), + ("PATCH", item.patch.as_ref()), + ("DELETE", item.delete.as_ref()), + ]; + + for (method, maybe_op) in method_ops { + let Some(op) = maybe_op else { continue }; + + let tags = op.tags.as_deref().unwrap_or(&[]); + if tags.is_empty() { + tag_ops.entry("_untagged".to_owned()).or_default().push(( + (*method).to_owned(), + path.clone(), + op, + )); + } else { + for tag in tags { + tag_ops.entry(tag.clone()).or_default().push(( + (*method).to_owned(), + path.clone(), + op, + )); + } + } + } + } + + // Build a lookup from tag name to description. + let tag_descriptions: BTreeMap = api + .tags + .as_deref() + .unwrap_or(&[]) + .iter() + .map(|t| { + let desc = t.description.as_deref().unwrap_or("").to_owned(); + (t.name.clone(), desc) + }) + .collect(); + + let mut files_written = 0usize; + + for (tag_name, ops) in &tag_ops { + let description = tag_descriptions.get(tag_name).map_or("", String::as_str); + + let mut md = String::new(); + + write!(md, "# {}\n\n", title_case(tag_name)).expect("write to String"); + if !description.is_empty() { + write!(md, "{description}\n\n").expect("write to String"); + } + md.push_str("## Endpoints\n\n"); + + for (method, path, op) in ops { + write_operation(&mut md, method, path, op); + } + + let file_name = format!("{}.md", tag_name.replace('/', "_")); + let dest = out_dir.join(&file_name); + std::fs::write(&dest, &md).expect("write markdown file"); + println!("Written docs/api/{file_name}"); + files_written += 1; + } + + println!( + "Done: wrote docs/api/openapi.json and {files_written} markdown files." + ); +} + +fn title_case(s: &str) -> String { + let mut chars = s.chars(); + chars.next().map_or_else(String::new, |c| { + c.to_uppercase().collect::() + chars.as_str() + }) +} + +#[expect( + clippy::expect_used, + reason = "write! on String is infallible, but clippy still warns on expect()" +)] +fn write_operation( + md: &mut String, + method: &str, + path: &str, + op: &utoipa::openapi::path::Operation, +) { + let summary = op.summary.as_deref().unwrap_or(""); + let description = op.description.as_deref().unwrap_or(""); + + write!(md, "### {method} {path}\n\n").expect("write to String"); + + if !summary.is_empty() { + md.push_str(summary); + md.push('\n'); + if !description.is_empty() { + md.push('\n'); + md.push_str(description); + md.push('\n'); + } + md.push('\n'); + } else if !description.is_empty() { + write!(md, "{description}\n\n").expect("write to String"); + } + + // Authentication + let needs_auth = op.security.as_ref().is_none_or(|s| !s.is_empty()); + if needs_auth { + md.push_str("**Authentication:** Required (Bearer JWT)\n\n"); + } else { + md.push_str("**Authentication:** Not required\n\n"); + } + + // Parameters + if let Some(params) = &op.parameters { + if !params.is_empty() { + md.push_str("#### Parameters\n\n"); + md.push_str("| Name | In | Required | Description |\n"); + md.push_str("|------|----|----------|-------------|\n"); + for p in params { + let location = param_in_str(&p.parameter_in); + let required = match p.required { + Required::True => "Yes", + Required::False => "No", + }; + let desc = p + .description + .as_deref() + .unwrap_or("") + .replace('|', "\\|") + .replace('\n', " "); + writeln!( + md, + "| `{}` | {} | {} | {} |", + p.name, location, required, desc + ) + .expect("write to String"); + } + md.push('\n'); + } + } + + // Request body + if let Some(rb) = &op.request_body { + md.push_str("#### Request Body\n\n"); + if let Some(desc) = &rb.description { + writeln!(md, "{desc}").expect("write to String"); + } + for content_type in rb.content.keys() { + write!(md, "`Content-Type: {content_type}`\n\n") + .expect("write to String"); + md.push_str("See `docs/api/openapi.json` for the full schema.\n\n"); + } + } + + // Responses + let responses = &op.responses; + if !responses.responses.is_empty() { + md.push_str("#### Responses\n\n"); + md.push_str("| Status | Description |\n"); + md.push_str("|--------|-------------|\n"); + for (status, resp) in &responses.responses { + let raw = match resp { + RefOr::T(r) => r.description.as_str(), + RefOr::Ref(_) => "See schema", + }; + let desc = raw.replace('|', "\\|").replace('\n', " "); + writeln!(md, "| {status} | {desc} |").expect("write to String"); + } + md.push('\n'); + } + + md.push_str("---\n\n"); +} + +const fn param_in_str(pin: &ParameterIn) -> &'static str { + match pin { + ParameterIn::Path => "path", + ParameterIn::Query => "query", + ParameterIn::Header => "header", + ParameterIn::Cookie => "cookie", + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..cd395c8 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,19 @@ +mod docs; + +#[expect(clippy::print_stderr)] +fn main() { + let args: Vec = std::env::args().collect(); + match args.get(1).map(String::as_str) { + Some("docs") => docs::run(), + Some(cmd) => { + eprintln!("Unknown command: {cmd}"); + std::process::exit(1); + }, + None => { + eprintln!("Usage: cargo xtask "); + eprintln!("Commands:"); + eprintln!(" docs Generate API documentation"); + std::process::exit(1); + }, + } +}