nix: set up project-wide formatter

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4806c58aa0a17f504c9312723ad770166a6a6964
This commit is contained in:
raf 2026-03-22 23:42:02 +03:00
commit 9e5eb41d39
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
78 changed files with 7406 additions and 2504 deletions

File diff suppressed because one or more lines are too long

85
docs/api/analytics.md vendored
View file

@ -16,11 +16,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Event recorded |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Event recorded |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -30,18 +30,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | No | Maximum number of results |
| `offset` | query | No | Pagination offset |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Most viewed media |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -51,18 +51,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | No | Maximum number of results |
| `offset` | query | No | Pagination offset |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Recently viewed media |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -72,18 +72,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Watch progress |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -93,9 +93,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -105,13 +105,12 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Progress updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---

19
docs/api/audit.md vendored
View file

@ -10,18 +10,17 @@ Audit log entries
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Audit log entries |
| 401 | Unauthorized |
| 500 | Internal server error |
---

68
docs/api/auth.md vendored
View file

@ -16,12 +16,12 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Login successful |
| 400 | Bad request |
| 401 | Invalid credentials |
| 500 | Internal server error |
---
@ -31,11 +31,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Logged out |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Logged out |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -45,28 +45,27 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Current user info |
| 401 | Unauthorized |
| 500 | Internal server error |
| 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.
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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Session refreshed |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -78,11 +77,11 @@ Revoke all sessions for the current user
#### Responses
| Status | Description |
|--------|-------------|
| 200 | All sessions revoked |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | All sessions revoked |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -92,12 +91,11 @@ Revoke all sessions for the current user
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Active sessions |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Active sessions |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

21
docs/api/backup.md vendored
View file

@ -6,22 +6,21 @@ Database backup
### POST /api/v1/admin/backup
Create a database backup and return it as a downloadable file.
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).
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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Backup file download |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

147
docs/api/books.md vendored
View file

@ -12,23 +12,23 @@ List all books with optional search filters
#### 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 |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | List of books |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -40,17 +40,17 @@ List all authors with book counts
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit |
| Name | In | Required | Description |
| -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Authors with book counts |
| 401 | Unauthorized |
| Status | Description |
| ------ | ------------------------ |
| 200 | Authors with book counts |
| 401 | Unauthorized |
---
@ -62,18 +62,18 @@ Get books by a specific author
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `name` | path | Yes | Author name |
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit |
| 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 |
| Status | Description |
| ------ | --------------- |
| 200 | Books by author |
| 401 | Unauthorized |
---
@ -85,16 +85,16 @@ Get user's reading list
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `status` | query | No | Filter by reading status |
| Name | In | Required | Description |
| -------- | ----- | -------- | ------------------------ |
| `status` | query | No | Filter by reading status |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Reading list |
| 401 | Unauthorized |
| Status | Description |
| ------ | ------------ |
| 200 | Reading list |
| 401 | Unauthorized |
---
@ -106,10 +106,10 @@ List all series with book counts
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of series with counts |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------------------- |
| 200 | List of series with counts |
| 401 | Unauthorized |
---
@ -121,16 +121,16 @@ Get books in a specific series
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `name` | path | Yes | Series name |
| Name | In | Required | Description |
| ------ | ---- | -------- | ----------- |
| `name` | path | Yes | Series name |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Books in series |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------- |
| 200 | Books in series |
| 401 | Unauthorized |
---
@ -142,17 +142,17 @@ Get book metadata by media ID
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Book metadata |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ------------- |
| 200 | Book metadata |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -164,17 +164,17 @@ Get reading progress for a book
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Reading progress |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ---------------- |
| 200 | Reading progress |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -186,9 +186,9 @@ Update reading progress for a book
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -198,11 +198,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 204 | Progress updated |
| 400 | Bad request |
| 401 | Unauthorized |
| Status | Description |
| ------ | ---------------- |
| 204 | Progress updated |
| 400 | Bad request |
| 401 | Unauthorized |
---

View file

@ -10,11 +10,11 @@ Media collections
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of collections |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | List of collections |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -30,13 +30,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Collection created |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -46,18 +46,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Collection ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Collection |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Collection |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -67,19 +67,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Collection ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Collection deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -89,18 +89,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Collection ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Collection members |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Collection members |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -110,9 +110,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Collection ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID |
#### Request Body
@ -122,13 +122,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Member added |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -138,20 +138,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Collection ID |
| `media_id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Member removed |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---

73
docs/api/config.md vendored
View file

@ -10,12 +10,12 @@ Server configuration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Current server configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | ---------------------------- |
| 200 | Current server configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -31,13 +31,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Updated configuration |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -53,12 +53,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Updated configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Updated configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -74,12 +74,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Updated configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Updated configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -89,11 +89,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | UI configuration |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | UI configuration |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -109,12 +109,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Updated UI configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | ------------------------ |
| 200 | Updated UI configuration |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

37
docs/api/database.md vendored
View file

@ -10,12 +10,12 @@ Database administration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Database cleared |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Database cleared |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -25,12 +25,12 @@ Database administration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Database statistics |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Database statistics |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -40,12 +40,11 @@ Database administration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Database vacuumed |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Database vacuumed |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

View file

@ -10,11 +10,10 @@ Duplicate media detection
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Duplicate groups |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Duplicate groups |
| 401 | Unauthorized |
| 500 | Internal server error |
---

View file

@ -16,13 +16,13 @@ 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 |
| Status | Description |
| ------ | ------------------------ |
| 200 | Enrichment job submitted |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -32,19 +32,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | ------------------------ |
| 200 | Enrichment job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -54,18 +54,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | External metadata |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---

25
docs/api/export.md vendored
View file

@ -10,12 +10,12 @@ Media library export
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Export job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Export job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -31,12 +31,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Export job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Export job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

35
docs/api/health.md vendored
View file

@ -12,9 +12,9 @@ Comprehensive health check - includes database, filesystem, and cache status
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Health status |
| Status | Description |
| ------ | ------------- |
| 200 | Health status |
---
@ -24,40 +24,39 @@ Comprehensive health check - includes database, filesystem, and cache status
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Detailed health status |
| 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
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 |
| 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
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 |
| Status | Description |
| ------ | ---------------- |
| 200 | Server is ready |
| 503 | Server not ready |
---

61
docs/api/integrity.md vendored
View file

@ -10,12 +10,12 @@ Library integrity checks and repairs
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Orphan detection job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | ------------------------------ |
| 200 | Orphan detection job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -31,12 +31,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Orphans resolved |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Orphans resolved |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -46,12 +46,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Thumbnail cleanup job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | ------------------------------- |
| 200 | Thumbnail cleanup job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -67,12 +67,12 @@ 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 |
| Status | Description |
| ------ | ---------------------------------- |
| 200 | Thumbnail generation job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -88,12 +88,11 @@ 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 |
| Status | Description |
| ------ | ------------------------------------ |
| 200 | Integrity verification job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---

47
docs/api/jobs.md vendored
View file

@ -10,11 +10,11 @@ Background job management
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of jobs |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | ------------ |
| 200 | List of jobs |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -24,18 +24,18 @@ Background job management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Job ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Job ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Job details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | Job details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -45,18 +45,17 @@ Background job management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Job ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Job ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Job cancelled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------- |
| 200 | Job cancelled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---

489
docs/api/media.md vendored
View file

@ -10,19 +10,19 @@ Media item management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size |
| `sort` | query | No | Sort field |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | List of media items |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -32,12 +32,12 @@ Media item management
#### Responses
| Status | Description |
|--------|-------------|
| 200 | All media deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | All media deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -53,13 +53,13 @@ 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 |
| Status | Description |
| ------ | ----------------------- |
| 200 | Batch collection result |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -75,13 +75,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Batch delete result |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -97,13 +97,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Batch move result |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -119,13 +119,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Batch tag result |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -141,13 +141,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Batch update result |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -157,11 +157,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Media count |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Media count |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -177,13 +177,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media imported |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -199,13 +199,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Batch import results |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -221,13 +221,13 @@ 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 |
| Status | Description |
| ------ | ------------------------ |
| 200 | Directory import results |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -243,13 +243,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media imported |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -265,13 +265,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Directory preview |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -281,18 +281,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Trashed media items |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -302,12 +302,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Trash emptied |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Trash emptied |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -317,11 +317,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Trash info |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Trash info |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -331,18 +331,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media item |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -352,9 +352,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -364,14 +364,14 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Updated media item |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -381,19 +381,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -403,9 +403,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -415,14 +415,14 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Custom field set |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -432,20 +432,20 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| `name` | path | Yes | Custom field name |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Custom field deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -455,9 +455,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -467,14 +467,14 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Moved media item |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -484,18 +484,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media opened |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -505,20 +505,20 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| `permanent` | query | No | Set to 'true' for permanent deletion |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -528,9 +528,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -540,14 +540,14 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Renamed media item |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -557,19 +557,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media restored |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -579,19 +579,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media stream |
| 206 | Partial content |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -601,18 +601,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Thumbnail image |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -622,19 +622,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media moved to trash |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---

93
docs/api/notes.md vendored
View file

@ -14,18 +14,18 @@ GET /api/v1/media/{id}/backlinks
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Backlinks |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Backlinks |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -39,18 +39,18 @@ GET /api/v1/media/{id}/outgoing-links
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Outgoing links |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -64,18 +64,18 @@ POST /api/v1/media/{id}/reindex-links
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Links reindexed |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -89,18 +89,18 @@ GET /api/v1/notes/graph?center={uuid}&depth={n}
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `center` | query | No | Center node ID |
| `depth` | query | No | Traversal depth (max 5, default 2) |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Graph data |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -114,11 +114,11 @@ POST /api/v1/notes/resolve-links
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Links resolved |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Links resolved |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -132,11 +132,10 @@ GET /api/v1/notes/unresolved-count
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Unresolved link count |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Unresolved link count |
| 401 | Unauthorized |
| 500 | Internal server error |
---

47
docs/api/photos.md vendored
View file

@ -12,21 +12,21 @@ Get photos in a bounding box for map view
#### 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 |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Map markers |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -38,20 +38,19 @@ Get timeline of photos grouped by date
#### 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) |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Photo timeline groups |
| 401 | Unauthorized |
| 500 | Internal server error |
---

171
docs/api/playlists.md vendored
View file

@ -10,11 +10,11 @@ Media playlists
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of playlists |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | List of playlists |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -30,12 +30,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Playlist created |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Playlist created |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -45,18 +45,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Playlist details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ---------------- |
| 200 | Playlist details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -66,9 +66,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Request Body
@ -78,13 +78,13 @@ 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 |
| Status | Description |
| ------ | ---------------- |
| 200 | Playlist updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -94,18 +94,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Playlist deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ---------------- |
| 200 | Playlist deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -115,18 +115,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Playlist items |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Playlist items |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -136,9 +136,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Request Body
@ -148,12 +148,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Item added |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | Item added |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -163,9 +163,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Request Body
@ -175,12 +175,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Item reordered |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Item reordered |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -190,19 +190,19 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| `media_id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | ------------ |
| 200 | Item removed |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -212,18 +212,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Playlist ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Shuffled playlist items |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ----------------------- |
| 200 | Shuffled playlist items |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---

