docs: restructure documentation into audience-based portals
BREAKING CHANGE: Documentation URLs have changed New structure: - user/ - End-user documentation (device setup, sync guides, frontend) - developer/ - Developer documentation (API reference, protocols, specs) - operations/ - Operations documentation (deployment, troubleshooting) - contributing/ - Contribution guides Changes: - Created portal INDEX.md files for each audience section - Moved device guides to user/devices/ (kobo-setup.md, koreader-setup.md) - Moved API docs to developer/ (api-reference.md, collections-api.md) - Moved sync guide to user/sync-guide.md - Moved troubleshooting to operations/troubleshooting.md - Moved all split API docs to developer/api/ - Renamed protocol files (kobo-protocol.md, koreader-protocol.md) - Added placeholder user guides (frontend, user-areas, settings, admin) - Updated all internal links to new paths - Updated Go code (http_handler.go, navigation.go) for new paths - Updated main INDEX.md for audience-based navigation Benefits: - Clear separation of user and developer documentation - Scalable structure for future user guide expansion - Better organization and discoverability - Audience-specific landing pages Related to DOCS_IMPLEMENTATION_PLAN.md Phase 2 completion
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# Developer Documentation
|
||||
|
||||
Welcome to the Bookhoard developer documentation. This section contains API references, protocol specifications, and technical documentation for integrating with Bookhoard.
|
||||
|
||||
## 📚 API Documentation
|
||||
|
||||
### Complete API Reference
|
||||
|
||||
- **[API Reference](api-reference.md)** - Complete REST API reference
|
||||
- Authentication
|
||||
- User management
|
||||
- Libraries
|
||||
- Media items
|
||||
- Reading progress
|
||||
- Notes & highlights
|
||||
- Analytics
|
||||
- Book matching
|
||||
- OPDS
|
||||
- Sync protocols (KOReader, Kobo)
|
||||
- WebSocket
|
||||
|
||||
### Split Endpoint Documentation
|
||||
|
||||
Browse individual endpoint documentation with interactive API Explorer:
|
||||
|
||||
- **[API Documentation](api/INDEX.md)** - Split endpoint docs
|
||||
- [Authentication](api/authentication/) - Register, login, tokens
|
||||
- [Users](api/users/) - Profile management
|
||||
- [Libraries](api/libraries/) - Library operations
|
||||
- [Media Items](api/media-items/) - Book/ebook management
|
||||
- [Progress](api/progress/) - Reading progress tracking
|
||||
- [Notes](api/notes/) - User notes
|
||||
- [Highlights](api/highlights/) - Book highlights
|
||||
- [Ratings](api/ratings/) - Book ratings
|
||||
- [Devices](api/devices/) - Device management
|
||||
- [Analytics](api/analytics/) - Usage statistics
|
||||
- [Book Matching](api/book-matching/) - Search and link books
|
||||
- [Collections](api/collections/) - Collection management
|
||||
- [OPDS](api/opds/) - Open Publication Distribution
|
||||
- [Sync Protocols](api/sync/) - KOReader and Kobo sync
|
||||
- [WebSocket](api/websocket/) - Real-time events
|
||||
|
||||
### Collections API
|
||||
|
||||
- **[Collections API](collections-api.md)** - Complete collections API reference
|
||||
- Create and manage collections
|
||||
- Auto-assign rules
|
||||
- Test rules
|
||||
- Bulk operations
|
||||
- Device shelf mappings
|
||||
|
||||
## 🔌 Protocol Specifications
|
||||
|
||||
### Sync Protocols
|
||||
|
||||
- **[Kobo Sync Protocol](api/sync/kobo-protocol.md)** - Kobo device sync protocol
|
||||
- Kobo markup sync
|
||||
- Library fetch
|
||||
- Authentication
|
||||
|
||||
- **[KOReader Sync Protocol](api/sync/koreader-protocol.md)** - KOReader sync protocol
|
||||
- Progress sync
|
||||
- Metadata fetch
|
||||
- Conflict handling
|
||||
|
||||
### WebSocket Protocol
|
||||
|
||||
- **[WebSocket API](websocket-api.md)** - Real-time sync and events
|
||||
- Connection flow
|
||||
- Message format
|
||||
- Event types
|
||||
- Authentication
|
||||
|
||||
---
|
||||
|
||||
**Looking for user documentation?** See the [User Portal](../user/INDEX.md)
|
||||
**Need to deploy?** See the [Operations Portal](../operations/INDEX.md)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
# API Documentation
|
||||
|
||||
Complete reference for Bookhoard REST API endpoints.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Authentication](authentication/) - User registration, login, tokens
|
||||
- [Users](users/) - Profile management
|
||||
- [Libraries](libraries/) - Library management
|
||||
- [Media Items](media-items/) - Book/ebook operations
|
||||
- [Progress](progress/) - Reading progress tracking
|
||||
- [Notes](notes/) - User notes management
|
||||
- [Highlights](highlights/) - Book highlights
|
||||
- [Ratings](ratings/) - Book ratings
|
||||
- [Devices](devices/) - Device registration and sync
|
||||
- [Analytics](analytics/) - Usage statistics
|
||||
- [Book Matching](book-matching/) - Search and link books
|
||||
- [Collections](collections/) - See [Collections API](../collections-api.md)
|
||||
- [OPDS](opds/) - Open Publication Distribution
|
||||
- [Sync Protocols](sync/) - KOReader and Kobo sync
|
||||
- [WebSocket](websocket/) - Real-time sync events
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
See [Authentication Endpoints](authentication/)
|
||||
|
||||
## Users & Profiles
|
||||
|
||||
See [User Management](users/)
|
||||
|
||||
## Libraries
|
||||
|
||||
See [Library Management](libraries/)
|
||||
|
||||
## Media Items
|
||||
|
||||
See [Media Item Operations](media-items/)
|
||||
|
||||
## Reading Progress
|
||||
|
||||
See [Progress Tracking](progress/)
|
||||
|
||||
## Notes & Highlights
|
||||
|
||||
See [Notes Management](notes/) and [Highlights Management](highlights/)
|
||||
|
||||
## Ratings
|
||||
|
||||
See [Ratings System](ratings/)
|
||||
|
||||
## Device Management
|
||||
|
||||
See [Device Registration & Sync](devices/)
|
||||
|
||||
## Analytics
|
||||
|
||||
See [Usage Analytics](analytics/)
|
||||
|
||||
## Book Matching & Linking
|
||||
|
||||
See [Book Matching](book-matching/)
|
||||
|
||||
## Collections
|
||||
|
||||
See [Collections API](../collections-api.md) or [Collections Endpoints](collections/)
|
||||
|
||||
## OPDS
|
||||
|
||||
See [OPDS Feeds](opds/)
|
||||
|
||||
## Sync Protocols
|
||||
|
||||
See [KOReader Sync](sync/) and [Kobo Sync](sync/)
|
||||
|
||||
## WebSocket
|
||||
|
||||
See [WebSocket Protocol](websocket/)
|
||||
@@ -0,0 +1,49 @@
|
||||
# Get Reading Statistics
|
||||
|
||||
Retrieve reading statistics for a date range.
|
||||
|
||||
**Endpoint**: `GET /api/analytics/reading-stats`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| start_date | string | No | Start date (ISO 8601 format) |
|
||||
| end_date | string | No | End date (ISO 8601 format) |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/analytics/reading-stats?start_date=2026-01-01&end_date=2026-01-31
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"pages_read": 1250,
|
||||
"books_completed": 5,
|
||||
"reading_time_hours": 42.5,
|
||||
"sessions_count": 28,
|
||||
"average_session_minutes": 91
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid date format |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,49 @@
|
||||
# Login User
|
||||
|
||||
Authenticate with email and password.
|
||||
|
||||
**Endpoint**: `POST /api/auth/login`
|
||||
**Auth**: Not required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| password | string | Yes | User's password |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "SecureP@ss123!"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid email or password |
|
||||
| 400 | Missing required fields |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,35 @@
|
||||
# Logout User
|
||||
|
||||
Invalidate the current JWT token.
|
||||
|
||||
**Endpoint**: `POST /api/auth/logout`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token (e.g., `Bearer eyJhbG...`) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
POST /api/auth/logout
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | Token already invalidated |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Refresh Token
|
||||
|
||||
Obtain a new JWT token using a refresh token.
|
||||
|
||||
**Endpoint**: `POST /api/auth/refresh`
|
||||
**Auth**: Not required (uses refresh token)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| refresh_token | string | Yes | Valid refresh token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"refresh_token": "d4f5g6h7..."
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "new-jwt-token",
|
||||
"refresh_token": "new-refresh-token"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired refresh token |
|
||||
| 400 | Missing refresh token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,57 @@
|
||||
# Register User
|
||||
|
||||
Create a new user account.
|
||||
|
||||
**Endpoint**: `POST /api/auth/register`
|
||||
**Auth**: Not required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| username | string | Yes | Desired username |
|
||||
| password | string | Yes | Password (min 8 chars) |
|
||||
| first_name | string | No | User's first name |
|
||||
| last_name | string | No | User's last name |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"password": "SecureP@ss123!",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "d4f5g6h7...",
|
||||
"user": {
|
||||
"id": "uuid-here",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"role": "user",
|
||||
"theme": "tokyo-night",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid email format, weak password, or missing fields |
|
||||
| 409 | Email or username already exists |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,92 @@
|
||||
# Link Book
|
||||
|
||||
Link a device book to a Bookhoard media item. Supports bulk linking.
|
||||
|
||||
**Endpoint**: `POST /api/sync/bulk-link-books` or `POST /api/sync/auto-link-books`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Manual Link Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| links | array | Yes | List of book links |
|
||||
| links[].unlinked_book_id | string | Yes | Device book UUID |
|
||||
| links[].media_item_id | string | Yes | Bookhoard media item UUID |
|
||||
| links[].confidence_score | float | No | Match confidence (0-1) |
|
||||
|
||||
### Example Manual Link Request
|
||||
|
||||
```json
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"media_item_id": "uuid-2",
|
||||
"confidence_score": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-Link Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| confidence_threshold | float | No | Minimum confidence for auto-link (default: 0.8) |
|
||||
| limit | integer | No | Maximum books to auto-link (default: 50) |
|
||||
|
||||
### Example Auto-Link Request
|
||||
|
||||
```json
|
||||
{
|
||||
"confidence_threshold": 0.8,
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Manual Link
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"status": "success",
|
||||
"media_item_id": "uuid-2"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Auto-Link
|
||||
|
||||
```json
|
||||
{
|
||||
"auto_linked": 15,
|
||||
"results": [
|
||||
{
|
||||
"unlinked_book_id": "uuid-1",
|
||||
"title": "The Hobbit",
|
||||
"matched_media_item_id": "uuid-2",
|
||||
"confidence": 0.95,
|
||||
"match_method": "sha256_match"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid link data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Search Books for Matching
|
||||
|
||||
Query books to find potential matches for linking.
|
||||
|
||||
**Endpoint**: `POST /api/sync/books/query`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| identifiers | array | No | List of identifiers (ISBN, UUID) |
|
||||
| sha256 | string | No | SHA256 hash of book file |
|
||||
| title | string | No | Book title |
|
||||
| author | string | No | Book author |
|
||||
| file_size | integer | No | File size in bytes |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"identifiers": ["isbn:978-0345391802", "uuid:abc-123"],
|
||||
"sha256": "a1b2c3d4e5f6abc123...",
|
||||
"title": "The Hobbit",
|
||||
"author": "J.R.R. Tolkien",
|
||||
"file_size": 2456789
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"media_item_id": "uuid-123",
|
||||
"bookhoard_uuid": "uuid-123",
|
||||
"confidence": 1.0,
|
||||
"match_method": "uuid_match"
|
||||
}
|
||||
],
|
||||
"action": "auto_link"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid query parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,35 @@
|
||||
# Collections API
|
||||
|
||||
See [Collections API](../../collections-api.md) for complete collections documentation.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [List Collections](list_collections.md) - Get all collections
|
||||
- [Get Collection](get_collection.md) - Get collection details
|
||||
- [Create Collection](create_collection.md) - Create a new collection
|
||||
- [Update Collection](update_collection.md) - Update collection metadata
|
||||
- [Delete Collection](delete_collection.md) - Delete a collection
|
||||
- [Add Auto-Assign Rule](add_auto_assign_rule.md) - Add automatic book assignment rule
|
||||
- [Remove Auto-Assign Rule](remove_auto_assign_rule.md) - Remove assignment rule
|
||||
- [Test Rule](test_rule.md) - Test assignment rule
|
||||
- [Bulk Assign](bulk_assign.md) - Bulk assign books to collection
|
||||
- [Create Shelf Mapping](create_shelf_mapping.md) - Map collection to device shelf
|
||||
- [Delete Shelf Mapping](delete_shelf_mapping.md) - Remove shelf mapping
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Collections allow you to organize your books into custom groups with automatic assignment rules.
|
||||
|
||||
## Features
|
||||
|
||||
- **Manual Assignment**: Add specific books to collections
|
||||
- **Auto-Assignment**: Define rules to automatically assign books based on metadata
|
||||
- **Device Sync**: Collections sync as shelves on Kobo/KOReader devices
|
||||
- **Flexible Rules**: Filter by genre, series, tags, and more
|
||||
|
||||
## See Also
|
||||
|
||||
- [Collections API](../../collections-api.md) - Complete API reference
|
||||
- [Sync Guide](../../user/sync-guide.md) - How collections sync to devices
|
||||
@@ -0,0 +1,85 @@
|
||||
# Add Auto-Assign Rule
|
||||
|
||||
Add an automatic book assignment rule to a collection.
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/rules`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
| priority | integer | No | Rule priority (1 = highest, default: 1) |
|
||||
| enabled | boolean | No | Whether rule is active (default: true) |
|
||||
|
||||
### Supported Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| genre | string | Book genre |
|
||||
| author | string | Book author |
|
||||
| series | string | Book series name |
|
||||
| language | string | Book language |
|
||||
| publisher | string | Publisher name |
|
||||
| copyright_year | number | Publication year (numeric comparison) |
|
||||
| tags | string | Book tags |
|
||||
|
||||
### Supported Operators
|
||||
|
||||
| Operator | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| equals | all | Exact match |
|
||||
| not_equals | all | Not equal |
|
||||
| contains | string | Contains substring (case-insensitive) |
|
||||
| not_contains | string | Does not contain |
|
||||
| starts_with | string | Starts with (case-insensitive) |
|
||||
| ends_with | string | Ends with (case-insensitive) |
|
||||
| greater_than | number | Greater than |
|
||||
| less_than | number | Less than |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "rule-uuid-here",
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true,
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,52 @@
|
||||
# Bulk Assign Books
|
||||
|
||||
Add multiple books to a collection at once.
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/books`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| book_ids | array of UUID | Yes | Array of book IDs to add |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"book_ids": [
|
||||
"660e8400-e29b-41d4-a716-446655440000",
|
||||
"770e8400-e29b-41d4-a716-446655440000",
|
||||
"880e8400-e29b-41d4-a716-446655440000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Books added to collection successfully. No response body.
|
||||
|
||||
## Notes
|
||||
|
||||
- Books already in the collection are ignored
|
||||
- This is more efficient than adding books individually
|
||||
- Maximum 1000 books per request
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection or book(s) not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,96 @@
|
||||
# Create Collection
|
||||
|
||||
Create a new collection.
|
||||
|
||||
**Endpoint**: `POST /api/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | Yes | Collection name (max 255 chars) |
|
||||
| description | string | No | Collection description |
|
||||
| color | string | No | Hex color code (e.g., "#FF5733") |
|
||||
| icon | string | No | Emoji icon (e.g., "🚀", "📖") |
|
||||
| auto_assign_rules | array | No | Array of rule objects |
|
||||
| view_settings | object | No | Per-device display preferences |
|
||||
|
||||
### Auto-Assign Rule Object
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
| priority | integer | No | Rule priority (1 = highest, default: 1) |
|
||||
| enabled | boolean | No | Whether rule is active (default: true) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "To Read",
|
||||
"description": "Books I want to read soon",
|
||||
"color": "#00FF00",
|
||||
"icon": "📖",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "tags",
|
||||
"operator": "contains",
|
||||
"value": "to-read",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
},
|
||||
"koreader": {
|
||||
"view_mode": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "To Read",
|
||||
"description": "Books I want to read soon",
|
||||
"color": "#00FF00",
|
||||
"icon": "📖",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "tags",
|
||||
"operator": "contains",
|
||||
"value": "to-read",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
},
|
||||
"koreader": {
|
||||
"view_mode": "list"
|
||||
}
|
||||
},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,69 @@
|
||||
# Create Shelf Mapping
|
||||
|
||||
Map a collection to a device shelf for syncing.
|
||||
|
||||
**Endpoint**: `POST /api/devices/{deviceId}/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| deviceId | string (UUID) | Yes | Device UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| collection_id | string (UUID) | Yes | Collection UUID to map |
|
||||
| device_shelf_name | string | Yes | Name of the shelf on the device |
|
||||
| sync_direction | string | No | Sync direction (default: "bidirectional") |
|
||||
|
||||
### Sync Directions
|
||||
|
||||
| Direction | Description |
|
||||
|-----------|-------------|
|
||||
| bidirectional | Sync both ways between Bookhoard and device |
|
||||
| book_to_hoard | Bookhoard → Device only |
|
||||
| device_to_hoard | Device → Bookhoard only |
|
||||
| none | No sync (mapping only for reference) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_name": "Science Fiction",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional",
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Use Case
|
||||
|
||||
Collections can be synced to device-specific shelves (Kobo, KOReader). This allows automatic organization of books on your e-reader based on your Bookhoard collections.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Device or collection not found |
|
||||
| 409 | Mapping already exists |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,27 @@
|
||||
# Delete Collection
|
||||
|
||||
Delete a collection. Books are NOT deleted.
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Collection deleted successfully. No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,34 @@
|
||||
# Delete Shelf Mapping
|
||||
|
||||
Remove a collection-to-shelf mapping for a device.
|
||||
|
||||
**Endpoint**: `DELETE /api/devices/{deviceId}/collections/{collectionId}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| deviceId | string (UUID) | Yes | Device UUID |
|
||||
| collectionId | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Shelf mapping deleted successfully. No response body.
|
||||
|
||||
## Notes
|
||||
|
||||
- Deleting a mapping does NOT delete the collection
|
||||
- Deleting a mapping does NOT delete books from the device
|
||||
- It only removes the association between the collection and device shelf
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Device or collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,54 @@
|
||||
# Get Collection
|
||||
|
||||
Get single collection with all books.
|
||||
|
||||
**Endpoint**: `GET /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| include_books | boolean | No | Include books in response (default: true) |
|
||||
| limit | integer | No | Number of books to return (default: 50) |
|
||||
| offset | integer | No | Number of books to skip (default: 0) |
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"books": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg"
|
||||
}
|
||||
],
|
||||
"auto_assign_rules": [],
|
||||
"view_settings": {},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,57 @@
|
||||
# List Collections
|
||||
|
||||
Get all collections for the authenticated user.
|
||||
|
||||
**Endpoint**: `GET /api/collections`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| limit | integer | No | Number of collections to return (default: 50) |
|
||||
| offset | integer | No | Number of collections to skip (default: 0) |
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid",
|
||||
"sort_order": "name",
|
||||
"items_per_page": 24
|
||||
}
|
||||
},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,28 @@
|
||||
# Remove Auto-Assign Rule
|
||||
|
||||
Remove an automatic book assignment rule from a collection.
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{collectionId}/rules/{ruleId}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| collectionId | string (UUID) | Yes | Collection UUID |
|
||||
| ruleId | string (UUID) | Yes | Rule UUID |
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Rule deleted successfully. No response body.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection or rule not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,71 @@
|
||||
# Test Rule
|
||||
|
||||
Test which books would match given rules without saving.
|
||||
|
||||
**Endpoint**: `POST /api/collections/test-rules`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| rules | array | Yes | Array of rule objects to test |
|
||||
|
||||
### Rule Object
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) |
|
||||
| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) |
|
||||
| value | string/number | Yes | Value to compare against |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction"
|
||||
},
|
||||
{
|
||||
"field": "author",
|
||||
"operator": "contains",
|
||||
"value": "Asimov"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg",
|
||||
"match_reason": "Matched rule: genre equals Science Fiction"
|
||||
}
|
||||
],
|
||||
"total": 42
|
||||
}
|
||||
```
|
||||
|
||||
## Use Case
|
||||
|
||||
Test rules before creating a collection to verify correct book matching. This endpoint shows which books would be added without actually modifying any collections.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,63 @@
|
||||
# Update Collection
|
||||
|
||||
Update collection details.
|
||||
|
||||
**Endpoint**: `PUT /api/collections/{id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| id | string (UUID) | Yes | Collection UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
All fields are optional. Include only fields you want to update.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | No | Collection name (max 255 chars) |
|
||||
| description | string | No | Collection description |
|
||||
| color | string | No | Hex color code (e.g., "#FF5733") |
|
||||
| icon | string | No | Emoji icon (e.g., "🚀", "📖") |
|
||||
| auto_assign_rules | array | No | Array of rule objects (replaces existing rules) |
|
||||
| view_settings | object | No | Per-device display preferences |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "Updated description",
|
||||
"color": "#7aa2f7",
|
||||
"icon": "🌟"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "Updated description",
|
||||
"color": "#7aa2f7",
|
||||
"icon": "🌟",
|
||||
"auto_assign_rules": [],
|
||||
"view_settings": {},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 404 | Collection not found |
|
||||
| 500 | Internal server error |
|
||||
|
||||
## Try It Out
|
||||
@@ -0,0 +1,62 @@
|
||||
# Get Device / Check Registration Status
|
||||
|
||||
Check device registration status or get device details.
|
||||
|
||||
**Endpoint**: `POST /api/devices/auth/status` or `GET /api/devices/{device_id}`
|
||||
**Auth**: Not required for status check, Required for device details
|
||||
**Content-Type**: `application/json` (for status check)
|
||||
|
||||
## Request Body (Status Check)
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| registration_id | string | Yes | Registration UUID |
|
||||
|
||||
### Example Request (Status Check)
|
||||
|
||||
```json
|
||||
{
|
||||
"registration_id": "registration-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "pending|approved|expired",
|
||||
"auth_token": "device-bearer-token...",
|
||||
"device_id": "uuid",
|
||||
"sync_endpoints": {
|
||||
"progress": "https://bookhoard.com/api/sync/progress",
|
||||
"metadata": "https://bookhoard.com/api/sync/metadata",
|
||||
"annotations": "https://bookhoard.com/api/sync/annotations"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK) - Device Details
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"last_sync": "2026-01-31T10:00:00Z",
|
||||
"last_seen": "2026-01-31T10:05:00Z",
|
||||
"sync_enabled": true,
|
||||
"auto_sync": true,
|
||||
"sync_frequency_minutes": 5
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token (for device details) |
|
||||
| 404 | Device or registration not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# List User Devices
|
||||
|
||||
Retrieve all devices registered to the current user.
|
||||
|
||||
**Endpoint**: `GET /api/devices`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/devices
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"last_sync": "2026-01-31T10:00:00Z",
|
||||
"last_seen": "2026-01-31T10:05:00Z",
|
||||
"sync_enabled": true,
|
||||
"auto_sync": true,
|
||||
"sync_frequency_minutes": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Register Device
|
||||
|
||||
Register a new device for sync.
|
||||
|
||||
**Endpoint**: `POST /api/devices/register`
|
||||
**Auth**: Not required (device registration flow)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| device_name | string | Yes | Device name |
|
||||
| device_type | string | Yes | Device type: kobo, koreader, web, mobile |
|
||||
| device_identifier | string | Yes | Hardware-specific ID |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"device_name": "My Kobo Clara",
|
||||
"device_type": "kobo",
|
||||
"device_identifier": "hardware-specific-id"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "uuid",
|
||||
"registration_id": "registration-uuid",
|
||||
"auth_url": "https://bookhoard.com/devices/auth/confirm/abc123",
|
||||
"qr_code": "data:image/png;base64,iVBORw0KG...",
|
||||
"expires_in": 300
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid device data |
|
||||
| 409 | Device already registered |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Revoke Device
|
||||
|
||||
Revoke access to a device.
|
||||
|
||||
**Endpoint**: `DELETE /api/devices/{device_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| device_id | string | Yes | Device UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/devices/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Device revoked successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this device |
|
||||
| 404 | Device not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,66 @@
|
||||
# Create Highlight
|
||||
|
||||
Create a new highlight for a media item.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/highlights`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| selection_text | string | Yes | Highlighted text |
|
||||
| start_position | string | No | Start position (e.g., epubcfi) |
|
||||
| end_position | string | No | End position (e.g., epubcfi) |
|
||||
| color | string | No | Highlight color (hex, default: "#ffff00") |
|
||||
| percentage_start | float | No | Start percentage (0-1) |
|
||||
| percentage_end | float | No | End percentage (0-1) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"selection_text": "Highlighted text...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Highlighted text...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid highlight data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Highlight
|
||||
|
||||
Delete a highlight.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/highlights/{highlight_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| highlight_id | string | Yes | Highlight UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/highlights/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Highlight deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this highlight |
|
||||
| 404 | Highlight not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,61 @@
|
||||
# Get Highlights
|
||||
|
||||
Retrieve all highlights for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/highlights`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/highlights
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"highlights": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Highlighted text passage...",
|
||||
"start_position": "epubcfi(/6/4/2:15)",
|
||||
"end_position": "epubcfi(/6/4/2:20)",
|
||||
"color": "#ffff00",
|
||||
"percentage_start": 0.45,
|
||||
"percentage_end": 0.47,
|
||||
"character_start": 15432,
|
||||
"character_end": 15480,
|
||||
"epubcfi_start": "epubcfi(/6/4/2:15)",
|
||||
"epubcfi_end": "epubcfi(/6/4/2:20)",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Update Highlight
|
||||
|
||||
Update an existing highlight.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/highlights/{highlight_id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| highlight_id | string | Yes | Highlight UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| selection_text | string | No | Updated highlighted text |
|
||||
| color | string | No | Updated highlight color (hex) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"selection_text": "Updated text",
|
||||
"color": "#00ff00"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"selection_text": "Updated text",
|
||||
"color": "#00ff00",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid highlight data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this highlight |
|
||||
| 404 | Highlight not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,51 @@
|
||||
# Add Library Folder
|
||||
|
||||
Add a folder to an existing library (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries/{library_id}/folders`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| folder_path | string | Yes | Absolute path to folder |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"folder_path": "/path/to/comics"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"folder_path": "/path/to/comics",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid folder path |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,49 @@
|
||||
# Create Library
|
||||
|
||||
Create a new library (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | Yes | Library name |
|
||||
| description | string | No | Library description |
|
||||
| type | string | Yes | Library type (e.g., "ebooks", "comics", "audiobooks") |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Comics Collection",
|
||||
"description": "Digital comics",
|
||||
"type": "comics"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Comics Collection",
|
||||
"description": "Digital comics",
|
||||
"type": "comics",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Get Library Details
|
||||
|
||||
Retrieve details of a specific library.
|
||||
|
||||
**Endpoint**: `GET /api/libraries/{library_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/libraries/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Ebooks",
|
||||
"description": "Ebook collection",
|
||||
"type": "ebooks",
|
||||
"folders": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"folder_path": "/path/to/ebooks"
|
||||
}
|
||||
],
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
| 404 | Library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,45 @@
|
||||
# Get Visible Libraries
|
||||
|
||||
Retrieve all libraries visible to the current user.
|
||||
|
||||
**Endpoint**: `GET /api/libraries/visible`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/libraries/visible
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"libraries": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Ebooks",
|
||||
"description": "Ebook collection",
|
||||
"type_name": "ebooks",
|
||||
"is_visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Set Library Visibility
|
||||
|
||||
Set library visibility for a specific user (Admin only).
|
||||
|
||||
**Endpoint**: `POST /api/libraries/visibility`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| user_id | string | Yes | User UUID |
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| is_visible | boolean | Yes | Whether library is visible to user |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "user-uuid",
|
||||
"library_id": "library-uuid",
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "user-uuid",
|
||||
"library_id": "library-uuid",
|
||||
"is_visible": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | User or library not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Media Item
|
||||
|
||||
Delete a media item from the library (Admin only).
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/{media_id}`
|
||||
**Auth**: Required (Admin only)
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Media item deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,67 @@
|
||||
# Filter & Sort Media Items
|
||||
|
||||
Filter and sort media items with advanced criteria.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/filter`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| author_filter | string | No | Filter by author name |
|
||||
| series_filter | string | No | Filter by series name |
|
||||
| genre_filter | string | No | Filter by genre |
|
||||
| year_min | integer | No | Minimum copyright year |
|
||||
| year_max | integer | No | Maximum copyright year |
|
||||
| has_cover | boolean | No | Filter by cover image existence |
|
||||
| sort | string | No | Sort field and order (e.g., "title ASC", "created_at DESC") |
|
||||
| limit | integer | No | Number of results (default 20) |
|
||||
| offset | integer | No | Number to skip |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "uuid",
|
||||
"author_filter": "Rowling",
|
||||
"series_filter": "Harry Potter",
|
||||
"genre_filter": "Fantasy",
|
||||
"year_min": 1997,
|
||||
"year_max": 2007,
|
||||
"has_cover": true,
|
||||
"sort": "title ASC",
|
||||
"limit": 20,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"match_score": 0.95
|
||||
}
|
||||
],
|
||||
"total": 7
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid filter parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,61 @@
|
||||
# Get Media Item
|
||||
|
||||
Retrieve details of a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid-here
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"description": "Book description",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_size": 1024000,
|
||||
"mime_type": "application/epub+zip",
|
||||
"cover_image_path": "/path/to/cover.jpg",
|
||||
"series": "Series Name",
|
||||
"series_number": 1,
|
||||
"tags": "sci-fi, space opera",
|
||||
"language": "en",
|
||||
"page_count": 350,
|
||||
"genre": "Science Fiction",
|
||||
"copyright_year": 2023,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this media item |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,68 @@
|
||||
# List Media Items
|
||||
|
||||
Retrieve a paginated list of media items from a library.
|
||||
|
||||
**Endpoint**: `GET /api/media-items`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| limit | integer | No | Number of items to return (max 100, default 20) |
|
||||
| offset | integer | No | Number of items to skip |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items?library_id=uuid&limit=20&offset=0
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"library_id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"description": "Book description",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_size": 1024000,
|
||||
"mime_type": "application/epub+zip",
|
||||
"cover_image_path": "/path/to/cover.jpg",
|
||||
"series": "Series Name",
|
||||
"series_number": 1,
|
||||
"tags": "sci-fi, space opera",
|
||||
"language": "en",
|
||||
"page_count": 350,
|
||||
"genre": "Science Fiction",
|
||||
"copyright_year": 2023,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid query parameters |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not have access to this library |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,54 @@
|
||||
# Search Media Items
|
||||
|
||||
Search for media items by title, author, or description.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/search`
|
||||
**Auth**: Required
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| q | string | Yes | Search query (minimum 2 characters) |
|
||||
| limit | integer | No | Number of results (default 20) |
|
||||
| offset | integer | No | Number to skip |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/search?q=Harry+Potter&limit=20&offset=0
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"match_score": 0.95
|
||||
}
|
||||
],
|
||||
"total": 15
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid search query (too short) |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,62 @@
|
||||
# Update Media Item
|
||||
|
||||
Update media item metadata (Admin only).
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/{media_id}`
|
||||
**Auth**: Required (Admin only)
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| title | string | No | Updated title |
|
||||
| author | string | No | Updated author |
|
||||
| description | string | No | Updated description |
|
||||
| series | string | No | Series name |
|
||||
| series_number | integer | No | Number in series |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Updated Title",
|
||||
"author": "Updated Author",
|
||||
"description": "Updated description",
|
||||
"series": "Series",
|
||||
"series_number": 2
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Updated Title",
|
||||
"author": "Updated Author",
|
||||
"description": "Updated description",
|
||||
"series": "Series",
|
||||
"series_number": 2,
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User is not an admin |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,60 @@
|
||||
# Create Note
|
||||
|
||||
Create a new note for a media item.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/notes`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| content | string | Yes | Note content |
|
||||
| position | string | No | Location reference (e.g., epubcfi) |
|
||||
| percentage_location | float | No | Location as percentage (0-1) |
|
||||
| epubcfi_location | string | No | EPUB CFI location |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "This is a note",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "This is a note",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid note data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete Note
|
||||
|
||||
Delete a note.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/notes/{note_id}`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| note_id | string | Yes | Note UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/notes/uuid
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Note deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this note |
|
||||
| 404 | Note not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,58 @@
|
||||
# Get Notes
|
||||
|
||||
Retrieve all notes for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/notes`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/notes
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"notes": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "This is an interesting passage...",
|
||||
"position": "epubcfi(/6/4/2:15)",
|
||||
"percentage_location": 0.45,
|
||||
"character_start": 15432,
|
||||
"character_end": 15480,
|
||||
"epubcfi_location": "epubcfi(/6/4/2:15)",
|
||||
"created_at": "2026-01-31T10:00:00Z",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Update Note
|
||||
|
||||
Update an existing note.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/notes/{note_id}`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| note_id | string | Yes | Note UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| content | string | No | Updated note content |
|
||||
| position | string | No | Updated location reference |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "Updated note content",
|
||||
"position": "epubcfi(/6/4/2:20)"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"content": "Updated note content",
|
||||
"position": "epubcfi(/6/4/2:20)",
|
||||
"updated_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid note data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 403 | User does not own this note |
|
||||
| 404 | Note not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,72 @@
|
||||
# OPDS Acquisition
|
||||
|
||||
Download books and list available formats.
|
||||
|
||||
## Download Book with Format Conversion
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/download/{bookId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| format | string | No | Book format: `epub` (default), `kepub` |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/download/uuid-123?format=kepub
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
**Headers:**
|
||||
- `Content-Type`: `application/epub+zip` or `application/vnd.kobo+xml+zip`
|
||||
- `Content-Disposition`: attachment; filename="The Hobbit.epub"
|
||||
- `X-Bookhoard-UUID`: uuid-123
|
||||
- `X-Bookhoard-SHA256`: abc123... (original hash)
|
||||
- `X-Bookhoard-KEPUB-SHA256`: xyz789... (KEPUB hash if format=kepub)
|
||||
|
||||
**Body:** Book file binary data
|
||||
|
||||
## List Available Formats
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/formats/{bookId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/formats/uuid-123
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_item_id": "uuid-123",
|
||||
"formats": [
|
||||
{
|
||||
"format_type": "epub",
|
||||
"file_path": "/path/to/book.epub",
|
||||
"file_sha256": "abc123...",
|
||||
"file_size_bytes": 2456789,
|
||||
"mime_type": "application/epub+zip",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"format_type": "kepub",
|
||||
"file_path": "/cache/book.kepub.epub",
|
||||
"file_sha256": "xyz789...",
|
||||
"file_size_bytes": 2478932,
|
||||
"mime_type": "application/vnd.kobo+xml+zip",
|
||||
"available": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,81 @@
|
||||
# OPDS Feeds
|
||||
|
||||
Bookhoard provides OPDS 1.2 feeds for device compatibility.
|
||||
|
||||
## Get Device Catalog
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/catalog`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| page | integer | No | Page number (default: 1) |
|
||||
| per_page | integer | No | Items per page (default: 50, max: 200) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/catalog?page=1&per_page=50
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:opds="http://opds-spec.org/2010/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<id>urn:uuid:device-id</id>
|
||||
<title>Bookhoard Library</title>
|
||||
<updated>2026-02-01T12:00:00Z</updated>
|
||||
|
||||
<link rel="self" href="http://localhost:8765/opds/devices/kobo-id/catalog"/>
|
||||
<link rel="search" href="http://localhost:8765/opds/devices/kobo-id/search"/>
|
||||
<link rel="start" href="http://localhost:8765/opds/devices/kobo-id/nav"/>
|
||||
|
||||
<entry>
|
||||
<id>urn:uuid:bookhoard-uuid-123</id>
|
||||
<dc:title>The Hobbit</dc:title>
|
||||
<dc:creator>J.R.R. Tolkien</dc:creator>
|
||||
<updated>2026-02-01T10:00:00Z</updated>
|
||||
|
||||
<link href="http://localhost:8765/opds/devices/kobo-id/download/uuid-123"
|
||||
type="application/epub+zip"
|
||||
rel="http://opds-spec.org/acquisition/open-access"/>
|
||||
|
||||
<link href="http://localhost:8765/opds/devices/kobo-id/download/uuid-123?format=kepub"
|
||||
type="application/vnd.kobo+xml+zip"
|
||||
rel="alternate"/>
|
||||
|
||||
<dc:identifier id="bookhoard">uuid-123</dc:identifier>
|
||||
<meta property="bookhoard:sha256">abc123...</meta>
|
||||
</entry>
|
||||
</feed>
|
||||
```
|
||||
|
||||
## Search OPDS Catalog
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/search`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| q | string | Yes | Search query |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/search?q=Hobbit
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML with search results)
|
||||
|
||||
Returns OPDS feed with matching books.
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# OPDS Publication
|
||||
|
||||
OPDS navigation and publication feeds.
|
||||
|
||||
## Navigation Feed
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/nav`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/nav
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 Navigation XML)
|
||||
|
||||
Returns OPDS navigation feed with links to:
|
||||
- Root catalog
|
||||
- Search
|
||||
- Collections/shelves
|
||||
- Filtered views (by author, series, etc.)
|
||||
|
||||
## Publication Feeds by Collection
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/collections/{collectionId}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/collections/collection-uuid
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
Returns OPDS feed with books in the specified collection.
|
||||
|
||||
## Publication Feeds by Shelf
|
||||
|
||||
**Endpoint**: `GET /opds/devices/{deviceId}/shelves/{shelfName}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /opds/devices/kobo-id/shelves/Favorites
|
||||
```
|
||||
|
||||
### Response (200 OK - OPDS 1.2 XML)
|
||||
|
||||
Returns OPDS feed with books on the specified device shelf.
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,40 @@
|
||||
# Delete Reading Progress
|
||||
|
||||
Delete reading progress for a media item.
|
||||
|
||||
**Endpoint**: `DELETE /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
DELETE /api/media-items/uuid/progress
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Progress deleted successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Get Reading Progress
|
||||
|
||||
Retrieve reading progress for a specific media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/progress
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"media_item_id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"current_page": 45,
|
||||
"total_pages": 200,
|
||||
"percentage": 0.225,
|
||||
"character_offset": 15432,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3,
|
||||
"chapter_progress": 0.5,
|
||||
"last_read_at": "2026-01-31T10:00:00Z",
|
||||
"format_group": "reflowable",
|
||||
"viewport_y": 0.12,
|
||||
"zoom_level": 1.0
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,72 @@
|
||||
# Update Reading Progress
|
||||
|
||||
Update reading progress for a media item. This will sync across all devices via WebSocket.
|
||||
|
||||
**Endpoint**: `PUT /api/media-items/{media_id}/progress`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| source | string | Yes | Progress source (e.g., "web", "koreader", "kobo") |
|
||||
| location | object | Yes | Location information |
|
||||
| location.percentage | float | No | Progress percentage (0-1) |
|
||||
| location.epubcfi | string | No | EPUB CFI location |
|
||||
| location.character | integer | No | Character offset |
|
||||
| location.chapter | integer | No | Chapter number |
|
||||
| location.page | integer | No | Current page |
|
||||
| location.total_pages | integer | No | Total pages |
|
||||
| device_metadata | object | No | Device metadata |
|
||||
| device_metadata.device_type | string | No | Device type |
|
||||
| device_metadata.user_agent | string | No | User agent string |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "web",
|
||||
"location": {
|
||||
"percentage": 0.45678,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"character": 15432,
|
||||
"chapter": 3,
|
||||
"page": 89,
|
||||
"total_pages": 200
|
||||
},
|
||||
"device_metadata": {
|
||||
"device_type": "web",
|
||||
"user_agent": "Mozilla/5.0..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"sync_status": "success",
|
||||
"progress_updated": true,
|
||||
"devices_notified": ["device-1", "device-2"],
|
||||
"broadcast": true
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid location data |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,52 @@
|
||||
# Create Rating
|
||||
|
||||
Set a rating for a media item. Creates a new rating or updates an existing one.
|
||||
|
||||
**Endpoint**: `POST /api/media-items/{media_id}/rating`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| rating | integer | Yes | Rating from 1-10 |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8
|
||||
}
|
||||
```
|
||||
|
||||
## Response (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8,
|
||||
"user_id": "uuid",
|
||||
"media_item_id": "uuid",
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid rating (must be 1-10) |
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Get Rating
|
||||
|
||||
Retrieve the current user's rating for a media item.
|
||||
|
||||
**Endpoint**: `GET /api/media-items/{media_id}/rating`
|
||||
**Auth**: Required
|
||||
|
||||
## Path Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| media_id | string | Yes | Media item UUID |
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/media-items/uuid/rating
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"rating": 8,
|
||||
"user_id": "uuid",
|
||||
"media_item_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
| 404 | Media item not found or no rating set |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,103 @@
|
||||
# Kobo Sync Protocol
|
||||
|
||||
Kobo uses a proprietary sync protocol with JSON payloads.
|
||||
|
||||
## Kobo Markup Sync
|
||||
|
||||
**Endpoint**: `POST /api/sync/kobo/markup`
|
||||
**Auth**: Device token required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
### Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer device token |
|
||||
| x-kobo-device | string | Yes | JSON device info |
|
||||
| Content-Type | string | Yes | application/json |
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| ReadingSync | array | No | Array of reading progress data |
|
||||
| ReadingSync[].ContentId | string | Yes | Book UUID |
|
||||
| ReadingSync[].PercentRead | float | Yes | Progress percentage (0-100) |
|
||||
| ReadingSync[].EntitlementId | string | Yes | Kobo entitlement ID |
|
||||
| ReadingSync[].RemainingTimeMinutes | integer | No | Estimated remaining time |
|
||||
| ReadingSync[].LastModified | string | Yes | ISO 8601 timestamp |
|
||||
| BookmarkSync | array | No | Array of bookmarks/highlights |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
POST /api/sync/kobo/markup
|
||||
Authorization: Bearer device-token
|
||||
x-kobo-device: {"DeviceId":"device-id","Model":"Kobo Clara"}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ReadingSync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"PercentRead": 45.6,
|
||||
"EntitlementId": "entitlement-id",
|
||||
"RemainingTimeMinutes": 120,
|
||||
"LastModified": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
],
|
||||
"BookmarkSync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"BookmarkText": "highlighted text",
|
||||
"BookmarkType": "annotation",
|
||||
"BookmarkTitle": "Chapter 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"Status": "Success",
|
||||
"MarkupsSynced": 5,
|
||||
"BookmarksSynced": 3
|
||||
}
|
||||
```
|
||||
|
||||
## Kobo Library Fetch
|
||||
|
||||
**Endpoint**: `GET /api/sync/kobo/library`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/sync/kobo/library
|
||||
Authorization: Bearer device-token
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"library_sync": [
|
||||
{
|
||||
"ContentId": "book-uuid",
|
||||
"ContentType": "6",
|
||||
"Title": "Book Title",
|
||||
"Author": "Author Name",
|
||||
"PercentRead": 42.3,
|
||||
"PagesRemaining": 115,
|
||||
"BookmarkCount": 3,
|
||||
"LastModified": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,125 @@
|
||||
# KOReader Sync Protocol
|
||||
|
||||
KOReader uses a custom JSON-based sync protocol.
|
||||
|
||||
## KOReader Progress Sync
|
||||
|
||||
**Endpoint**: `POST /api/sync/koreader/progress`
|
||||
**Auth**: Device token required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
### Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer device token |
|
||||
| Content-Type | string | Yes | application/json |
|
||||
|
||||
### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| library_id | string | No | Library UUID |
|
||||
| books | array | Yes | Array of book sync data |
|
||||
| books[].uuid | string | Yes | Book UUID |
|
||||
| books[].title | string | Yes | Book title |
|
||||
| books[].authors | array | Yes | Array of author names |
|
||||
| books[].progress | float | Yes | Progress percentage (0-1) |
|
||||
| books[].percentage | float | Yes | Progress percentage (0-1) |
|
||||
| books[].last_read | string | Yes | ISO 8601 timestamp |
|
||||
| books[].chapter | integer | No | Current chapter |
|
||||
| books[].epubcfi | string | No | EPUB CFI location |
|
||||
| books[].character | integer | No | Character offset |
|
||||
| books[].bookmarks | array | No | Array of bookmarks/highlights |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "optional-uuid",
|
||||
"books": [
|
||||
{
|
||||
"uuid": "book-uuid",
|
||||
"title": "Book Title",
|
||||
"authors": ["Author Name"],
|
||||
"progress": 0.45,
|
||||
"percentage": 0.45,
|
||||
"last_read": "2026-01-30T20:00:00Z",
|
||||
"chapter": 3,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"character": 15432,
|
||||
"bookmarks": [
|
||||
{
|
||||
"chapter": 3,
|
||||
"datetime": "2026-01-30T19:55:00Z",
|
||||
"notes": "highlighted text",
|
||||
"pos0": "epubcfi(/6/4/2:15)",
|
||||
"pos1": "epubcfi(/6/4/2:20)",
|
||||
"page": 45,
|
||||
"text": "highlighted text excerpt",
|
||||
"type": "highlight"
|
||||
}
|
||||
],
|
||||
"highlights": [],
|
||||
"notes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Response (202 Accepted)
|
||||
|
||||
```json
|
||||
{
|
||||
"sync_status": "accepted",
|
||||
"books_synced": 1,
|
||||
"conflicts": [
|
||||
{
|
||||
"book_uuid": "book-uuid",
|
||||
"conflict_type": "progress_mismatch",
|
||||
"device_progress": 0.45,
|
||||
"server_progress": 0.42,
|
||||
"resolution": "device_wins"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## KOReader Metadata Fetch
|
||||
|
||||
**Endpoint**: `GET /api/sync/koreader/metadata/{book_uuid}`
|
||||
**Auth**: Device token required
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/sync/koreader/metadata/book-uuid
|
||||
Authorization: Bearer device-token
|
||||
```
|
||||
|
||||
### Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "book-uuid",
|
||||
"title": "Book Title",
|
||||
"authors": ["Author Name"],
|
||||
"progress": {
|
||||
"percentage": 0.42,
|
||||
"character": 15432,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3,
|
||||
"chapter_progress": 0.234
|
||||
},
|
||||
"annotations": {
|
||||
"highlights": [...],
|
||||
"notes": [...],
|
||||
"bookmarks": [...]
|
||||
},
|
||||
"last_sync": "2026-01-30T20:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,39 @@
|
||||
# Change Password
|
||||
|
||||
Change the current user's password.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/password`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| current_password | string | Yes | Current password |
|
||||
| new_password | string | Yes | New password (min 8 chars) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"current_password": "oldPassword",
|
||||
"new_password": "NewSecureP@ss123!"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (204 No Content)
|
||||
|
||||
Password changed successfully.
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input or weak password |
|
||||
| 401 | Current password is incorrect |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,45 @@
|
||||
# Get Current User Profile
|
||||
|
||||
Retrieve the current authenticated user's profile.
|
||||
|
||||
**Endpoint**: `GET /api/users/me`
|
||||
**Auth**: Required
|
||||
|
||||
## Request Headers
|
||||
|
||||
| Header | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| Authorization | string | Yes | Bearer token |
|
||||
|
||||
### Example Request
|
||||
|
||||
```http
|
||||
GET /api/users/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user",
|
||||
"max_devices": 10,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,50 @@
|
||||
# Update Profile
|
||||
|
||||
Update the current user's profile information.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/profile`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| first_name | string | No | User's first name |
|
||||
| last_name | string | No | User's last name |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"first_name": "John",
|
||||
"last_name": "Smith"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"first_name": "John",
|
||||
"last_name": "Smith",
|
||||
"theme": "tokyo-night",
|
||||
"role": "user",
|
||||
"max_devices": 10,
|
||||
"created_at": "2026-01-31T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid input data |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,44 @@
|
||||
# Update Theme
|
||||
|
||||
Update the current user's theme preference.
|
||||
|
||||
**Endpoint**: `PUT /api/users/me/theme`
|
||||
**Auth**: Required
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| theme | string | Yes | Theme name (e.g., "tokyo-night", "dracula") |
|
||||
|
||||
### Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "dracula"
|
||||
}
|
||||
```
|
||||
|
||||
## Response (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"username": "john",
|
||||
"theme": "dracula",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid theme name |
|
||||
| 401 | Invalid or expired token |
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,109 @@
|
||||
# WebSocket Protocol
|
||||
|
||||
Real-time sync events broadcast to connected clients.
|
||||
|
||||
## Connect to WebSocket
|
||||
|
||||
**Endpoint**: `WS /ws/sync?token=<token>`
|
||||
|
||||
### Connection Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|-----------|-------------|
|
||||
| token | string | Yes | JWT authentication token |
|
||||
|
||||
### Example Connection
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('wss://bookhoard.com/ws/sync?token=eyJhbG...');
|
||||
```
|
||||
|
||||
## Message Format
|
||||
|
||||
All messages are JSON objects with a `type` field.
|
||||
|
||||
### Client → Server Messages
|
||||
|
||||
#### Ping (Heartbeat)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
Keep connection alive. Server responds with `pong`.
|
||||
|
||||
### Server → Client Messages
|
||||
|
||||
#### Progress Update
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "progress_update",
|
||||
"timestamp": "2026-01-31T10:00:00Z",
|
||||
"data": {
|
||||
"book_id": "uuid",
|
||||
"progress": {
|
||||
"percentage": 0.45678,
|
||||
"epubcfi": "epubcfi(/6/4/2:15)",
|
||||
"chapter": 3
|
||||
},
|
||||
"annotations": {}
|
||||
},
|
||||
"source_device": {
|
||||
"id": "device-uuid",
|
||||
"name": "My Kobo",
|
||||
"type": "kobo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Broadcast when any device updates reading progress.
|
||||
|
||||
#### Conflict Detected
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "conflict",
|
||||
"timestamp": "2026-01-31T10:00:00Z",
|
||||
"data": {
|
||||
"book_id": "uuid",
|
||||
"conflict_id": "uuid",
|
||||
"conflict_type": "progress"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Broadcast when a sync conflict is detected.
|
||||
|
||||
#### Pong
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "pong"
|
||||
}
|
||||
```
|
||||
|
||||
Server response to client `ping`.
|
||||
|
||||
## Connection Management
|
||||
|
||||
- **Heartbeat**: Send `ping` every 30 seconds
|
||||
- **Reconnect**: Use exponential backoff if connection drops
|
||||
- **Authentication**: Token must be valid for connection
|
||||
- **Rate Limits**: 60 messages/minute per connection
|
||||
|
||||
## Error Handling
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"message": "Invalid token",
|
||||
"code": "AUTH_FAILED"
|
||||
}
|
||||
```
|
||||
|
||||
## Try It Out
|
||||
|
||||
<!-- API Explorer will be inserted here in Phase 3 -->
|
||||
@@ -0,0 +1,493 @@
|
||||
# Collections API Documentation
|
||||
|
||||
Complete API reference for collection management endpoints.
|
||||
|
||||
**Base Path**: `/api/collections`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Collections allow you to organize your books into custom categories with:
|
||||
- **Auto-assignment rules**: Automatically add books matching criteria
|
||||
- **View settings**: Per-device display preferences
|
||||
- **Shelf mappings**: Sync to device-specific shelves (Kobo, KOReader)
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### List All Collections
|
||||
|
||||
**Endpoint**: `GET /api/collections`
|
||||
**Authentication**: Required
|
||||
**Description**: Get all collections for the authenticated user
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid",
|
||||
"sort_order": "name",
|
||||
"items_per_page": 24
|
||||
}
|
||||
},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Create Collection
|
||||
|
||||
**Endpoint**: `POST /api/collections`
|
||||
**Authentication**: Required
|
||||
**Description**: Create a new collection
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "To Read",
|
||||
"description": "Books I want to read soon",
|
||||
"color": "#00FF00",
|
||||
"icon": "📖",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "tags",
|
||||
"operator": "contains",
|
||||
"value": "to-read",
|
||||
"priority": 1,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
},
|
||||
"koreader": {
|
||||
"view_mode": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `name` (required): Collection name (max 255 chars)
|
||||
- `description` (optional): Collection description
|
||||
- `color` (optional): Hex color code (e.g., "#FF5733")
|
||||
- `icon` (optional): Emoji icon (e.g., "🚀", "📖")
|
||||
- `auto_assign_rules` (optional): Array of rule objects
|
||||
- `view_settings` (optional): Per-device display preferences
|
||||
|
||||
**Rule Object**:
|
||||
- `field`: Field to match on (genre, author, series, language, publisher, copyright_year, tags)
|
||||
- `operator`: Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than)
|
||||
- `value`: Value to compare against
|
||||
- `priority`: Rule priority (1 = highest)
|
||||
- `enabled`: Whether rule is active
|
||||
|
||||
**Response** (201 Created): Collection object
|
||||
|
||||
### Get Collection Details
|
||||
|
||||
**Endpoint**: `GET /api/collections/{id}`
|
||||
**Authentication**: Required
|
||||
**Description**: Get single collection with all books
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Science Fiction",
|
||||
"description": "My sci-fi collection",
|
||||
"color": "#FF5733",
|
||||
"icon": "🚀",
|
||||
"books": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg"
|
||||
}
|
||||
],
|
||||
"auto_assign_rules": [],
|
||||
"view_settings": {},
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Update Collection
|
||||
|
||||
**Endpoint**: `PUT /api/collections/{id}`
|
||||
**Authentication**: Required
|
||||
**Description**: Update collection details
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Request Body**: All fields are optional
|
||||
```json
|
||||
{
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "Updated description",
|
||||
"color": "#BLUE",
|
||||
"icon": "🌟"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK): Updated collection object
|
||||
|
||||
### Delete Collection
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{id}`
|
||||
**Authentication**: Required
|
||||
**Description**: Delete a collection (books are NOT deleted)
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
---
|
||||
|
||||
## Collection Books
|
||||
|
||||
### Add Books to Collection
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/books`
|
||||
**Authentication**: Required
|
||||
**Description**: Add one or more books to a collection
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"book_ids": [
|
||||
"660e8400-e29b-41d4-a716-446655440000",
|
||||
"770e8400-e29b-41d4-a716-446655440000",
|
||||
"880e8400-e29b-41d4-a716-446655440000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
### Remove Book from Collection
|
||||
|
||||
**Endpoint**: `DELETE /api/collections/{id}/books/{bookId}`
|
||||
**Authentication**: Required
|
||||
**Description**: Remove a single book from a collection
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
- `bookId`: Media Item UUID
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
### Bulk Remove Books
|
||||
|
||||
**Endpoint**: `POST /api/collections/{id}/books/bulk-remove`
|
||||
**Authentication**: Required
|
||||
**Description**: Remove multiple books at once (efficient)
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"book_ids": [
|
||||
"660e8400-e29b-41d4-a716-446655440000",
|
||||
"770e8400-e29b-41d4-a716-446655440000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"removed": 2,
|
||||
"total": 2
|
||||
}
|
||||
```
|
||||
|
||||
**Example**: Removing 2 books, both successfully removed
|
||||
|
||||
### Get Collection Books
|
||||
|
||||
**Endpoint**: `GET /api/collections/{id}/books`
|
||||
**Authentication**: Required
|
||||
**Description**: Get all books in a collection
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Collection UUID
|
||||
|
||||
**Query Parameters**:
|
||||
- `limit` (optional): Number of books to return (default: 50)
|
||||
- `offset` (optional): Number of books to skip (default: 0)
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"books": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg",
|
||||
"added_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Collection Rules
|
||||
|
||||
### Test Collection Rules
|
||||
|
||||
**Endpoint**: `POST /api/collections/test-rules`
|
||||
**Authentication**: Required
|
||||
**Description**: Test which books would match given rules (without saving)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Science Fiction"
|
||||
},
|
||||
{
|
||||
"field": "author",
|
||||
"operator": "contains",
|
||||
"value": "Asimov"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Fields**:
|
||||
- `genre`: Book genre
|
||||
- `author`: Book author
|
||||
- `series`: Book series name
|
||||
- `language`: Book language
|
||||
- `publisher`: Publisher name
|
||||
- `copyright_year`: Publication year (numeric comparison)
|
||||
- `tags`: Book tags
|
||||
|
||||
**Supported Operators**:
|
||||
- `equals`: Exact match
|
||||
- `not_equals`: Not equal
|
||||
- `contains`: Contains substring (case-insensitive)
|
||||
- `not_contains`: Does not contain
|
||||
- `starts_with`: Starts with (case-insensitive)
|
||||
- `ends_with`: Ends with (case-insensitive)
|
||||
- `greater_than`: Greater than (numeric)
|
||||
- `less_than`: Less than (numeric)
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"matches": [
|
||||
{
|
||||
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "Foundation",
|
||||
"author": "Isaac Asimov",
|
||||
"cover_image_path": "/covers/foundation.jpg",
|
||||
"match_reason": "Matched rule: genre equals Science Fiction"
|
||||
}
|
||||
],
|
||||
"total": 42
|
||||
}
|
||||
```
|
||||
|
||||
**Use Case**: Test rules before creating collection to verify correct book matching
|
||||
|
||||
---
|
||||
|
||||
## Device Shelf Mappings
|
||||
|
||||
### Get Device Shelf Mappings
|
||||
|
||||
**Endpoint**: `GET /api/devices/{deviceId}/collections`
|
||||
**Authentication**: Required
|
||||
**Description**: Get all collection-to-shelf mappings for a device
|
||||
|
||||
**Path Parameters**:
|
||||
- `deviceId`: Device UUID
|
||||
|
||||
**Response** (200 OK):
|
||||
```json
|
||||
{
|
||||
"mappings": [
|
||||
{
|
||||
"id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"collection_name": "Science Fiction",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional",
|
||||
"created_at": "2026-02-01T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Create Shelf Mapping
|
||||
|
||||
**Endpoint**: `POST /api/devices/{deviceId}/collections`
|
||||
**Authentication**: Required
|
||||
**Description**: Map a collection to a device shelf
|
||||
|
||||
**Path Parameters**:
|
||||
- `deviceId`: Device UUID
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"device_shelf_name": "Sci-Fi",
|
||||
"sync_direction": "bidirectional"
|
||||
}
|
||||
```
|
||||
|
||||
**Sync Directions**:
|
||||
- `bidirectional`: Sync both ways between Bookhoard and device
|
||||
- `book_to_hoard`: Bookhoard → Device only
|
||||
- `device_to_hoard`: Device → Bookhoard only
|
||||
- `none`: No sync (mapping only)
|
||||
|
||||
**Response** (201 Created): Mapping object
|
||||
|
||||
### Update Shelf Mapping
|
||||
|
||||
**Endpoint**: `PUT /api/devices/{deviceId}/collections/{collectionId}`
|
||||
**Authentication**: Required
|
||||
**Description**: Update existing shelf mapping
|
||||
|
||||
**Path Parameters**:
|
||||
- `deviceId`: Device UUID
|
||||
- `collectionId`: Collection UUID
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"device_shelf_name": "Science Fiction",
|
||||
"sync_direction": "book_to_hoard"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200 OK): Updated mapping object
|
||||
|
||||
### Delete Shelf Mapping
|
||||
|
||||
**Endpoint**: `DELETE /api/devices/{deviceId}/collections/{collectionId}`
|
||||
**Authentication**: Required
|
||||
**Description**: Remove shelf mapping
|
||||
|
||||
**Path Parameters**:
|
||||
- `deviceId`: Device UUID
|
||||
- `collectionId`: Collection UUID
|
||||
|
||||
**Response** (204 No Content)
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints may return these errors:
|
||||
|
||||
**400 Bad Request**:
|
||||
```json
|
||||
{
|
||||
"error": "invalid request: validation failed"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"error": "authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"error": "collection not found"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"error": "internal server error"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **Authenticated**: 100 requests per minute
|
||||
- **Unauthenticated**: 10 requests per minute
|
||||
|
||||
Headers included:
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 95
|
||||
X-RateLimit-Reset: 1643723400
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bruno Tests
|
||||
|
||||
Complete API tests available in `bruno/collections/`:
|
||||
|
||||
- `Get Collections.bru`
|
||||
- `Create Collection.bru`
|
||||
- `Get Collection.bru`
|
||||
- `Update Collection.bru`
|
||||
- `Delete Collection.bru`
|
||||
- `Add Books to Collection.bru`
|
||||
- `Remove Book from Collection.bru`
|
||||
- `Get Book Collections.bru`
|
||||
- `Test Collection Rules.bru`
|
||||
- `Bulk Remove Books.bru`
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
bruno run bruno/collections/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-02-01
|
||||
**Version**: 1.0
|
||||
@@ -0,0 +1,675 @@
|
||||
# WebSocket API Documentation
|
||||
|
||||
**Base URL**: `ws://localhost:8765/ws/sync`
|
||||
**Protocol**: WebSocket (RFC 6455)
|
||||
**Authentication**: Required (JWT token or device token)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Authentication](#authentication)
|
||||
2. [Connection](#connection)
|
||||
3. [Message Format](#message-format)
|
||||
4. [Message Types](#message-types)
|
||||
5. [Client Implementation Guide](#client-implementation-guide)
|
||||
6. [Examples](#examples)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Option 1: JWT Token (Web/Mobile Clients)
|
||||
|
||||
Pass JWT token as query parameter:
|
||||
|
||||
```
|
||||
ws://localhost:8765/ws/sync?token=<your_jwt_token>
|
||||
```
|
||||
|
||||
**How to get JWT token**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8765/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"user@example.com","password":"password"}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"user": {
|
||||
"id": "user-uuid",
|
||||
"email": "user@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Device Token (Kobo, KOReader, etc.)
|
||||
|
||||
Pass device token in Authorization header:
|
||||
|
||||
```
|
||||
ws://localhost:8765/ws/sync?token=any-value
|
||||
Authorization: Bearer <device_token>
|
||||
```
|
||||
|
||||
**How to get device token**:
|
||||
Device tokens are generated when devices are registered via the API.
|
||||
|
||||
---
|
||||
|
||||
## Connection
|
||||
|
||||
### Step 1: Connect to WebSocket
|
||||
|
||||
**JavaScript Example**:
|
||||
```javascript
|
||||
const token = "your-jwt-token";
|
||||
const ws = new WebSocket(`ws://localhost:8765/ws/sync?token=${token}`);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("Connected to Bookhoard WebSocket");
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
console.log("WebSocket closed:", event.code, event.reason);
|
||||
};
|
||||
```
|
||||
|
||||
**Go Example**:
|
||||
```go
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
url := "ws://localhost:8765/ws/sync?token=" + token
|
||||
ws, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
log.Fatal("Dial error:", err)
|
||||
}
|
||||
defer ws.Close()
|
||||
```
|
||||
|
||||
**Python Example**:
|
||||
```python
|
||||
import websocket
|
||||
|
||||
token = "your-jwt-token"
|
||||
url = f"ws://localhost:8765/ws/sync?token={token}"
|
||||
|
||||
def on_message(ws, message):
|
||||
print(f"Received: {message}")
|
||||
|
||||
def on_error(ws, error):
|
||||
print(f"Error: {error}")
|
||||
|
||||
def on_close(ws, close_status_code, close_msg):
|
||||
print("Closed")
|
||||
|
||||
def on_open(ws):
|
||||
print("Connected")
|
||||
|
||||
ws = websocket.WebSocketApp(
|
||||
url,
|
||||
on_open=on_open,
|
||||
on_message=on_message,
|
||||
on_error=on_error,
|
||||
on_close=on_close
|
||||
)
|
||||
|
||||
ws.run_forever()
|
||||
```
|
||||
|
||||
### Step 2: Handle Initial State
|
||||
|
||||
Immediately after connecting, you'll receive the initial state:
|
||||
|
||||
```javascript
|
||||
ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
if (message.type === "initial_state") {
|
||||
console.log("Initial progress:", message.data.progress);
|
||||
console.log("Connected devices:", message.data.devices);
|
||||
|
||||
// Store initial state
|
||||
initialState = message.data;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Step 3: Handle Real-time Updates
|
||||
|
||||
Listen for subsequent updates:
|
||||
|
||||
```javascript
|
||||
ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
switch (message.type) {
|
||||
case "progress_update":
|
||||
handleProgressUpdate(message);
|
||||
break;
|
||||
case "annotation_update":
|
||||
handleAnnotationUpdate(message);
|
||||
break;
|
||||
case "conflict":
|
||||
handleConflict(message);
|
||||
break;
|
||||
case "heartbeat":
|
||||
// Server ping, ignore
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown message type:", message.type);
|
||||
}
|
||||
};
|
||||
|
||||
function handleProgressUpdate(message) {
|
||||
const { book_id, percentage } = message.data;
|
||||
const source = message.source_device;
|
||||
|
||||
console.log(`Progress update for ${book_id}: ${percentage * 100}%`);
|
||||
console.log(`Source: ${source.name} (${source.type})`);
|
||||
|
||||
// Update UI
|
||||
updateProgressBar(book_id, percentage);
|
||||
}
|
||||
|
||||
function handleAnnotationUpdate(message) {
|
||||
const { book_id, annotation_type, data } = message.data;
|
||||
const source = message.source_device;
|
||||
|
||||
console.log(`Annotation update for ${book_id}: ${annotation_type}`);
|
||||
console.log(`Source: ${source.name}`);
|
||||
|
||||
// Update UI
|
||||
showAnnotation(book_id, data);
|
||||
}
|
||||
|
||||
function handleConflict(message) {
|
||||
const { book_id, notification_type, conflict_id } = message.data;
|
||||
|
||||
console.log(`Conflict detected for ${book_id}: ${notification_type}`);
|
||||
|
||||
// Show conflict resolution UI
|
||||
showConflictDialog(book_id, conflict_id);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message Format
|
||||
|
||||
All messages follow this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message_type",
|
||||
"timestamp": "2026-02-01T12:00:00Z",
|
||||
"data": {
|
||||
// Message-specific data
|
||||
},
|
||||
"source_device": {
|
||||
"id": "device-uuid",
|
||||
"name": "Device Name",
|
||||
"type": "kobo|koreader|web|mobile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `type` (string, required): Message type identifier
|
||||
- `timestamp` (string, required): ISO 8601 timestamp
|
||||
- `data` (object, required): Message payload
|
||||
- `source_device` (object, optional): Device that sent the update (not present for initial_state)
|
||||
|
||||
---
|
||||
|
||||
## Message Types
|
||||
|
||||
### 1. initial_state
|
||||
|
||||
**When**: Immediately after connection
|
||||
|
||||
**Purpose**: Send current progress for all user's books
|
||||
|
||||
**Data Structure**:
|
||||
```json
|
||||
{
|
||||
"type": "initial_state",
|
||||
"timestamp": "2026-02-01T12:00:00Z",
|
||||
"data": {
|
||||
"progress": {
|
||||
"<book_uuid>": {
|
||||
"percentage": 0.5,
|
||||
"current_page": 150,
|
||||
"total_pages": 300,
|
||||
"last_read": "2026-02-01T11:30:00Z"
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"kobo": 2,
|
||||
"koreader": 1,
|
||||
"web": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `progress` (object): Map of book UUID → progress data
|
||||
- `percentage` (number): 0.0 to 1.0
|
||||
- `current_page` (number): Current page number
|
||||
- `total_pages` (number): Total pages in book
|
||||
- `last_read` (string): ISO 8601 timestamp of last read
|
||||
- `devices` (object): Connection statistics by device type
|
||||
|
||||
### 2. progress_update
|
||||
|
||||
**When**: Any device syncs reading progress
|
||||
|
||||
**Purpose**: Notify all connected clients of progress change
|
||||
|
||||
**Data Structure**:
|
||||
```json
|
||||
{
|
||||
"type": "progress_update",
|
||||
"timestamp": "2026-02-01T12:00:00Z",
|
||||
"data": {
|
||||
"book_id": "book-uuid-1",
|
||||
"percentage": 0.75
|
||||
},
|
||||
"source_device": {
|
||||
"id": "device-uuid-1",
|
||||
"name": "My Kobo Clara",
|
||||
"type": "kobo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `book_id` (string): UUID of book with updated progress
|
||||
- `percentage` (number): New progress value (0.0 to 1.0)
|
||||
- `source_device` (object): Device that sent the update
|
||||
|
||||
**Use Cases**:
|
||||
- Update progress bar in real-time
|
||||
- Sync reading position across devices
|
||||
- Update "currently reading" lists
|
||||
|
||||
### 3. annotation_update
|
||||
|
||||
**When**: Any device syncs annotations, bookmarks, or highlights
|
||||
|
||||
**Purpose**: Share annotations across devices
|
||||
|
||||
**Data Structure**:
|
||||
```json
|
||||
{
|
||||
"type": "annotation_update",
|
||||
"timestamp": "2026-02-01T12:00:00Z",
|
||||
"data": {
|
||||
"book_id": "book-uuid-1",
|
||||
"annotation_type": "bookmark",
|
||||
"data": {
|
||||
"page": 150,
|
||||
"text": "Great quote on page 150",
|
||||
"chapter": 5,
|
||||
"created_at": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
},
|
||||
"source_device": {
|
||||
"id": "device-uuid-1",
|
||||
"name": "My Kobo Clara",
|
||||
"type": "kobo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `book_id` (string): UUID of book
|
||||
- `annotation_type` (string): Type of annotation (bookmark, highlight, note)
|
||||
- `data` (object): Annotation-specific data
|
||||
- `page` (number): Page number
|
||||
- `text` (string): Annotated text or note
|
||||
- `chapter` (number): Chapter number (optional)
|
||||
- `created_at` (string): ISO 8601 timestamp
|
||||
- `source_device` (object): Device that created the annotation
|
||||
|
||||
**Use Cases**:
|
||||
- Show bookmarks on all devices
|
||||
- Share highlights between devices
|
||||
- Display reading notes
|
||||
|
||||
### 4. conflict
|
||||
|
||||
**When**: Sync conflict is detected
|
||||
|
||||
**Purpose**: Notify user of conflicting updates
|
||||
|
||||
**Data Structure**:
|
||||
```json
|
||||
{
|
||||
"type": "conflict",
|
||||
"timestamp": "2026-02-01T12:00:00Z",
|
||||
"data": {
|
||||
"book_id": "book-uuid-1",
|
||||
"notification_type": "progress_conflict",
|
||||
"conflict_id": "conflict-uuid-1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields**:
|
||||
- `book_id` (string): UUID of book with conflict
|
||||
- `notification_type` (string): Type of conflict (progress_conflict, annotation_conflict)
|
||||
- `conflict_id` (string): UUID of conflict record
|
||||
|
||||
**Use Cases**:
|
||||
- Prompt user to resolve conflict
|
||||
- Show conflict resolution UI
|
||||
- Log conflict for manual review
|
||||
|
||||
### 5. heartbeat
|
||||
|
||||
**When**: Server sends ping every 30 seconds
|
||||
|
||||
**Purpose**: Keep connection alive
|
||||
|
||||
**Data Structure**:
|
||||
```json
|
||||
{
|
||||
"type": "heartbeat",
|
||||
"timestamp": "2026-02-01T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Handling**: Clients should respond automatically (handled by WebSocket protocol)
|
||||
|
||||
---
|
||||
|
||||
## Client Implementation Guide
|
||||
|
||||
### Reconnection Strategy
|
||||
|
||||
Implement exponential backoff for reconnection:
|
||||
|
||||
```javascript
|
||||
let reconnectAttempts = 0;
|
||||
const maxReconnectAttempts = 10;
|
||||
const baseReconnectDelay = 1000; // 1 second
|
||||
|
||||
function connect() {
|
||||
const token = getAuthToken();
|
||||
const ws = new WebSocket(`ws://localhost:8765/ws/sync?token=${token}`);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("Connected");
|
||||
reconnectAttempts = 0; // Reset on successful connection
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
const delay = baseReconnectDelay * Math.pow(2, reconnectAttempts);
|
||||
console.log(`Reconnecting in ${delay}ms...`);
|
||||
|
||||
setTimeout(() => {
|
||||
reconnectAttempts++;
|
||||
connect();
|
||||
}, delay);
|
||||
} else {
|
||||
console.error("Max reconnection attempts reached");
|
||||
}
|
||||
};
|
||||
|
||||
ws.onmessage = handleIncomingMessage;
|
||||
|
||||
return ws;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Handle common errors:
|
||||
|
||||
```javascript
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
|
||||
// Check specific error types
|
||||
if (error.code === 1006) {
|
||||
console.error("Abnormal closure - server may be down");
|
||||
} else if (error.code === 1008) {
|
||||
console.error("Policy violation - check authentication");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Message Queueing
|
||||
|
||||
Queue messages while disconnected:
|
||||
|
||||
```javascript
|
||||
let messageQueue = [];
|
||||
let isConnected = false;
|
||||
|
||||
function queueMessage(message) {
|
||||
if (isConnected) {
|
||||
ws.send(JSON.stringify(message));
|
||||
} else {
|
||||
messageQueue.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
isConnected = true;
|
||||
|
||||
// Send queued messages
|
||||
while (messageQueue.length > 0) {
|
||||
const message = messageQueue.shift();
|
||||
ws.send(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
isConnected = false;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete JavaScript Client
|
||||
|
||||
```javascript
|
||||
class BookhoardWebSocket {
|
||||
constructor(token) {
|
||||
this.token = token;
|
||||
this.ws = null;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 10;
|
||||
this.messageHandlers = {};
|
||||
}
|
||||
|
||||
connect() {
|
||||
const url = `ws://localhost:8765/ws/sync?token=${this.token}`;
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log("Connected to Bookhoard");
|
||||
this.reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handleMessage(message);
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.reconnect();
|
||||
};
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
const handler = this.messageHandlers[message.type];
|
||||
if (handler) {
|
||||
handler(message);
|
||||
}
|
||||
}
|
||||
|
||||
on(messageType, callback) {
|
||||
this.messageHandlers[messageType] = callback;
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
const delay = 1000 * Math.pow(2, this.reconnectAttempts);
|
||||
console.log(`Reconnecting in ${delay}ms...`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.reconnectAttempts++;
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const token = "your-jwt-token";
|
||||
const client = new BookhoardWebSocket(token);
|
||||
|
||||
client.on("initial_state", (message) => {
|
||||
console.log("Initial state received:", message.data);
|
||||
updateUIWithInitialState(message.data);
|
||||
});
|
||||
|
||||
client.on("progress_update", (message) => {
|
||||
console.log("Progress updated:", message.data);
|
||||
updateProgressBar(message.data.book_id, message.data.percentage);
|
||||
});
|
||||
|
||||
client.on("annotation_update", (message) => {
|
||||
console.log("Annotation updated:", message.data);
|
||||
showNotification("New annotation from " + message.source_device.name);
|
||||
});
|
||||
|
||||
client.on("conflict", (message) => {
|
||||
console.log("Conflict detected:", message.data);
|
||||
showConflictDialog(message.data);
|
||||
});
|
||||
|
||||
client.connect();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Fails
|
||||
|
||||
**Problem**: Can't connect to WebSocket
|
||||
|
||||
**Solutions**:
|
||||
1. Check JWT token is valid
|
||||
2. Verify server is running: `curl http://localhost:8765/health`
|
||||
3. Check firewall settings
|
||||
4. Verify WebSocket URL format
|
||||
|
||||
### Connection Drops Frequently
|
||||
|
||||
**Problem**: WebSocket disconnects unexpectedly
|
||||
|
||||
**Solutions**:
|
||||
1. Check network stability
|
||||
2. Verify server keepalive settings
|
||||
3. Implement reconnection logic (see above)
|
||||
4. Check server logs for errors
|
||||
|
||||
### No Messages Received
|
||||
|
||||
**Problem**: Connected but no messages
|
||||
|
||||
**Solutions**:
|
||||
1. Check onmessage handler is registered
|
||||
2. Verify initial_state message received
|
||||
3. Test with manual progress update via API
|
||||
4. Check browser console for errors
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
**Problem**: 401 Unauthorized
|
||||
|
||||
**Solutions**:
|
||||
1. Verify JWT token is not expired
|
||||
2. Check token has correct claims (user_id)
|
||||
3. For device tokens, verify Authorization header format
|
||||
4. Regenerate token via `/api/auth/login`
|
||||
|
||||
### High Memory Usage
|
||||
|
||||
**Problem**: Client memory increases over time
|
||||
|
||||
**Solutions**:
|
||||
1. Clean up old messages
|
||||
2. Don't store entire message history
|
||||
3. Use weak references for large data
|
||||
4. Implement message pagination
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Debounce UI Updates**: Don't update DOM on every message
|
||||
```javascript
|
||||
const debouncedUpdate = debounce(updateUI, 100);
|
||||
client.on("progress_update", (message) => {
|
||||
debouncedUpdate(message);
|
||||
});
|
||||
```
|
||||
|
||||
2. **Use Web Workers** for heavy processing
|
||||
3. **Limit Message History**: Keep only last N messages
|
||||
4. **Virtual Scrolling**: For large lists of updates
|
||||
5. **Connection Pooling**: Reuse connections across tabs
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use WSS in Production**: Always use secure WebSocket (wss://) in production
|
||||
2. **Validate Tokens**: Always verify JWT tokens on server
|
||||
3. **Rate Limiting**: Implement message rate limiting per connection
|
||||
4. **Input Sanitization**: Sanitize all message data before displaying
|
||||
5. **Origin Checks**: Validate WebSocket origin on server
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- GitHub Issues: [Bookhoard Repository]
|
||||
- Documentation: [Bookhoard Docs]
|
||||
- API Reference: [Bookhoard API Docs]
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-02-01
|
||||
**Version**: 1.0.0
|
||||
**Protocol**: WebSocket (RFC 6455)
|
||||
Reference in New Issue
Block a user