Files
storycove/docs/API.md
2025-07-24 08:51:45 +02:00

21 KiB

StoryCove REST API Documentation

Overview

StoryCove provides a comprehensive REST API for managing stories, authors, tags, and series. All endpoints use JSON for request and response data, follow RESTful conventions, and require JWT authentication (except for login).

Base URL: /api Authentication: JWT tokens via httpOnly cookies Content-Type: application/json (unless otherwise specified)

Authentication

All API endpoints (except authentication endpoints) require JWT authentication. Tokens are provided via httpOnly cookies and expire after 24 hours.

Authentication Flow

  1. Login: POST /api/auth/login with password
  2. Receive Token: Server sets httpOnly cookie with JWT
  3. Access Protected Endpoints: Cookie automatically sent with requests
  4. Token Expiry: 24 hours, requires re-authentication

Authentication Endpoints

POST /api/auth/login

Description: Authenticate with application password and receive JWT token.

Request Body:

{
  "password": "string"  // Required, application password
}

Response (200 OK):

{
  "message": "Authentication successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response (401 Unauthorized):

{
  "error": "Invalid password"
}

Side Effects: Sets httpOnly cookie token with 24-hour expiration.

POST /api/auth/logout

Description: Clear authentication token and logout.

Request Body: None

Response (200 OK):

{
  "message": "Logged out successfully"
}

Side Effects: Clears the token cookie.

GET /api/auth/verify

Description: Verify if current JWT token is valid.

Authentication: Required

Response (200 OK):

{
  "message": "Token is valid"
}

Response (401 Unauthorized):

{
  "error": "Token is invalid or expired"
}

Story Management Endpoints

GET /api/stories

Description: Get paginated list of all stories.

Query Parameters:

  • page (integer, default: 0): Page number (0-based)
  • size (integer, default: 20): Page size (max: 100)
  • sortBy (string, default: "createdAt"): Sort field
  • sortDir (string, default: "desc"): Sort direction (asc/desc)

Response (200 OK):

{
  "content": [StoryDto],
  "pageable": {
    "sort": { "sorted": true, "unsorted": false },
    "pageNumber": 0,
    "pageSize": 20,
    "offset": 0,
    "paged": true,
    "unpaged": false
  },
  "totalElements": 150,
  "totalPages": 8,
  "last": false,
  "first": true,
  "numberOfElements": 20,
  "size": 20,
  "number": 0,
  "sort": { "sorted": true, "unsorted": false },
  "empty": false
}

GET /api/stories/{id}

Description: Get specific story by UUID.

Path Parameters:

  • id (UUID): Story identifier

Response (200 OK): StoryDto

Response (404 Not Found):

{
  "error": "Story not found with id: {id}"
}

POST /api/stories

Description: Create a new story.

Request Body:

{
  "title": "string",                    // Required
  "summary": "string",                  // Optional
  "description": "string",              // Optional
  "contentHtml": "<p>Story content</p>", // Optional
  "sourceUrl": "https://example.com",   // Optional
  "volume": 1,                          // Optional, integer
  "authorId": "uuid",                   // Optional, use either authorId or authorName
  "authorName": "Author Name",          // Optional, creates author if doesn't exist
  "seriesId": "uuid",                   // Optional, use either seriesId or seriesName
  "seriesName": "Series Name",          // Optional, creates series if doesn't exist
  "tagNames": ["tag1", "tag2"]          // Optional, creates tags if they don't exist
}

Response (201 Created): StoryDto

Response (400 Bad Request):

{
  "error": "Title is required",
  "details": {
    "title": "must not be blank"
  }
}

PUT /api/stories/{id}

Description: Update existing story. All fields are optional.

Path Parameters:

  • id (UUID): Story identifier

Request Body: Same as POST, all fields optional

Response (200 OK): StoryDto

DELETE /api/stories/{id}

Description: Delete story and all associated relationships.

Path Parameters:

  • id (UUID): Story identifier

Response (200 OK):

{
  "message": "Story deleted successfully"
}

POST /api/stories/{id}/cover

Description: Upload cover image for story.

Path Parameters:

  • id (UUID): Story identifier

Content-Type: multipart/form-data

Form Parameters:

  • file (File): Image file (JPG, PNG, WebP, max 5MB)

Response (200 OK):

{
  "message": "Cover uploaded successfully",
  "coverPath": "covers/story-uuid-cover.jpg",
  "coverUrl": "/api/files/images/covers/story-uuid-cover.jpg"
}

DELETE /api/stories/{id}/cover

Description: Remove cover image from story.

Path Parameters:

  • id (UUID): Story identifier

Response (200 OK):

{
  "message": "Cover removed successfully"
}

POST /api/stories/{id}/rating

Description: Set story rating.

Path Parameters:

  • id (UUID): Story identifier

Request Body:

{
  "rating": 4  // Integer, 1-5
}

Response (200 OK): StoryDto

Tag Management for Stories

POST /api/stories/{id}/tags/{tagId}

Description: Add existing tag to story.

Path Parameters:

  • id (UUID): Story identifier
  • tagId (UUID): Tag identifier

Response (200 OK): StoryDto

DELETE /api/stories/{id}/tags/{tagId}

Description: Remove tag from story.

Path Parameters:

  • id (UUID): Story identifier
  • tagId (UUID): Tag identifier

Response (200 OK): StoryDto

Story Search and Filtering

GET /api/stories/search

Description: Search stories using Typesense full-text search.

Query Parameters:

  • query (string, required): Search query
  • page (integer, default: 0): Page number
  • size (integer, default: 20): Page size
  • authors (string[]): Filter by author names
  • tags (string[]): Filter by tag names
  • minRating (integer): Minimum rating filter
  • maxRating (integer): Maximum rating filter
  • sortBy (string): Sort field
  • sortDir (string): Sort direction

Response (200 OK):

{
  "results": [StorySearchDto],
  "totalHits": 42,
  "page": 0,
  "perPage": 20,
  "query": "search terms",
  "searchTimeMs": 15
}

GET /api/stories/search/suggestions

Description: Get search query suggestions.

Query Parameters:

  • query (string, required): Partial search query
  • limit (integer, default: 5): Number of suggestions

Response (200 OK):

["suggestion 1", "suggestion 2", "suggestion 3"]

Story Collections

GET /api/stories/author/{authorId}

Description: Get stories by specific author.

Path Parameters:

  • authorId (UUID): Author identifier

Query Parameters: Standard pagination parameters

Response (200 OK): Paginated StoryDto list

GET /api/stories/series/{seriesId}

Description: Get stories in series, ordered by volume.

Path Parameters:

  • seriesId (UUID): Series identifier

Response (200 OK): StoryDto[] (ordered by volume)

GET /api/stories/tags/{tagName}

Description: Get stories with specific tag.

Path Parameters:

  • tagName (string): Tag name

Query Parameters: Standard pagination parameters

Response (200 OK): Paginated StoryDto list

GET /api/stories/recent

Description: Get recently added stories.

Query Parameters:

  • limit (integer, default: 10): Number of stories

Response (200 OK): StoryDto[]

GET /api/stories/top-rated

Description: Get top-rated stories.

Query Parameters:

  • limit (integer, default: 10): Number of stories

Response (200 OK): StoryDto[]


Author Management Endpoints

GET /api/authors

Description: Get paginated list of all authors.

Query Parameters: Standard pagination and sorting parameters

Response (200 OK): Paginated AuthorDto list

GET /api/authors/{id}

Description: Get specific author by UUID.

Path Parameters:

  • id (UUID): Author identifier

Response (200 OK): AuthorDto

POST /api/authors

Description: Create new author.

Request Body:

{
  "name": "Author Name",              // Required, unique
  "notes": "Notes about the author",  // Optional
  "urls": ["https://example.com"]      // Optional, list of URLs
}

Response (201 Created): AuthorDto

PUT /api/authors/{id} (JSON)

Description: Update author with JSON data.

Path Parameters:

  • id (UUID): Author identifier

Content-Type: application/json

Request Body:

{
  "name": "Updated Name",    // Optional
  "notes": "Updated notes",  // Optional
  "urls": ["url1", "url2"],   // Optional, replaces all URLs
  "rating": 4                // Optional, 1-5
}

Response (200 OK): AuthorDto

PUT /api/authors/{id} (Multipart)

Description: Update author with multipart form data (supports avatar upload).

Path Parameters:

  • id (UUID): Author identifier

Content-Type: multipart/form-data

Form Parameters:

  • name (string): Author name
  • notes (string): Author notes
  • urls (string[]): List of URLs
  • authorRating (integer): Author rating
  • avatar (File): Avatar image file

Response (200 OK): AuthorDto

DELETE /api/authors/{id}

Description: Delete author and all associated data.

Path Parameters:

  • id (UUID): Author identifier

Response (200 OK):

{
  "message": "Author deleted successfully"
}

Author Image Management

POST /api/authors/{id}/avatar

Description: Upload avatar image for author.

Path Parameters:

  • id (UUID): Author identifier

Content-Type: multipart/form-data

Form Parameters:

  • file (File): Avatar image (JPG, PNG, WebP, max 5MB)

Response (200 OK):

{
  "message": "Avatar uploaded successfully",
  "avatarPath": "avatars/author-uuid-avatar.jpg",
  "avatarUrl": "/api/files/images/avatars/author-uuid-avatar.jpg"
}

DELETE /api/authors/{id}/avatar

Description: Remove avatar image from author.

Response (200 OK):

{
  "message": "Avatar removed successfully"
}

Author URL Management

POST /api/authors/{id}/urls

Description: Add URL to author.

Path Parameters:

  • id (UUID): Author identifier

Request Body:

{
  "url": "https://example.com"
}

Response (200 OK): AuthorDto

DELETE /api/authors/{id}/urls

Description: Remove URL from author.

Request Body:

{
  "url": "https://example.com"
}

Response (200 OK): AuthorDto

Author Search and Collections

GET /api/authors/search

Description: Search authors by name (database search).

Query Parameters:

  • query (string, required): Search query
  • Standard pagination parameters

Response (200 OK): Paginated AuthorDto list

GET /api/authors/search-typesense

Description: Search authors using Typesense.

Query Parameters:

  • q (string, default: "*"): Search query
  • page, size: Pagination
  • sortBy (string, default: "name"): Sort field
  • sortOrder (string, default: "asc"): Sort order

Response (200 OK): SearchResultDto<AuthorDto>

GET /api/authors/top-rated

Description: Get top-rated authors.

Query Parameters:

  • limit (integer, default: 10): Number of authors

Response (200 OK): AuthorDto[]


Tag Management Endpoints

GET /api/tags

Description: Get paginated list of all tags.

Query Parameters: Standard pagination and sorting parameters

Response (200 OK): Paginated TagDto list

GET /api/tags/{id}

Description: Get specific tag by UUID.

Response (200 OK): TagDto

POST /api/tags

Description: Create new tag.

Request Body:

{
  "name": "tag-name"  // Required, max 100 characters, will be lowercased
}

Response (201 Created): TagDto

PUT /api/tags/{id}

Description: Update tag name.

Request Body:

{
  "name": "new-tag-name"  // Optional
}

Response (200 OK): TagDto

DELETE /api/tags/{id}

Description: Delete tag and remove from all stories.

Response (200 OK):

{
  "message": "Tag deleted successfully"
}

GET /api/tags/search

Description: Search tags by name.

Query Parameters:

  • query (string, required): Search query
  • Standard pagination parameters

Response (200 OK): Paginated TagDto list

GET /api/tags/autocomplete

Description: Get tag suggestions for autocomplete.

Query Parameters:

  • query (string, required): Partial tag name
  • limit (integer, default: 10): Number of suggestions

Response (200 OK): TagDto[]

GET /api/tags/popular

Description: Get most used tags.

Query Parameters:

  • limit (integer, default: 20): Number of tags

Response (200 OK): TagDto[] (ordered by usage count)

GET /api/tags/unused

Description: Get tags not used by any stories.

Response (200 OK): TagDto[]

GET /api/tags/stats

Description: Get tag usage statistics.

Response (200 OK):

{
  "totalTags": 150,
  "usedTags": 120,
  "unusedTags": 30
}

Series Management Endpoints

GET /api/series

Description: Get paginated list of all series.

Response (200 OK): Paginated SeriesDto list

GET /api/series/{id}

Description: Get specific series by UUID.

Response (200 OK): SeriesDto

POST /api/series

Description: Create new series.

Request Body:

{
  "name": "Series Name",        // Required, max 255 characters
  "description": "Description"  // Optional, max 1000 characters
}

Response (201 Created): SeriesDto

PUT /api/series/{id}

Description: Update series.

Request Body:

{
  "name": "Updated Name",       // Optional
  "description": "Updated desc"  // Optional
}

Response (200 OK): SeriesDto

DELETE /api/series/{id}

Description: Delete series and remove series association from stories.

Response (200 OK):

{
  "message": "Series deleted successfully"
}

Series Collections

GET /api/series/search

Description: Search series by name.

Query Parameters:

  • query (string, required): Search query
  • Standard pagination parameters

Response (200 OK): Paginated SeriesDto list

GET /api/series/with-stories

Description: Get series that have stories.

Query Parameters:

  • limit (integer, default: 20): Number of series

Response (200 OK): SeriesDto[]

GET /api/series/popular

Description: Get most popular series (by story count).

Query Parameters:

  • limit (integer, default: 10): Number of series

Response (200 OK): SeriesDto[]

GET /api/series/empty

Description: Get series with no stories.

Response (200 OK): SeriesDto[]

GET /api/series/stats

Description: Get series statistics.

Response (200 OK):

{
  "totalSeries": 50,
  "seriesWithStories": 35,
  "emptySeries": 15
}

File Management Endpoints

POST /api/files/upload/cover

Description: Upload cover image (generic endpoint).

Content-Type: multipart/form-data

Form Parameters:

  • file (File): Image file

Response (200 OK):

{
  "message": "Cover uploaded successfully",
  "path": "covers/filename.jpg",
  "url": "/api/files/images/covers/filename.jpg"
}

POST /api/files/upload/avatar

Description: Upload avatar image (generic endpoint).

Response (200 OK):

{
  "message": "Avatar uploaded successfully",
  "path": "avatars/filename.jpg",
  "url": "/api/files/images/avatars/filename.jpg"
}

GET /api/files/images/**

Description: Serve image files.

Query Parameters:

  • path (string, required): Image path

Response: Image file with appropriate Content-Type

Headers:

  • Cache-Control: max-age=31536000 (1 year)
  • Content-Type: image/jpeg|png|webp

DELETE /api/files/images

Description: Delete image file.

Query Parameters:

  • path (string, required): Image path to delete

Response (200 OK):

{
  "message": "Image deleted successfully"
}

Search and Indexing Endpoints

POST /api/search/reindex

Description: Manually reindex all stories in Typesense.

Response (200 OK):

{
  "message": "Successfully reindexed all stories",
  "storiesCount": 1250
}

GET /api/search/health

Description: Check search service health status.

Response (200 OK):

{
  "status": "healthy",
  "message": "Typesense is running and responsive"
}

Response (503 Service Unavailable):

{
  "status": "unhealthy",
  "message": "Typesense is not responding",
  "error": "Connection refused"
}

Configuration Endpoints

GET /api/config/html-sanitization

Description: Get HTML sanitization configuration for frontend.

Response (200 OK):

{
  "allowedTags": [
    "p", "br", "div", "span", "h1", "h2", "h3",
    "b", "strong", "i", "em", "u", "s", "del"
  ],
  "allowedAttributes": {
    "p": ["class", "style"],
    "div": ["class", "style"],
    "span": ["class", "style"]
  },
  "allowedCssProperties": [
    "color", "background-color", "font-size",
    "font-weight", "font-style", "text-align"
  ],
  "removedAttributes": {
    "a": ["href", "target"]
  },
  "description": "HTML sanitization configuration for StoryCove"
}

Data Transfer Objects (DTOs)

StoryDto

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "title": "Story Title",
  "summary": "Story summary",
  "description": "Story description",
  "contentHtml": "<p>HTML content</p>",
  "contentPlain": "Plain text content",
  "sourceUrl": "https://example.com/story",
  "coverPath": "covers/story-cover.jpg",
  "wordCount": 1500,
  "rating": 4,
  "volume": 1,
  "authorId": "author-uuid",
  "authorName": "Author Name",
  "seriesId": "series-uuid",
  "seriesName": "Series Name",
  "tags": [
    {
      "id": "tag-uuid",
      "name": "tag-name",
      "storyCount": 10,
      "createdAt": "2024-01-15T10:30:00",
      "updatedAt": "2024-01-15T10:30:00"
    }
  ],
  "createdAt": "2024-01-15T10:30:00",
  "updatedAt": "2024-01-16T14:20:00"
}

AuthorDto

{
  "id": "author-uuid",
  "name": "Author Name",
  "notes": "Notes about the author",
  "avatarImagePath": "avatars/author-avatar.jpg",
  "authorRating": 4,
  "averageStoryRating": 3.8,
  "urls": [
    "https://authorwebsite.com",
    "https://twitter.com/author"
  ],
  "storyCount": 15,
  "createdAt": "2024-01-10T09:00:00",
  "updatedAt": "2024-01-16T11:00:00"
}

TagDto

{
  "id": "tag-uuid",
  "name": "fantasy",
  "storyCount": 25,
  "createdAt": "2024-01-05T12:00:00",
  "updatedAt": "2024-01-05T12:00:00"
}

SeriesDto

{
  "id": "series-uuid",
  "name": "Series Name",
  "description": "Series description",
  "storyCount": 5,
  "createdAt": "2024-01-01T00:00:00"
}

SearchResultDto

{
  "results": [T],  // Array of result objects
  "totalHits": 42,
  "page": 0,
  "perPage": 20,
  "query": "search terms",
  "searchTimeMs": 15
}

Error Handling

Standard HTTP Status Codes

  • 200 OK: Request successful
  • 201 Created: Resource created successfully
  • 400 Bad Request: Invalid request data or validation error
  • 401 Unauthorized: Authentication required or invalid token
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource not found
  • 409 Conflict: Resource conflict (e.g., duplicate name)
  • 413 Payload Too Large: File upload too large
  • 415 Unsupported Media Type: Invalid file type
  • 422 Unprocessable Entity: Validation error
  • 500 Internal Server Error: Server error

Error Response Format

Single Error:

{
  "error": "Error message"
}

Validation Errors:

{
  "error": "Validation failed",
  "details": {
    "title": "must not be blank",
    "rating": "must be between 1 and 5"
  }
}

Field-Specific Error:

{
  "error": "Author not found with id: 123e4567-e89b-12d3-a456-426614174000"
}

Rate Limiting and Performance

Request Limits

  • No explicit rate limiting implemented
  • File uploads limited to 5MB per file
  • Page size limited to 100 items maximum
  • Search queries have reasonable timeout limits

Performance Considerations

  • Pagination used for all list endpoints
  • Lazy loading for entity relationships
  • Image files cached with 1-year expiration
  • Search powered by Typesense for fast full-text search
  • Database queries optimized with proper indexing

Best Practices

  • Use pagination for large datasets
  • Cache images and static content
  • Use search endpoints for finding content rather than filtering
  • Batch operations when possible
  • Include only necessary fields in responses

This API documentation provides comprehensive coverage of all StoryCove endpoints with examples, error handling, and best practices for integration.