125
docs/api/plugins.md vendored
View file

@ -12,11 +12,11 @@ List all installed plugins
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of plugins |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | List of plugins |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -34,12 +34,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin installed |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | ---------------- |
| 200 | Plugin installed |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -58,10 +58,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Event received |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------- |
| 200 | Event received |
| 401 | Unauthorized |
---
@ -73,10 +73,10 @@ List all UI pages provided by loaded plugins
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin UI pages |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------- |
| 200 | Plugin UI pages |
| 401 | Unauthorized |
---
@ -88,10 +88,10 @@ List merged CSS custom property overrides from all enabled plugins
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin UI theme extensions |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------------------- |
| 200 | Plugin UI theme extensions |
| 401 | Unauthorized |
---
@ -103,10 +103,10 @@ List all UI widgets provided by loaded plugins
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin UI widgets |
| 401 | Unauthorized |
| Status | Description |
| ------ | ----------------- |
| 200 | Plugin UI widgets |
| 401 | Unauthorized |
---
@ -118,17 +118,17 @@ Get a specific plugin by ID
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Plugin ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin details |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Plugin details |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -140,18 +140,18 @@ Uninstall a plugin
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Plugin ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin uninstalled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------------ |
| 200 | Plugin uninstalled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -163,18 +163,18 @@ Reload a plugin (for development)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Plugin ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin reloaded |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | --------------- |
| 200 | Plugin reloaded |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -186,9 +186,9 @@ Enable or disable a plugin
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Plugin ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID |
#### Request Body
@ -198,12 +198,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Plugin toggled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Plugin toggled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---

View file

@ -10,11 +10,11 @@ Saved search queries
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of saved searches |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | ---------------------- |
| 200 | List of saved searches |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -30,12 +30,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Search saved |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Search saved |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -45,18 +45,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Saved search ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Saved search deleted |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---

21
docs/api/scan.md vendored
View file

@ -18,12 +18,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Scan job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Scan job submitted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -33,10 +33,9 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Scan status |
| 401 | Unauthorized |
| Status | Description |
| ------ | ------------ |
| 200 | Scan status |
| 401 | Unauthorized |
---

View file

@ -10,11 +10,11 @@ Scheduled background tasks
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of scheduled tasks |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | ----------------------- |
| 200 | List of scheduled tasks |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -24,18 +24,18 @@ Scheduled background tasks
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Task ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Task ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Task triggered |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Task triggered |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -45,18 +45,17 @@ Scheduled background tasks
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Task ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Task ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Task toggled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | Task toggled |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---

37
docs/api/search.md vendored
View file

@ -10,21 +10,21 @@ Full-text media search
#### 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 |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Search results |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -40,12 +40,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Search results |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Search results |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---

217
docs/api/shares.md vendored
View file

@ -6,86 +6,81 @@ Media sharing and notifications
### GET /api/v1/notifications/shares
Get unread share notifications
GET /api/notifications/shares
Get unread share notifications GET /api/notifications/shares
**Authentication:** Required (Bearer JWT)
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Unread notifications |
| 401 | Unauthorized |
| 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
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 |
| 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
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 |
| Name | In | Required | Description |
| ---- | ---- | -------- | --------------- |
| `id` | path | Yes | Notification ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Notification marked as read |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------------------- |
| 200 | Notification marked as read |
| 401 | Unauthorized |
---
### GET /api/v1/shared/{token}
Access a public shared resource
GET /api/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 |
| 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 |
| Status | Description |
| ------ | -------------- |
| 200 | Shared content |
| 401 | Unauthorized |
| 404 | Not found |
---
### POST /api/v1/shares
Create a new share
POST /api/shares
Create a new share POST /api/shares
**Authentication:** Required (Bearer JWT)
@ -97,19 +92,18 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Share created |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| 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
Batch delete shares POST /api/shares/batch/delete
**Authentication:** Required (Bearer JWT)
@ -121,97 +115,93 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Shares deleted |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 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
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 |
| Name | In | Required | Description |
| -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Incoming shares |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------- |
| 200 | Incoming shares |
| 401 | Unauthorized |
---
### GET /api/v1/shares/outgoing
List outgoing shares (shares I created)
GET /api/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 |
| Name | In | Required | Description |
| -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Outgoing shares |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------- |
| 200 | Outgoing shares |
| 401 | Unauthorized |
---
### GET /api/v1/shares/{id}
Get share details
GET /api/shares/{id}
Get share details GET /api/shares/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Share ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Share details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------- |
| 200 | Share details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
### PATCH /api/v1/shares/{id}
Update a share
PATCH /api/shares/{id}
Update a share PATCH /api/shares/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Share ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID |
#### Request Body
@ -221,62 +211,59 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Share updated |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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}
Delete (revoke) a share DELETE /api/shares/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Share ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID |
#### Responses
| Status | Description |
|--------|-------------|
| 204 | Share deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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
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 |
| 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 |
| Status | Description |
| ------ | -------------- |
| 200 | Share activity |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---

135
docs/api/social.md vendored
View file

@ -10,11 +10,11 @@ Ratings, comments, favorites, and share links
#### Responses
| Status | Description |
|--------|-------------|
| 200 | User favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | User favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -30,11 +30,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Added to favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Added to favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -44,17 +44,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `media_id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Removed from favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | ---------------------- |
| 200 | Removed from favorites |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -70,12 +70,12 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Share link created |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -85,17 +85,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Media comments |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Media comments |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -105,9 +105,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -117,12 +117,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Comment added |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Comment added |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -132,9 +132,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -144,12 +144,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Rating saved |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Rating saved |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -159,17 +159,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Media ratings |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Media ratings |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -179,18 +179,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `token` | path | Yes | Share token |
| `password` | query | No | Share password |
| 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 |
| Status | Description |
| ------ | ------------ |
| 200 | Shared media |
| 401 | Unauthorized |
| 404 | Not found |
---

View file

@ -10,11 +10,10 @@ Library statistics
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Library statistics |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Library statistics |
| 401 | Unauthorized |
| 500 | Internal server error |
---

99
docs/api/streaming.md vendored
View file

@ -10,18 +10,18 @@ HLS and DASH adaptive streaming
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | DASH manifest |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ------------- |
| 200 | DASH manifest |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -31,20 +31,20 @@ HLS and DASH adaptive streaming
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name |
| `segment` | path | Yes | Segment filename |
| 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 |
| Status | Description |
| ------ | ------------------------- |
| 200 | DASH segment data |
| 202 | Segment not yet available |
| 400 | Bad request |
| 401 | Unauthorized |
---
@ -54,17 +54,17 @@ HLS and DASH adaptive streaming
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | HLS master playlist |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ------------------- |
| 200 | HLS master playlist |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -74,19 +74,19 @@ HLS and DASH adaptive streaming
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name |
| 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 |
| Status | Description |
| ------ | -------------------- |
| 200 | HLS variant playlist |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -96,20 +96,19 @@ HLS and DASH adaptive streaming
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name |
| `segment` | path | Yes | Segment filename |
| 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 |
| Status | Description |
| ------ | ------------------------- |
| 200 | HLS segment data |
| 202 | Segment not yet available |
| 400 | Bad request |
| 401 | Unauthorized |
---

85
docs/api/subtitles.md vendored
View file

@ -10,17 +10,17 @@ Media subtitle management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Subtitles |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | Subtitles |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -30,9 +30,9 @@ Media subtitle management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -42,12 +42,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Subtitle added |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Subtitle added |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -57,18 +57,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `media_id` | path | Yes | Media item ID |
| `subtitle_id` | path | Yes | Subtitle ID |
| 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 |
| Status | Description |
| ------ | ---------------- |
| 200 | Subtitle content |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -78,17 +78,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Subtitle ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Subtitle ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Subtitle deleted |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ---------------- |
| 200 | Subtitle deleted |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -98,9 +98,9 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Subtitle ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Subtitle ID |
#### Request Body
@ -110,11 +110,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Offset updated |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | -------------- |
| 200 | Offset updated |
| 401 | Unauthorized |
| 404 | Not found |
---

307
docs/api/sync.md vendored
View file

@ -6,8 +6,7 @@ Multi-device library synchronization
### POST /api/v1/sync/ack
Acknowledge processed changes
POST /api/sync/ack
Acknowledge processed changes POST /api/sync/ack
**Authentication:** Required (Bearer JWT)
@ -19,66 +18,63 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Changes acknowledged |
| 400 | Bad request |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------------- |
| 200 | Changes acknowledged |
| 400 | Bad request |
| 401 | Unauthorized |
---
### GET /api/v1/sync/changes
Get changes since cursor
GET /api/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) |
| 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 |
| Status | Description |
| ------ | -------------------- |
| 200 | Changes since cursor |
| 400 | Bad request |
| 401 | Unauthorized |
---
### GET /api/v1/sync/conflicts
List unresolved conflicts
GET /api/sync/conflicts
List unresolved conflicts GET /api/sync/conflicts
**Authentication:** Required (Bearer JWT)
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Unresolved conflicts |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------------- |
| 200 | Unresolved conflicts |
| 401 | Unauthorized |
---
### POST /api/v1/sync/conflicts/{id}/resolve
Resolve a sync conflict
POST /api/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 |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Conflict ID |
#### Request Body
@ -88,34 +84,32 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Conflict resolved |
| 400 | Bad request |
| 401 | Unauthorized |
| Status | Description |
| ------ | ----------------- |
| 200 | Conflict resolved |
| 400 | Bad request |
| 401 | Unauthorized |
---
### GET /api/v1/sync/devices
List user's sync devices
GET /api/sync/devices
List user's sync devices GET /api/sync/devices
**Authentication:** Required (Bearer JWT)
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of devices |
| 401 | Unauthorized |
| Status | Description |
| ------ | --------------- |
| 200 | List of devices |
| 401 | Unauthorized |
---
### POST /api/v1/sync/devices
Register a new sync device
POST /api/sync/devices
Register a new sync device POST /api/sync/devices
**Authentication:** Required (Bearer JWT)
@ -127,51 +121,49 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Device registered |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
| 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}
Get device details GET /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Device ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Device details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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}
Update a device PUT /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Device ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID |
#### Request Body
@ -181,91 +173,87 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Device updated |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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}
Delete a device DELETE /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Device ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID |
#### Responses
| Status | Description |
|--------|-------------|
| 204 | Device deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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
Regenerate device token POST /api/sync/devices/{id}/token
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Device ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Token regenerated |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 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}
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 |
| Name | In | Required | Description |
| ------ | ---- | -------- | ----------- |
| `path` | path | Yes | File path |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | File content |
| 206 | Partial content |
| 401 | Unauthorized |
| 404 | Not found |
| 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
Report local changes from client POST /api/sync/report
**Authentication:** Required (Bearer JWT)
@ -277,18 +265,17 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Changes processed |
| 400 | Bad request |
| 401 | Unauthorized |
| 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
Create an upload session for chunked upload POST /api/sync/upload
**Authentication:** Required (Bearer JWT)
@ -300,113 +287,107 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Upload session created |
| 400 | Bad request |
| 401 | Unauthorized |
| 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}
Get upload session status GET /api/sync/upload/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Upload session ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Upload session status |
| 401 | Unauthorized |
| 404 | Not found |
| 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}
Cancel an upload session DELETE /api/sync/upload/{id}
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Upload session ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID |
#### Responses
| Status | Description |
|--------|-------------|
| 204 | Upload cancelled |
| 401 | Unauthorized |
| 404 | Not found |
| 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}
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 |
| 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`
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 |
| 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
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 |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Upload completed |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ---------------- |
| 200 | Upload completed |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
---

