# 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**: ```json { "password": "string" // Required, application password } ``` **Response (200 OK)**: ```json { "message": "Authentication successful", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` **Response (401 Unauthorized)**: ```json { "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)**: ```json { "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)**: ```json { "message": "Token is valid" } ``` **Response (401 Unauthorized)**: ```json { "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)**: ```json { "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)**: ```json { "error": "Story not found with id: {id}" } ``` ### POST /api/stories **Description**: Create a new story. **Request Body**: ```json { "title": "string", // Required "summary": "string", // Optional "description": "string", // Optional "contentHtml": "
Story content
", // 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)**: ```json { "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)**: ```json { "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)**: ```json { "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)**: ```json { "message": "Cover removed successfully" } ``` ### POST /api/stories/{id}/rating **Description**: Set story rating. **Path Parameters**: - `id` (UUID): Story identifier **Request Body**: ```json { "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)**: ```json { "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)**: ```json ["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**: ```json { "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**: ```json { "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)**: ```json { "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)**: ```json { "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)**: ```json { "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**: ```json { "url": "https://example.com" } ``` **Response (200 OK)**: `AuthorDto` #### DELETE /api/authors/{id}/urls **Description**: Remove URL from author. **Request Body**: ```json { "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)**: `SearchResultDtoHTML content
", "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 ```json { "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 ```json { "id": "tag-uuid", "name": "fantasy", "storyCount": 25, "createdAt": "2024-01-05T12:00:00", "updatedAt": "2024-01-05T12:00:00" } ``` ### SeriesDto ```json { "id": "series-uuid", "name": "Series Name", "description": "Series description", "storyCount": 5, "createdAt": "2024-01-01T00:00:00" } ``` ### SearchResultDto