123
docs/api/tags.md vendored
View file

@ -10,18 +10,18 @@ Media tag management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `media_id` | path | Yes | Media item ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Media tags |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -31,9 +31,9 @@ Media tag management
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `media_id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID |
#### Request Body
@ -43,13 +43,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Tag applied |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -59,20 +59,20 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `media_id` | path | Yes | Media item ID |
| `tag_id` | path | Yes | Tag ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Tag removed |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---
@ -82,11 +82,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of tags |
| 401 | Unauthorized |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | List of tags |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -102,13 +102,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Tag created |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -118,18 +118,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Tag ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Tag ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Tag |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
| Status | Description |
| ------ | --------------------- |
| 200 | Tag |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Internal server error |
---
@ -139,19 +139,18 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Tag ID |
| 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | Tag deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal server error |
---

59
docs/api/transcode.md vendored
View file

@ -10,9 +10,9 @@ Video transcoding sessions
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Request Body
@ -22,12 +22,12 @@ 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 |
| Status | Description |
| ------ | ----------------------- |
| 200 | Transcode job submitted |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---
@ -37,10 +37,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of transcode sessions |
| 401 | Unauthorized |
| Status | Description |
| ------ | -------------------------- |
| 200 | List of transcode sessions |
| 401 | Unauthorized |
---
@ -50,17 +50,17 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Transcode session ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | -------------------- |
| `id` | path | Yes | Transcode session ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Transcode session details |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | ------------------------- |
| 200 | Transcode session details |
| 401 | Unauthorized |
| 404 | Not found |
---
@ -70,17 +70,16 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Transcode session ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | -------------------- |
| `id` | path | Yes | Transcode session ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Transcode session cancelled |
| 401 | Unauthorized |
| 404 | Not found |
| Status | Description |
| ------ | --------------------------- |
| 200 | Transcode session cancelled |
| 401 | Unauthorized |
| 404 | Not found |
---

71
docs/api/upload.md vendored
View file

@ -6,84 +6,79 @@ File upload and managed storage
### GET /api/v1/managed/stats
Get managed storage statistics
GET /api/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 |
| 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
Download a managed file GET /api/media/{id}/download
**Authentication:** Required (Bearer JWT)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | Media item ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | File content |
| 400 | Bad request |
| 401 | Unauthorized |
| 404 | Not found |
| 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
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 |
| 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 |
| 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
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 |
| Status | Description |
| ------ | --------------------- |
| 200 | File uploaded |
| 400 | Bad request |
| 401 | Unauthorized |
| 500 | Internal server error |
---

133
docs/api/users.md vendored
View file

@ -12,11 +12,11 @@ List all users (admin only)
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of users |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | ------------- |
| 200 | List of users |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -35,13 +35,13 @@ 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 |
| Status | Description |
| ------ | --------------------- |
| 200 | User created |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 500 | Internal server error |
---
@ -53,18 +53,18 @@ Get a specific user by ID
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | User details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | User details |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -76,9 +76,9 @@ Update a user
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Request Body
@ -89,13 +89,13 @@ 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 |
| Status | Description |
| ------ | ------------ |
| 200 | User updated |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -107,18 +107,18 @@ Delete a user (admin only)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | User deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| Status | Description |
| ------ | ------------ |
| 200 | User deleted |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
---
@ -130,17 +130,17 @@ Get user's accessible libraries
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Responses
| Status | Description |
|--------|-------------|
| 200 | User libraries |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | -------------- |
| 200 | User libraries |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -152,9 +152,9 @@ Grant library access to a user (admin only)
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Request Body
@ -164,12 +164,12 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Access granted |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | -------------- |
| 200 | Access granted |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -184,9 +184,9 @@ slashes that conflict with URL routing.
#### Parameters
| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | Yes | User ID |
| Name | In | Required | Description |
| ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID |
#### Request Body
@ -196,12 +196,11 @@ See `docs/api/openapi.json` for the full schema.
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Access revoked |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | -------------- |
| 200 | Access revoked |
| 400 | Bad request |
| 401 | Unauthorized |
| 403 | Forbidden |
---

21
docs/api/webhooks.md vendored
View file

@ -10,11 +10,11 @@ Webhook configuration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | List of configured webhooks |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | --------------------------- |
| 200 | List of configured webhooks |
| 401 | Unauthorized |
| 403 | Forbidden |
---
@ -24,11 +24,10 @@ Webhook configuration
#### Responses
| Status | Description |
|--------|-------------|
| 200 | Test webhook sent |
| 401 | Unauthorized |
| 403 | Forbidden |
| Status | Description |
| ------ | ----------------- |
| 200 | Test webhook sent |
| 401 | Unauthorized |
| 403 | Forbidden |
---

View file

@ -1,18 +1,23 @@
# HEIF/HEIC Support Plugin
This example plugin adds support for HEIF (High Efficiency Image Format) and HEIC (HEIF Container) to Pinakes.
This example plugin adds support for HEIF (High Efficiency Image Format) and
HEIC (HEIF Container) to Pinakes.
## Overview
HEIF is a modern image format that provides better compression than JPEG while maintaining higher quality. This plugin enables Pinakes to:
HEIF is a modern image format that provides better compression than JPEG while
maintaining higher quality. This plugin enables Pinakes to:
- Recognize HEIF/HEIC files as a media type
- Extract metadata from HEIF images
- Generate thumbnails from HEIF images
## Features
- **Media Type Registration**: Registers `.heif`, `.heic`, `.hif` extensions as image media types
- **EXIF Extraction**: Extracts EXIF metadata including camera info, GPS coordinates, timestamps
- **Media Type Registration**: Registers `.heif`, `.heic`, `.hif` extensions as
image media types
- **EXIF Extraction**: Extracts EXIF metadata including camera info, GPS
coordinates, timestamps
- **Thumbnail Generation**: Generates thumbnails in JPEG, PNG, or WebP format
- **Resource Limits**: Configurable memory and CPU limits for safe processing
- **Large Image Support**: Handles images up to 8192x8192 pixels
@ -95,6 +100,7 @@ impl ThumbnailGenerator for HeifPlugin {
## Dependencies
The plugin uses the following Rust crates (compiled to WASM):
- `libheif-rs`: HEIF decoding and encoding
- `image`: Image processing and thumbnail generation
- `kamadak-exif`: EXIF metadata parsing
@ -159,15 +165,19 @@ pinakes plugin install /path/to/heif-support
The plugin can be configured through the `config` section in `plugin.toml`:
### EXIF Extraction
- `extract_exif`: Enable EXIF metadata extraction (default: true)
### Thumbnail Generation
- `generate_thumbnails`: Enable thumbnail generation (default: true)
- `thumbnail_quality`: JPEG quality for thumbnails, 1-100 (default: 85)
- `thumbnail_format`: Output format - "jpeg", "png", or "webp" (default: "jpeg")
### Resource Limits
- `max_memory_mb`: Maximum memory the plugin can use in megabytes (default: 256, set in `[capabilities]`)
- `max_memory_mb`: Maximum memory the plugin can use in megabytes (default: 256,
set in `[capabilities]`)
- `max_width`: Maximum image width to process (default: 8192)
- `max_height`: Maximum image height to process (default: 8192)
@ -188,6 +198,7 @@ The plugin can be configured through the `config` section in `plugin.toml`:
### Sandboxing
The plugin runs in a WASM sandbox with:
- No access to host filesystem beyond granted paths
- No network access
- No arbitrary code execution
@ -210,6 +221,7 @@ The plugin runs in a WASM sandbox with:
## Error Handling
The plugin handles:
- **Corrupted Files**: Returns descriptive error
- **Unsupported Variants**: Gracefully skips unsupported HEIF features
- **Memory Limits**: Fails safely if image too large

View file

@ -1,10 +1,12 @@
# Markdown Metadata Extractor Plugin
This example plugin demonstrates how to create a metadata extractor plugin for Pinakes.
This example plugin demonstrates how to create a metadata extractor plugin for
Pinakes.
## Overview
The Markdown Metadata Extractor enhances Pinakes' built-in markdown support by:
- Parsing YAML and TOML frontmatter
- Extracting metadata from frontmatter fields
- Converting frontmatter tags to Pinakes media tags
@ -12,8 +14,10 @@ The Markdown Metadata Extractor enhances Pinakes' built-in markdown support by:
## Features
- **Frontmatter Parsing**: Supports both YAML (`---`) and TOML (`+++`) frontmatter formats
- **Tag Extraction**: Automatically extracts tags from frontmatter and applies them to media items
- **Frontmatter Parsing**: Supports both YAML (`---`) and TOML (`+++`)
frontmatter formats
- **Tag Extraction**: Automatically extracts tags from frontmatter and applies
them to media items
- **Custom Fields**: Preserves all frontmatter fields as custom metadata
- **Configuration**: Configurable via `plugin.toml` config section
@ -88,6 +92,7 @@ The plugin can be configured through the `config` section in `plugin.toml`:
## Security
This plugin has minimal capabilities:
- **Filesystem**: No write access, read access only to files being processed
- **Network**: Disabled
- **Environment**: No access

BIN
flake.lock generated

Binary file not shown.

View file

@ -21,5 +21,38 @@
in {
default = pkgs.callPackage ./nix/shell.nix {};
});
formatter = forEachSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in
pkgs.writeShellApplication {
name = "nix3-fmt-wrapper";
runtimeInputs = [
pkgs.alejandra
pkgs.fd
pkgs.prettier
pkgs.deno
pkgs.taplo
pkgs.sql-formatter
];
text = ''
# Format Nix with Alejandra
fd "$@" -t f -e nix -x alejandra -q '{}'
# Format TOML with Taplo
fd "$@" -t f -e toml -x taplo fmt '{}'
# Format CSS with Prettier
fd "$@" -t f -e css -x prettier --write '{}'
# Format SQL with sql-format
fd "$@" -t f -e sql -x sql-formatter --fix '{}' -l postgresql
# Format Markdown with Deno
fd "$@" -t f -e md -x deno fmt -q '{}'
'';
});
};
}

View file

@ -1,19 +1,19 @@
-- Add file_mtime column to media_items table for incremental scanning
-- Stores Unix timestamp in seconds of the file's modification time
ALTER TABLE media_items ADD COLUMN file_mtime BIGINT;
ALTER TABLE media_items
ADD COLUMN file_mtime BIGINT;
-- Create index for quick mtime lookups
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items(file_mtime);
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items (file_mtime);
-- Create a scan_history table to track when each directory was last scanned
CREATE TABLE IF NOT EXISTS scan_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
directory TEXT NOT NULL UNIQUE,
last_scan_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
directory TEXT NOT NULL UNIQUE,
last_scan_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history(directory);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history (directory);

View file

@ -1,18 +1,17 @@
-- Session persistence for database-backed sessions
-- Replaces in-memory session storage
CREATE TABLE IF NOT EXISTS sessions (
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
last_accessed TIMESTAMPTZ NOT NULL
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
last_accessed TIMESTAMPTZ NOT NULL
);
-- Index for efficient cleanup of expired sessions
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions (expires_at);
-- Index for listing sessions by username
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions (username);

View file

@ -1,60 +1,61 @@
-- V12: Book Management Schema (PostgreSQL)
-- Adds comprehensive book metadata tracking, authors, and identifiers
-- Book metadata (supplements media_items for EPUB/PDF/MOBI)
CREATE TABLE book_metadata (
media_id UUID PRIMARY KEY REFERENCES media_items(id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date DATE,
series_name TEXT,
series_index DOUBLE PRECISION, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
media_id UUID PRIMARY KEY REFERENCES media_items (id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date DATE,
series_name TEXT,
series_index DOUBLE PRECISION, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_book_isbn13 ON book_metadata(isbn13);
CREATE INDEX idx_book_series ON book_metadata(series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata(publisher);
CREATE INDEX idx_book_language ON book_metadata(language);
CREATE INDEX idx_book_isbn13 ON book_metadata (isbn13);
CREATE INDEX idx_book_series ON book_metadata (series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata (publisher);
CREATE INDEX idx_book_language ON book_metadata (language);
-- Multiple authors per book (many-to-many)
CREATE TABLE book_authors (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
);
CREATE INDEX idx_book_authors_name ON book_authors(author_name);
CREATE INDEX idx_book_authors_sort ON book_authors(author_sort);
CREATE INDEX idx_book_authors_name ON book_authors (author_name);
CREATE INDEX idx_book_authors_sort ON book_authors (author_sort);
-- Multiple identifiers (ISBN variants, ASIN, DOI, etc.)
CREATE TABLE book_identifiers (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
);
CREATE INDEX idx_book_identifiers ON book_identifiers(identifier_type, identifier_value);
CREATE INDEX idx_book_identifiers ON book_identifiers (identifier_type, identifier_value);
-- Trigger to update updated_at on book_metadata changes
CREATE OR REPLACE FUNCTION update_book_metadata_timestamp()
RETURNS TRIGGER AS $$
CREATE OR REPLACE FUNCTION update_book_metadata_timestamp () RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_book_metadata_timestamp
BEFORE UPDATE ON book_metadata
FOR EACH ROW
EXECUTE FUNCTION update_book_metadata_timestamp();
CREATE TRIGGER update_book_metadata_timestamp BEFORE
UPDATE ON book_metadata FOR EACH ROW
EXECUTE FUNCTION update_book_metadata_timestamp ();

View file

@ -1,15 +1,40 @@
-- V13: Enhanced photo metadata support
-- Add photo-specific fields to media_items table
ALTER TABLE media_items
ADD COLUMN date_taken TIMESTAMPTZ;
ALTER TABLE media_items ADD COLUMN date_taken TIMESTAMPTZ;
ALTER TABLE media_items ADD COLUMN latitude DOUBLE PRECISION;
ALTER TABLE media_items ADD COLUMN longitude DOUBLE PRECISION;
ALTER TABLE media_items ADD COLUMN camera_make TEXT;
ALTER TABLE media_items ADD COLUMN camera_model TEXT;
ALTER TABLE media_items ADD COLUMN rating INTEGER CHECK (rating >= 0 AND rating <= 5);
ALTER TABLE media_items
ADD COLUMN latitude DOUBLE PRECISION;
ALTER TABLE media_items
ADD COLUMN longitude DOUBLE PRECISION;
ALTER TABLE media_items
ADD COLUMN camera_make TEXT;
ALTER TABLE media_items
ADD COLUMN camera_model TEXT;
ALTER TABLE media_items
ADD COLUMN rating INTEGER CHECK (
rating >= 0
AND rating <= 5
);
-- Indexes for photo queries
CREATE INDEX idx_media_date_taken ON media_items(date_taken) WHERE date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items(latitude, longitude) WHERE latitude IS NOT NULL AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items(camera_make) WHERE camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items(rating) WHERE rating IS NOT NULL;
CREATE INDEX idx_media_date_taken ON media_items (date_taken)
WHERE
date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items (latitude, longitude)
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items (camera_make)
WHERE
camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items (rating)
WHERE
rating IS NOT NULL;

View file

@ -1,7 +1,9 @@
-- V14: Perceptual hash for duplicate detection
-- Add perceptual hash column for image similarity detection
ALTER TABLE media_items ADD COLUMN perceptual_hash TEXT;
ALTER TABLE media_items
ADD COLUMN perceptual_hash TEXT;
-- Index for perceptual hash lookups
CREATE INDEX idx_media_phash ON media_items(perceptual_hash) WHERE perceptual_hash IS NOT NULL;
CREATE INDEX idx_media_phash ON media_items (perceptual_hash)
WHERE
perceptual_hash IS NOT NULL;

View file

@ -1,30 +1,33 @@
-- V15: Managed File Storage
-- Adds server-side content-addressable storage for uploaded files
-- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage)
ALTER TABLE media_items ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
ALTER TABLE media_items
ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
-- Original filename for managed uploads (preserved separately from file_name which may be normalized)
ALTER TABLE media_items ADD COLUMN original_filename TEXT;
ALTER TABLE media_items
ADD COLUMN original_filename TEXT;
-- When the file was uploaded to managed storage
ALTER TABLE media_items ADD COLUMN uploaded_at TIMESTAMPTZ;
ALTER TABLE media_items
ADD COLUMN uploaded_at TIMESTAMPTZ;
-- Storage key for looking up the blob (usually same as content_hash for deduplication)
ALTER TABLE media_items ADD COLUMN storage_key TEXT;
ALTER TABLE media_items
ADD COLUMN storage_key TEXT;
-- Managed blobs table - tracks deduplicated file storage
CREATE TABLE managed_blobs (
content_hash TEXT PRIMARY KEY NOT NULL,
file_size BIGINT NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TIMESTAMPTZ NOT NULL,
last_verified TIMESTAMPTZ
content_hash TEXT PRIMARY KEY NOT NULL,
file_size BIGINT NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TIMESTAMPTZ NOT NULL,
last_verified TIMESTAMPTZ
);
-- Index for finding managed media items
CREATE INDEX idx_media_storage_mode ON media_items(storage_mode);
CREATE INDEX idx_media_storage_mode ON media_items (storage_mode);
-- Index for finding orphaned blobs (reference_count = 0)
CREATE INDEX idx_blobs_reference_count ON managed_blobs(reference_count);
CREATE INDEX idx_blobs_reference_count ON managed_blobs (reference_count);

View file

@ -1,91 +1,100 @@
-- V16: Cross-Device Sync System
-- Adds device registration, change tracking, and chunked upload support
-- Sync devices table
CREATE TABLE sync_devices (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TIMESTAMPTZ,
last_seen_at TIMESTAMPTZ NOT NULL,
sync_cursor BIGINT DEFAULT 0,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TIMESTAMPTZ,
last_seen_at TIMESTAMPTZ NOT NULL,
sync_cursor BIGINT DEFAULT 0,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_sync_devices_user ON sync_devices(user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices(device_token_hash);
CREATE INDEX idx_sync_devices_user ON sync_devices (user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices (device_token_hash);
-- Sync log table - tracks all changes for sync
CREATE TABLE sync_log (
id TEXT PRIMARY KEY NOT NULL,
sequence BIGSERIAL UNIQUE NOT NULL,
change_type TEXT NOT NULL,
media_id TEXT REFERENCES media_items(id) ON DELETE SET NULL,
path TEXT NOT NULL,
content_hash TEXT,
file_size BIGINT,
metadata_json TEXT,
changed_by_device TEXT REFERENCES sync_devices(id) ON DELETE SET NULL,
timestamp TIMESTAMPTZ NOT NULL
id TEXT PRIMARY KEY NOT NULL,
sequence BIGSERIAL UNIQUE NOT NULL,
change_type TEXT NOT NULL,
media_id TEXT REFERENCES media_items (id) ON DELETE SET NULL,
path TEXT NOT NULL,
content_hash TEXT,
file_size BIGINT,
metadata_json TEXT,
changed_by_device TEXT REFERENCES sync_devices (id) ON DELETE SET NULL,
timestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_sync_log_sequence ON sync_log(sequence);
CREATE INDEX idx_sync_log_path ON sync_log(path);
CREATE INDEX idx_sync_log_timestamp ON sync_log(timestamp);
CREATE INDEX idx_sync_log_sequence ON sync_log (sequence);
CREATE INDEX idx_sync_log_path ON sync_log (path);
CREATE INDEX idx_sync_log_timestamp ON sync_log (timestamp);
-- Sequence counter for sync log
CREATE TABLE sync_sequence (
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value BIGINT NOT NULL DEFAULT 0
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value BIGINT NOT NULL DEFAULT 0
);
INSERT INTO sync_sequence (id, current_value) VALUES (1, 0);
INSERT INTO
sync_sequence (id, current_value)
VALUES
(1, 0);
-- Device sync state - tracks sync status per device per file
CREATE TABLE device_sync_state (
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime BIGINT,
server_mtime BIGINT,
sync_status TEXT NOT NULL,
last_synced_at TIMESTAMPTZ,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path)
device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime BIGINT,
server_mtime BIGINT,
sync_status TEXT NOT NULL,
last_synced_at TIMESTAMPTZ,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path)
);
CREATE INDEX idx_device_sync_status ON device_sync_state(device_id, sync_status);
CREATE INDEX idx_device_sync_status ON device_sync_state (device_id, sync_status);
-- Upload sessions for chunked uploads
CREATE TABLE upload_sessions (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size BIGINT NOT NULL,
chunk_size BIGINT NOT NULL,
chunk_count BIGINT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
last_activity TIMESTAMPTZ NOT NULL
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size BIGINT NOT NULL,
chunk_size BIGINT NOT NULL,
chunk_count BIGINT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
last_activity TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_upload_sessions_device ON upload_sessions(device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions(status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions(expires_at);
CREATE INDEX idx_upload_sessions_device ON upload_sessions (device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions (status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions (expires_at);
-- Upload chunks - tracks received chunks
CREATE TABLE upload_chunks (
upload_id TEXT NOT NULL REFERENCES upload_sessions(id) ON DELETE CASCADE,
chunk_index BIGINT NOT NULL,
offset BIGINT NOT NULL,
upload_id TEXT NOT NULL REFERENCES upload_sessions (id) ON DELETE CASCADE,
chunk_index BIGINT NOT NULL,
offset
BIGINT NOT NULL,
size BIGINT NOT NULL,
hash TEXT NOT NULL,
received_at TIMESTAMPTZ NOT NULL,
@ -94,17 +103,20 @@ CREATE TABLE upload_chunks (
-- Sync conflicts
CREATE TABLE sync_conflicts (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime BIGINT NOT NULL,
server_hash TEXT NOT NULL,
server_mtime BIGINT NOT NULL,
detected_at TIMESTAMPTZ NOT NULL,
resolved_at TIMESTAMPTZ,
resolution TEXT
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime BIGINT NOT NULL,
server_hash TEXT NOT NULL,
server_mtime BIGINT NOT NULL,
detected_at TIMESTAMPTZ NOT NULL,
resolved_at TIMESTAMPTZ,
resolution TEXT
);
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts(device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts(device_id) WHERE resolved_at IS NULL;
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts (device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts (device_id)
WHERE
resolved_at IS NULL;

View file

@ -1,68 +1,85 @@
-- V17: Enhanced Sharing System
-- Replaces simple share_links with comprehensive sharing capabilities
-- Enhanced shares table
CREATE TABLE shares (
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('media', 'collection', 'tag', 'saved_search')),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('public_link', 'user', 'group', 'federated')),
recipient_user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view BOOLEAN NOT NULL DEFAULT TRUE,
perm_download BOOLEAN NOT NULL DEFAULT FALSE,
perm_edit BOOLEAN NOT NULL DEFAULT FALSE,
perm_delete BOOLEAN NOT NULL DEFAULT FALSE,
perm_reshare BOOLEAN NOT NULL DEFAULT FALSE,
perm_add BOOLEAN NOT NULL DEFAULT FALSE,
note TEXT,
expires_at TIMESTAMPTZ,
access_count BIGINT NOT NULL DEFAULT 0,
last_accessed TIMESTAMPTZ,
inherit_to_children BOOLEAN NOT NULL DEFAULT TRUE,
parent_share_id TEXT REFERENCES shares(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
UNIQUE(owner_id, target_type, target_id, recipient_type, recipient_user_id)
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (
target_type IN ('media', 'collection', 'tag', 'saved_search')
),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
recipient_type TEXT NOT NULL CHECK (
recipient_type IN ('public_link', 'user', 'group', 'federated')
),
recipient_user_id TEXT REFERENCES users (id) ON DELETE CASCADE,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view BOOLEAN NOT NULL DEFAULT TRUE,
perm_download BOOLEAN NOT NULL DEFAULT FALSE,
perm_edit BOOLEAN NOT NULL DEFAULT FALSE,
perm_delete BOOLEAN NOT NULL DEFAULT FALSE,
perm_reshare BOOLEAN NOT NULL DEFAULT FALSE,
perm_add BOOLEAN NOT NULL DEFAULT FALSE,
note TEXT,
expires_at TIMESTAMPTZ,
access_count BIGINT NOT NULL DEFAULT 0,
last_accessed TIMESTAMPTZ,
inherit_to_children BOOLEAN NOT NULL DEFAULT TRUE,
parent_share_id TEXT REFERENCES shares (id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
UNIQUE (
owner_id,
target_type,
target_id,
recipient_type,
recipient_user_id
)
);
CREATE INDEX idx_shares_owner ON shares(owner_id);
CREATE INDEX idx_shares_recipient_user ON shares(recipient_user_id);
CREATE INDEX idx_shares_target ON shares(target_type, target_id);
CREATE INDEX idx_shares_token ON shares(public_token);
CREATE INDEX idx_shares_expires ON shares(expires_at);
CREATE INDEX idx_shares_owner ON shares (owner_id);
CREATE INDEX idx_shares_recipient_user ON shares (recipient_user_id);
CREATE INDEX idx_shares_target ON shares (target_type, target_id);
CREATE INDEX idx_shares_token ON shares (public_token);
CREATE INDEX idx_shares_expires ON shares (expires_at);
-- Share activity log
CREATE TABLE share_activity (
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL REFERENCES shares(id) ON DELETE CASCADE,
actor_id TEXT REFERENCES users(id) ON DELETE SET NULL,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TIMESTAMPTZ NOT NULL
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL REFERENCES shares (id) ON DELETE CASCADE,
actor_id TEXT REFERENCES users (id) ON DELETE SET NULL,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_share_activity_share ON share_activity(share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity(timestamp);
CREATE INDEX idx_share_activity_share ON share_activity (share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity (timestamp);
-- Share notifications
CREATE TABLE share_notifications (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
share_id TEXT NOT NULL REFERENCES shares(id) ON DELETE CASCADE,
notification_type TEXT NOT NULL,
is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
share_id TEXT NOT NULL REFERENCES shares (id) ON DELETE CASCADE,
notification_type TEXT NOT NULL,
is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX idx_share_notifications_user ON share_notifications(user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications(user_id) WHERE is_read = FALSE;
CREATE INDEX idx_share_notifications_user ON share_notifications (user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications (user_id)
WHERE
is_read = FALSE;
-- Migrate existing share_links to new shares table
DO $$

View file

@ -1,11 +1,13 @@
-- V18: File Management (Rename, Move, Trash)
-- Adds soft delete support for trash/recycle bin functionality
-- Add deleted_at column for soft delete (trash)
ALTER TABLE media_items ADD COLUMN deleted_at TIMESTAMPTZ;
ALTER TABLE media_items
ADD COLUMN deleted_at TIMESTAMPTZ;
-- Index for efficient trash queries
CREATE INDEX idx_media_deleted_at ON media_items(deleted_at);
CREATE INDEX idx_media_deleted_at ON media_items (deleted_at);
-- Partial index for listing non-deleted items (most common query pattern)
CREATE INDEX idx_media_not_deleted ON media_items(id) WHERE deleted_at IS NULL;
CREATE INDEX idx_media_not_deleted ON media_items (id)
WHERE
deleted_at IS NULL;

View file

@ -1,35 +1,35 @@
-- V19: Markdown Links (Obsidian-style bidirectional links)
-- Adds support for wikilinks, markdown links, embeds, and backlink tracking
-- Table for storing extracted markdown links
CREATE TABLE IF NOT EXISTS markdown_links (
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TIMESTAMPTZ NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TIMESTAMPTZ NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
-- Index for efficient outgoing link queries (what does this note link to?)
CREATE INDEX idx_links_source ON markdown_links(source_media_id);
CREATE INDEX idx_links_source ON markdown_links (source_media_id);
-- Index for efficient backlink queries (what links to this note?)
CREATE INDEX idx_links_target ON markdown_links(target_media_id);
CREATE INDEX idx_links_target ON markdown_links (target_media_id);
-- Index for path-based resolution (finding unresolved links)
CREATE INDEX idx_links_target_path ON markdown_links(target_path);
CREATE INDEX idx_links_target_path ON markdown_links (target_path);
-- Index for link type filtering
CREATE INDEX idx_links_type ON markdown_links(link_type);
CREATE INDEX idx_links_type ON markdown_links (link_type);
-- Track when links were last extracted from a media item
ALTER TABLE media_items ADD COLUMN links_extracted_at TIMESTAMPTZ;
ALTER TABLE media_items
ADD COLUMN links_extracted_at TIMESTAMPTZ;
-- Index for finding media items that need link extraction
CREATE INDEX idx_media_links_extracted ON media_items(links_extracted_at);
CREATE INDEX idx_media_links_extracted ON media_items (links_extracted_at);

View file

@ -1,73 +1,75 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE TABLE IF NOT EXISTS root_dirs (
path TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS root_dirs (path TEXT PRIMARY KEY NOT NULL);
CREATE TABLE IF NOT EXISTS media_items (
id UUID PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size BIGINT NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs DOUBLE PRECISION,
description TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
id UUID PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size BIGINT NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs DOUBLE PRECISION,
description TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE IF NOT EXISTS tags (
id UUID PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id UUID REFERENCES tags(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL
id UUID PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id UUID REFERENCES tags (id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags(name, COALESCE(parent_id, '00000000-0000-0000-0000-000000000000'));
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags (
name,
COALESCE(parent_id, '00000000-0000-0000-0000-000000000000')
);
CREATE TABLE IF NOT EXISTS media_tags (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (media_id, tag_id)
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags (id) ON DELETE CASCADE,
PRIMARY KEY (media_id, tag_id)
);
CREATE TABLE IF NOT EXISTS collections (
id UUID PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
id UUID PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE IF NOT EXISTS collection_members (
collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (collection_id, media_id)
collection_id UUID NOT NULL REFERENCES collections (id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (collection_id, media_id)
);
CREATE TABLE IF NOT EXISTS audit_log (
id UUID PRIMARY KEY NOT NULL,
media_id UUID REFERENCES media_items(id) ON DELETE SET NULL,
action TEXT NOT NULL,
details TEXT,
timestamp TIMESTAMPTZ NOT NULL
id UUID PRIMARY KEY NOT NULL,
media_id UUID REFERENCES media_items (id) ON DELETE SET NULL,
action TEXT NOT NULL,
details TEXT,
timestamp TIMESTAMPTZ NOT NULL
);
CREATE TABLE IF NOT EXISTS custom_fields (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name)
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name)
);

View file

@ -1,11 +1,12 @@
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(artist, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(album, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(genre, '')), 'C') ||
setweight(to_tsvector('english', COALESCE(description, '')), 'C') ||
setweight(to_tsvector('english', COALESCE(file_name, '')), 'D')
) STORED;
ALTER TABLE media_items
ADD COLUMN IF NOT EXISTS search_vector tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('english', COALESCE(title, '')), 'A') || setweight(to_tsvector('english', COALESCE(artist, '')), 'B') || setweight(to_tsvector('english', COALESCE(album, '')), 'B') || setweight(to_tsvector('english', COALESCE(genre, '')), 'C') || setweight(
to_tsvector('english', COALESCE(description, '')),
'C'
) || setweight(
to_tsvector('english', COALESCE(file_name, '')),
'D'
)
) STORED;
CREATE INDEX IF NOT EXISTS idx_media_search ON media_items USING GIN(search_vector);
CREATE INDEX IF NOT EXISTS idx_media_search ON media_items USING GIN (search_vector);

View file

@ -1,8 +1,15 @@
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log(media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items(content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items(media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items(created_at);
CREATE INDEX IF NOT EXISTS idx_media_title_trgm ON media_items USING GIN(title gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_media_artist_trgm ON media_items USING GIN(artist gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log (media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log (timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log (action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items (content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items (media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items (created_at);
CREATE INDEX IF NOT EXISTS idx_media_title_trgm ON media_items USING GIN (title gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_media_artist_trgm ON media_items USING GIN (artist gin_trgm_ops);

View file

@ -1 +1,2 @@
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT;
ALTER TABLE media_items
ADD COLUMN thumbnail_path TEXT;

View file

@ -1,12 +1,15 @@
-- Integrity tracking columns
ALTER TABLE media_items ADD COLUMN last_verified_at TIMESTAMPTZ;
ALTER TABLE media_items ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
ALTER TABLE media_items
ADD COLUMN last_verified_at TIMESTAMPTZ;
ALTER TABLE media_items
ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
-- Saved searches
CREATE TABLE IF NOT EXISTS saved_searches (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

View file

@ -1,15 +1,16 @@
-- Plugin registry table
CREATE TABLE plugin_registry (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
-- Index for quick lookups
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry(enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry(name);
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry (enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry (name);

View file

@ -1,35 +1,37 @@
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
id UUID PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
-- User profiles table
CREATE TABLE user_profiles (
user_id UUID PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
user_id UUID PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- User library access table
CREATE TABLE user_libraries (
user_id UUID NOT NULL,
root_path TEXT NOT NULL,
permission JSONB NOT NULL,
granted_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users(id)
user_id UUID NOT NULL,
root_path TEXT NOT NULL,
permission JSONB NOT NULL,
granted_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- Indexes for efficient lookups
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries(user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries(root_path);
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries (user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries (root_path);

View file

@ -1,131 +1,136 @@
-- Ratings
CREATE TABLE IF NOT EXISTS ratings (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
stars INTEGER NOT NULL CHECK (stars >= 1 AND stars <= 5),
review_text TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, media_id)
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
stars INTEGER NOT NULL CHECK (
stars >= 1
AND stars <= 5
),
review_text TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, media_id)
);
-- Comments
CREATE TABLE IF NOT EXISTS comments (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES comments(id) ON DELETE CASCADE,
text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES comments (id) ON DELETE CASCADE,
text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Favorites
CREATE TABLE IF NOT EXISTS favorites (
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, media_id)
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, media_id)
);
-- Share links
CREATE TABLE IF NOT EXISTS share_links (
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
created_by UUID NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TIMESTAMPTZ,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
created_by UUID NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TIMESTAMPTZ,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Playlists
CREATE TABLE IF NOT EXISTS playlists (
id UUID PRIMARY KEY,
owner_id UUID NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public BOOLEAN NOT NULL DEFAULT FALSE,
is_smart BOOLEAN NOT NULL DEFAULT FALSE,
filter_query TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
owner_id UUID NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public BOOLEAN NOT NULL DEFAULT FALSE,
is_smart BOOLEAN NOT NULL DEFAULT FALSE,
filter_query TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Playlist items
CREATE TABLE IF NOT EXISTS playlist_items (
playlist_id UUID NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (playlist_id, media_id)
playlist_id UUID NOT NULL REFERENCES playlists (id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (playlist_id, media_id)
);
-- Usage events
CREATE TABLE IF NOT EXISTS usage_events (
id UUID PRIMARY KEY,
media_id UUID REFERENCES media_items(id) ON DELETE SET NULL,
user_id UUID,
event_type TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
duration_secs DOUBLE PRECISION,
context_json JSONB
id UUID PRIMARY KEY,
media_id UUID REFERENCES media_items (id) ON DELETE SET NULL,
user_id UUID,
event_type TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
duration_secs DOUBLE PRECISION,
context_json JSONB
);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events(media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events(timestamp);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events (media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events (user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events (timestamp);
-- Watch history / progress
CREATE TABLE IF NOT EXISTS watch_history (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
progress_secs DOUBLE PRECISION NOT NULL DEFAULT 0,
last_watched TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, media_id)
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
progress_secs DOUBLE PRECISION NOT NULL DEFAULT 0,
last_watched TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, media_id)
);
-- Subtitles
CREATE TABLE IF NOT EXISTS subtitles (
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded BOOLEAN NOT NULL DEFAULT FALSE,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded BOOLEAN NOT NULL DEFAULT FALSE,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles(media_id);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles (media_id);
-- External metadata (enrichment)
CREATE TABLE IF NOT EXISTS external_metadata (
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
source TEXT NOT NULL,
external_id TEXT,
metadata_json JSONB NOT NULL DEFAULT '{}',
confidence DOUBLE PRECISION NOT NULL DEFAULT 0.0,
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
source TEXT NOT NULL,
external_id TEXT,
metadata_json JSONB NOT NULL DEFAULT '{}',
confidence DOUBLE PRECISION NOT NULL DEFAULT 0.0,
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata(media_id);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata (media_id);
-- Transcode sessions
CREATE TABLE IF NOT EXISTS transcode_sessions (
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress DOUBLE PRECISION NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ
id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
user_id UUID,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress DOUBLE PRECISION NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions(media_id);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions (media_id);

View file

@ -1,19 +1,26 @@
-- Drop redundant indexes (already covered by UNIQUE constraints)
DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_user_libraries_user_id;
-- Add missing indexes for comments table
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments(media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_comment_id);
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments (media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_comment_id);
-- Remove duplicates before adding unique constraint
DELETE FROM external_metadata e1
WHERE EXISTS (
SELECT 1 FROM external_metadata e2
WHERE e1.media_id = e2.media_id
AND e1.source = e2.source
AND e1.ctid < e2.ctid
);
WHERE
EXISTS (
SELECT
1
FROM
external_metadata e2
WHERE
e1.media_id = e2.media_id
AND e1.source = e2.source
AND e1.ctid < e2.ctid
);
-- Add unique constraint for external_metadata (idempotent)
DO $$

View file

@ -1,19 +1,19 @@
-- Add file_mtime column to media_items table for incremental scanning
-- Stores Unix timestamp in seconds of the file's modification time
ALTER TABLE media_items ADD COLUMN file_mtime INTEGER;
ALTER TABLE media_items
ADD COLUMN file_mtime INTEGER;
-- Create index for quick mtime lookups
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items(file_mtime);
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items (file_mtime);
-- Create a scan_history table to track when each directory was last scanned
CREATE TABLE IF NOT EXISTS scan_history (
id TEXT PRIMARY KEY,
directory TEXT NOT NULL UNIQUE,
last_scan_at TEXT NOT NULL,
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
id TEXT PRIMARY KEY,
directory TEXT NOT NULL UNIQUE,
last_scan_at TEXT NOT NULL,
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history(directory);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history (directory);

View file

@ -1,18 +1,17 @@
-- Session persistence for database-backed sessions
-- Replaces in-memory session storage
CREATE TABLE IF NOT EXISTS sessions (
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_accessed TEXT NOT NULL
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_accessed TEXT NOT NULL
);
-- Index for efficient cleanup of expired sessions
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions (expires_at);
-- Index for listing sessions by username
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions (username);

View file

@ -1,54 +1,62 @@
-- V12: Book Management Schema
-- Adds comprehensive book metadata tracking, authors, and identifiers
-- Book metadata (supplements media_items for EPUB/PDF/MOBI)
CREATE TABLE book_metadata (
media_id TEXT PRIMARY KEY REFERENCES media_items(id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date TEXT, -- ISO 8601 date string
series_name TEXT,
series_index REAL, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
media_id TEXT PRIMARY KEY REFERENCES media_items (id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date TEXT, -- ISO 8601 date string
series_name TEXT,
series_index REAL, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
) STRICT;
CREATE INDEX idx_book_isbn13 ON book_metadata(isbn13);
CREATE INDEX idx_book_series ON book_metadata(series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata(publisher);
CREATE INDEX idx_book_language ON book_metadata(language);
CREATE INDEX idx_book_isbn13 ON book_metadata (isbn13);
CREATE INDEX idx_book_series ON book_metadata (series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata (publisher);
CREATE INDEX idx_book_language ON book_metadata (language);
-- Multiple authors per book (many-to-many)
CREATE TABLE book_authors (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
) STRICT;
CREATE INDEX idx_book_authors_name ON book_authors(author_name);
CREATE INDEX idx_book_authors_sort ON book_authors(author_sort);
CREATE INDEX idx_book_authors_name ON book_authors (author_name);
CREATE INDEX idx_book_authors_sort ON book_authors (author_sort);
-- Multiple identifiers (ISBN variants, ASIN, DOI, etc.)
CREATE TABLE book_identifiers (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
) STRICT;
CREATE INDEX idx_book_identifiers ON book_identifiers(identifier_type, identifier_value);
CREATE INDEX idx_book_identifiers ON book_identifiers (identifier_type, identifier_value);
-- Trigger to update updated_at on book_metadata changes
CREATE TRIGGER update_book_metadata_timestamp
AFTER UPDATE ON book_metadata
FOR EACH ROW
AFTER
UPDATE ON book_metadata FOR EACH ROW
BEGIN
UPDATE book_metadata SET updated_at = datetime('now') WHERE media_id = NEW.media_id;
UPDATE book_metadata
SET
updated_at = datetime ('now')
WHERE
media_id = NEW.media_id;
END;

View file

@ -1,15 +1,40 @@
-- V13: Enhanced photo metadata support
-- Add photo-specific fields to media_items table
ALTER TABLE media_items
ADD COLUMN date_taken TIMESTAMP;
ALTER TABLE media_items ADD COLUMN date_taken TIMESTAMP;
ALTER TABLE media_items ADD COLUMN latitude REAL;
ALTER TABLE media_items ADD COLUMN longitude REAL;
ALTER TABLE media_items ADD COLUMN camera_make TEXT;
ALTER TABLE media_items ADD COLUMN camera_model TEXT;
ALTER TABLE media_items ADD COLUMN rating INTEGER CHECK (rating >= 0 AND rating <= 5);
ALTER TABLE media_items
ADD COLUMN latitude REAL;
ALTER TABLE media_items
ADD COLUMN longitude REAL;
ALTER TABLE media_items
ADD COLUMN camera_make TEXT;
ALTER TABLE media_items
ADD COLUMN camera_model TEXT;
ALTER TABLE media_items
ADD COLUMN rating INTEGER CHECK (
rating >= 0
AND rating <= 5
);
-- Indexes for photo queries
CREATE INDEX idx_media_date_taken ON media_items(date_taken) WHERE date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items(latitude, longitude) WHERE latitude IS NOT NULL AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items(camera_make) WHERE camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items(rating) WHERE rating IS NOT NULL;
CREATE INDEX idx_media_date_taken ON media_items (date_taken)
WHERE
date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items (latitude, longitude)
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items (camera_make)
WHERE
camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items (rating)
WHERE
rating IS NOT NULL;

View file

@ -1,7 +1,9 @@
-- V14: Perceptual hash for duplicate detection
-- Add perceptual hash column for image similarity detection
ALTER TABLE media_items ADD COLUMN perceptual_hash TEXT;
ALTER TABLE media_items
ADD COLUMN perceptual_hash TEXT;
-- Index for perceptual hash lookups
CREATE INDEX idx_media_phash ON media_items(perceptual_hash) WHERE perceptual_hash IS NOT NULL;
CREATE INDEX idx_media_phash ON media_items (perceptual_hash)
WHERE
perceptual_hash IS NOT NULL;

View file

@ -1,30 +1,33 @@
-- V15: Managed File Storage
-- Adds server-side content-addressable storage for uploaded files
-- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage)
ALTER TABLE media_items ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
ALTER TABLE media_items
ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
-- Original filename for managed uploads (preserved separately from file_name which may be normalized)
ALTER TABLE media_items ADD COLUMN original_filename TEXT;
ALTER TABLE media_items
ADD COLUMN original_filename TEXT;
-- When the file was uploaded to managed storage
ALTER TABLE media_items ADD COLUMN uploaded_at TEXT;
ALTER TABLE media_items
ADD COLUMN uploaded_at TEXT;
-- Storage key for looking up the blob (usually same as content_hash for deduplication)
ALTER TABLE media_items ADD COLUMN storage_key TEXT;
ALTER TABLE media_items
ADD COLUMN storage_key TEXT;
-- Managed blobs table - tracks deduplicated file storage
CREATE TABLE managed_blobs (
content_hash TEXT PRIMARY KEY NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TEXT NOT NULL,
last_verified TEXT
content_hash TEXT PRIMARY KEY NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TEXT NOT NULL,
last_verified TEXT
);
-- Index for finding managed media items
CREATE INDEX idx_media_storage_mode ON media_items(storage_mode);
CREATE INDEX idx_media_storage_mode ON media_items (storage_mode);
-- Index for finding orphaned blobs (reference_count = 0)
CREATE INDEX idx_blobs_reference_count ON managed_blobs(reference_count);
CREATE INDEX idx_blobs_reference_count ON managed_blobs (reference_count);

View file

@ -1,117 +1,129 @@
-- V16: Cross-Device Sync System
-- Adds device registration, change tracking, and chunked upload support
-- Sync devices table
CREATE TABLE sync_devices (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TEXT,
last_seen_at TEXT NOT NULL,
sync_cursor INTEGER DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TEXT,
last_seen_at TEXT NOT NULL,
sync_cursor INTEGER DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE INDEX idx_sync_devices_user ON sync_devices(user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices(device_token_hash);
CREATE INDEX idx_sync_devices_user ON sync_devices (user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices (device_token_hash);
-- Sync log table - tracks all changes for sync
CREATE TABLE sync_log (
id TEXT PRIMARY KEY NOT NULL,
sequence INTEGER NOT NULL UNIQUE,
change_type TEXT NOT NULL,
media_id TEXT,
path TEXT NOT NULL,
content_hash TEXT,
file_size INTEGER,
metadata_json TEXT,
changed_by_device TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL,
FOREIGN KEY (changed_by_device) REFERENCES sync_devices(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
sequence INTEGER NOT NULL UNIQUE,
change_type TEXT NOT NULL,
media_id TEXT,
path TEXT NOT NULL,
content_hash TEXT,
file_size INTEGER,
metadata_json TEXT,
changed_by_device TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL,
FOREIGN KEY (changed_by_device) REFERENCES sync_devices (id) ON DELETE SET NULL
);
CREATE INDEX idx_sync_log_sequence ON sync_log(sequence);
CREATE INDEX idx_sync_log_path ON sync_log(path);
CREATE INDEX idx_sync_log_timestamp ON sync_log(timestamp);
CREATE INDEX idx_sync_log_sequence ON sync_log (sequence);
CREATE INDEX idx_sync_log_path ON sync_log (path);
CREATE INDEX idx_sync_log_timestamp ON sync_log (timestamp);
-- Sequence counter for sync log
CREATE TABLE sync_sequence (
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value INTEGER NOT NULL DEFAULT 0
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value INTEGER NOT NULL DEFAULT 0
);
INSERT INTO sync_sequence (id, current_value) VALUES (1, 0);
INSERT INTO
sync_sequence (id, current_value)
VALUES
(1, 0);
-- Device sync state - tracks sync status per device per file
CREATE TABLE device_sync_state (
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime INTEGER,
server_mtime INTEGER,
sync_status TEXT NOT NULL,
last_synced_at TEXT,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path),
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime INTEGER,
server_mtime INTEGER,
sync_status TEXT NOT NULL,
last_synced_at TEXT,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path),
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_device_sync_status ON device_sync_state(device_id, sync_status);
CREATE INDEX idx_device_sync_status ON device_sync_state (device_id, sync_status);
-- Upload sessions for chunked uploads
CREATE TABLE upload_sessions (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size INTEGER NOT NULL,
chunk_size INTEGER NOT NULL,
chunk_count INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_activity TEXT NOT NULL,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size INTEGER NOT NULL,
chunk_size INTEGER NOT NULL,
chunk_count INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_activity TEXT NOT NULL,
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_upload_sessions_device ON upload_sessions(device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions(status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions(expires_at);
CREATE INDEX idx_upload_sessions_device ON upload_sessions (device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions (status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions (expires_at);
-- Upload chunks - tracks received chunks
CREATE TABLE upload_chunks (
upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
offset INTEGER NOT NULL,
upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
offset
INTEGER NOT NULL,
size INTEGER NOT NULL,
hash TEXT NOT NULL,
received_at TEXT NOT NULL,
PRIMARY KEY (upload_id, chunk_index),
FOREIGN KEY (upload_id) REFERENCES upload_sessions(id) ON DELETE CASCADE
FOREIGN KEY (upload_id) REFERENCES upload_sessions (id) ON DELETE CASCADE
);
-- Sync conflicts
CREATE TABLE sync_conflicts (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime INTEGER NOT NULL,
server_hash TEXT NOT NULL,
server_mtime INTEGER NOT NULL,
detected_at TEXT NOT NULL,
resolved_at TEXT,
resolution TEXT,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime INTEGER NOT NULL,
server_hash TEXT NOT NULL,
server_mtime INTEGER NOT NULL,
detected_at TEXT NOT NULL,
resolved_at TEXT,
resolution TEXT,
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts(device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts(device_id, resolved_at) WHERE resolved_at IS NULL;
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts (device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts (device_id, resolved_at)
WHERE
resolved_at IS NULL;

View file

@ -1,85 +1,133 @@
-- V17: Enhanced Sharing System
-- Replaces simple share_links with comprehensive sharing capabilities
-- Enhanced shares table
CREATE TABLE shares (
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('media', 'collection', 'tag', 'saved_search')),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('public_link', 'user', 'group', 'federated')),
recipient_user_id TEXT,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view INTEGER NOT NULL DEFAULT 1,
perm_download INTEGER NOT NULL DEFAULT 0,
perm_edit INTEGER NOT NULL DEFAULT 0,
perm_delete INTEGER NOT NULL DEFAULT 0,
perm_reshare INTEGER NOT NULL DEFAULT 0,
perm_add INTEGER NOT NULL DEFAULT 0,
note TEXT,
expires_at TEXT,
access_count INTEGER NOT NULL DEFAULT 0,
last_accessed TEXT,
inherit_to_children INTEGER NOT NULL DEFAULT 1,
parent_share_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (recipient_user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (parent_share_id) REFERENCES shares(id) ON DELETE CASCADE,
UNIQUE(owner_id, target_type, target_id, recipient_type, recipient_user_id)
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (
target_type IN ('media', 'collection', 'tag', 'saved_search')
),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL,
recipient_type TEXT NOT NULL CHECK (
recipient_type IN ('public_link', 'user', 'group', 'federated')
),
recipient_user_id TEXT,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view INTEGER NOT NULL DEFAULT 1,
perm_download INTEGER NOT NULL DEFAULT 0,
perm_edit INTEGER NOT NULL DEFAULT 0,
perm_delete INTEGER NOT NULL DEFAULT 0,
perm_reshare INTEGER NOT NULL DEFAULT 0,
perm_add INTEGER NOT NULL DEFAULT 0,
note TEXT,
expires_at TEXT,
access_count INTEGER NOT NULL DEFAULT 0,
last_accessed TEXT,
inherit_to_children INTEGER NOT NULL DEFAULT 1,
parent_share_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (recipient_user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (parent_share_id) REFERENCES shares (id) ON DELETE CASCADE,
UNIQUE (
owner_id,
target_type,
target_id,
recipient_type,
recipient_user_id
)
);
CREATE INDEX idx_shares_owner ON shares(owner_id);
CREATE INDEX idx_shares_recipient_user ON shares(recipient_user_id);
CREATE INDEX idx_shares_target ON shares(target_type, target_id);
CREATE INDEX idx_shares_token ON shares(public_token);
CREATE INDEX idx_shares_expires ON shares(expires_at);
CREATE INDEX idx_shares_owner ON shares (owner_id);
CREATE INDEX idx_shares_recipient_user ON shares (recipient_user_id);
CREATE INDEX idx_shares_target ON shares (target_type, target_id);
CREATE INDEX idx_shares_token ON shares (public_token);
CREATE INDEX idx_shares_expires ON shares (expires_at);
-- Share activity log
CREATE TABLE share_activity (
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL,
actor_id TEXT,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL,
actor_id TEXT,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE INDEX idx_share_activity_share ON share_activity(share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity(timestamp);
CREATE INDEX idx_share_activity_share ON share_activity (share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity (timestamp);
-- Share notifications
CREATE TABLE share_notifications (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
share_id TEXT NOT NULL,
notification_type TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
share_id TEXT NOT NULL,
notification_type TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE
);
CREATE INDEX idx_share_notifications_user ON share_notifications(user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications(user_id, is_read) WHERE is_read = 0;
CREATE INDEX idx_share_notifications_user ON share_notifications (user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications (user_id, is_read)
WHERE
is_read = 0;
-- Migrate existing share_links to new shares table (if share_links exists)
INSERT OR IGNORE INTO shares (
id, target_type, target_id, owner_id, recipient_type,
public_token, public_password_hash, perm_view, perm_download,
access_count, expires_at, created_at, updated_at
INSERT
OR IGNORE INTO shares (
id,
target_type,
target_id,
owner_id,
recipient_type,
public_token,
public_password_hash,
perm_view,
perm_download,
access_count,
expires_at,
created_at,
updated_at
)
SELECT
id, 'media', media_id, created_by, 'public_link',
token, password_hash, 1, 1,
view_count, expires_at, created_at, created_at
FROM share_links
WHERE EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='share_links');
id,
'media',
media_id,
created_by,
'public_link',
token,
password_hash,
1,
1,
view_count,
expires_at,
created_at,
created_at
FROM
share_links
WHERE
EXISTS (
SELECT
1
FROM
sqlite_master
WHERE
type = 'table'
AND name = 'share_links'
);

View file

@ -1,11 +1,13 @@
-- V18: File Management (Rename, Move, Trash)
-- Adds soft delete support for trash/recycle bin functionality
-- Add deleted_at column for soft delete (trash)
ALTER TABLE media_items ADD COLUMN deleted_at TEXT;
ALTER TABLE media_items
ADD COLUMN deleted_at TEXT;
-- Index for efficient trash queries
CREATE INDEX idx_media_deleted_at ON media_items(deleted_at);
CREATE INDEX idx_media_deleted_at ON media_items (deleted_at);
-- Index for listing non-deleted items (most common query pattern)
CREATE INDEX idx_media_not_deleted ON media_items(id) WHERE deleted_at IS NULL;
CREATE INDEX idx_media_not_deleted ON media_items (id)
WHERE
deleted_at IS NULL;

View file

@ -1,35 +1,35 @@
-- V19: Markdown Links (Obsidian-style bidirectional links)
-- Adds support for wikilinks, markdown links, embeds, and backlink tracking
-- Table for storing extracted markdown links
CREATE TABLE IF NOT EXISTS markdown_links (
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TEXT NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TEXT NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
-- Index for efficient outgoing link queries (what does this note link to?)
CREATE INDEX idx_links_source ON markdown_links(source_media_id);
CREATE INDEX idx_links_source ON markdown_links (source_media_id);
-- Index for efficient backlink queries (what links to this note?)
CREATE INDEX idx_links_target ON markdown_links(target_media_id);
CREATE INDEX idx_links_target ON markdown_links (target_media_id);
-- Index for path-based resolution (finding unresolved links)
CREATE INDEX idx_links_target_path ON markdown_links(target_path);
CREATE INDEX idx_links_target_path ON markdown_links (target_path);
-- Index for link type filtering
CREATE INDEX idx_links_type ON markdown_links(link_type);
CREATE INDEX idx_links_type ON markdown_links (link_type);
-- Track when links were last extracted from a media item
ALTER TABLE media_items ADD COLUMN links_extracted_at TEXT;
ALTER TABLE media_items
ADD COLUMN links_extracted_at TEXT;
-- Index for finding media items that need link extraction
CREATE INDEX idx_media_links_extracted ON media_items(links_extracted_at);
CREATE INDEX idx_media_links_extracted ON media_items (links_extracted_at);

View file

@ -1,77 +1,75 @@
CREATE TABLE IF NOT EXISTS root_dirs (
path TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS root_dirs (path TEXT PRIMARY KEY NOT NULL);
CREATE TABLE IF NOT EXISTS media_items (
id TEXT PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size INTEGER NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs REAL,
description TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size INTEGER NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs REAL,
description TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES tags(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES tags (id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags(name, parent_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags (name, parent_id);
CREATE TABLE IF NOT EXISTS media_tags (
media_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (media_id, tag_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
media_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (media_id, tag_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS collections (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS collection_members (
collection_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL,
PRIMARY KEY (collection_id, media_id),
FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
collection_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL,
PRIMARY KEY (collection_id, media_id),
FOREIGN KEY (collection_id) REFERENCES collections (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS audit_log (
id TEXT PRIMARY KEY NOT NULL,
media_id TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
media_id TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS custom_fields (
media_id TEXT NOT NULL,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
media_id TEXT NOT NULL,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);

View file

@ -1,27 +1,114 @@
CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5(
CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5 (
title,
artist,
album,
genre,
description,
file_name,
content = 'media_items',
content_rowid = 'rowid'
);
CREATE TRIGGER IF NOT EXISTS media_fts_insert
AFTER INSERT ON media_items
BEGIN
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name,
content='media_items',
content_rowid='rowid'
);
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
CREATE TRIGGER IF NOT EXISTS media_fts_insert AFTER INSERT ON media_items BEGIN
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name)
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name);
END;
CREATE TRIGGER IF NOT EXISTS media_fts_update AFTER UPDATE ON media_items BEGIN
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name)
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name);
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name)
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name);
CREATE TRIGGER IF NOT EXISTS media_fts_update
AFTER
UPDATE ON media_items
BEGIN
INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
END;
CREATE TRIGGER IF NOT EXISTS media_fts_delete AFTER DELETE ON media_items BEGIN
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name)
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name);
CREATE TRIGGER IF NOT EXISTS media_fts_delete
AFTER DELETE ON media_items
BEGIN
INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
END;

View file

@ -1,6 +1,11 @@
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log(media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items(content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items(media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log (media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log (timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log (action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items (content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items (media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items (created_at);

View file

@ -1 +1,2 @@
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT;
ALTER TABLE media_items
ADD COLUMN thumbnail_path TEXT;

View file

@ -1,12 +1,15 @@
-- Integrity tracking columns
ALTER TABLE media_items ADD COLUMN last_verified_at TEXT;
ALTER TABLE media_items ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
ALTER TABLE media_items
ADD COLUMN last_verified_at TEXT;
ALTER TABLE media_items
ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
-- Saved searches
CREATE TABLE IF NOT EXISTS saved_searches (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TEXT NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TEXT NOT NULL
);

View file

@ -1,15 +1,16 @@
-- Plugin registry table
CREATE TABLE plugin_registry (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- Index for quick lookups
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry(enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry(name);
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry (enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry (name);

View file

@ -1,35 +1,37 @@
-- Users table
CREATE TABLE users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- User profiles table
CREATE TABLE user_profiles (
user_id TEXT PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
user_id TEXT PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- User library access table
CREATE TABLE user_libraries (
user_id TEXT NOT NULL,
root_path TEXT NOT NULL,
permission TEXT NOT NULL,
granted_at TEXT NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users(id)
user_id TEXT NOT NULL,
root_path TEXT NOT NULL,
permission TEXT NOT NULL,
granted_at TEXT NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- Indexes for efficient lookups
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries(user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries(root_path);
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries (user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries (root_path);

View file

@ -1,143 +1,148 @@
-- Ratings
CREATE TABLE IF NOT EXISTS ratings (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
stars INTEGER NOT NULL CHECK (stars >= 1 AND stars <= 5),
review_text TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
stars INTEGER NOT NULL CHECK (
stars >= 1
AND stars <= 5
),
review_text TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Comments
CREATE TABLE IF NOT EXISTS comments (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
parent_comment_id TEXT,
text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (parent_comment_id) REFERENCES comments(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
parent_comment_id TEXT,
text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (parent_comment_id) REFERENCES comments (id) ON DELETE CASCADE
);
-- Favorites
CREATE TABLE IF NOT EXISTS favorites (
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Share links
CREATE TABLE IF NOT EXISTS share_links (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
created_by TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TEXT,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
created_by TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TEXT,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Playlists
CREATE TABLE IF NOT EXISTS playlists (
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public INTEGER NOT NULL DEFAULT 0,
is_smart INTEGER NOT NULL DEFAULT 0,
filter_query TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public INTEGER NOT NULL DEFAULT 0,
is_smart INTEGER NOT NULL DEFAULT 0,
filter_query TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
);
-- Playlist items
CREATE TABLE IF NOT EXISTS playlist_items (
playlist_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (playlist_id, media_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
playlist_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (playlist_id, media_id),
FOREIGN KEY (playlist_id) REFERENCES playlists (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Usage events
CREATE TABLE IF NOT EXISTS usage_events (
id TEXT PRIMARY KEY,
media_id TEXT,
user_id TEXT,
event_type TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
duration_secs REAL,
context_json TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY,
media_id TEXT,
user_id TEXT,
event_type TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime ('now')),
duration_secs REAL,
context_json TEXT,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events(media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events(timestamp);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events (media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events (user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events (timestamp);
-- Watch history / progress
CREATE TABLE IF NOT EXISTS watch_history (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
progress_secs REAL NOT NULL DEFAULT 0,
last_watched TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
progress_secs REAL NOT NULL DEFAULT 0,
last_watched TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Subtitles
CREATE TABLE IF NOT EXISTS subtitles (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded INTEGER NOT NULL DEFAULT 0,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded INTEGER NOT NULL DEFAULT 0,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles(media_id);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles (media_id);
-- External metadata (enrichment)
CREATE TABLE IF NOT EXISTS external_metadata (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
source TEXT NOT NULL,
external_id TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 0.0,
last_updated TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
source TEXT NOT NULL,
external_id TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 0.0,
last_updated TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata(media_id);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata (media_id);
-- Transcode sessions
CREATE TABLE IF NOT EXISTS transcode_sessions (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
user_id TEXT,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
user_id TEXT,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
expires_at TEXT,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions(media_id);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions (media_id);

View file

@ -1,18 +1,25 @@
-- Drop redundant indexes (already covered by UNIQUE constraints)
DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_user_libraries_user_id;
-- Add missing indexes for comments table
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments(media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_comment_id);
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments (media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_comment_id);
-- Remove duplicates before adding unique index (keep the first row)
DELETE FROM external_metadata
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM external_metadata
GROUP BY media_id, source
);
WHERE
rowid NOT IN (
SELECT
MIN(rowid)
FROM
external_metadata
GROUP BY
media_id,
source
);
-- Add unique index for external_metadata to prevent duplicates
CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata(media_id, source);
CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata (media_id, source);

View file

@ -68,9 +68,6 @@ in
echo "sccache setup complete!"
fi
# Start the daemon early for slightly faster startup.
"$SCCACHE_BIN" --start-server >/dev/null 2>&1 || true
'';
env = {