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

583 lines
14 KiB
YAML

openapi: 3.0.3
info:
title: StoryCove API
description: |
StoryCove is a self-hosted web application for storing, organizing, and reading short stories from various internet sources.
## Features
- Story management with HTML content support
- Author profiles with ratings and metadata
- Tag-based categorization and series organization
- Full-text search powered by Typesense
- Image upload for covers and avatars
- JWT-based authentication
## Authentication
All endpoints (except `/api/auth/login`) require JWT authentication via httpOnly cookies.
version: 1.0.0
contact:
name: StoryCove
license:
name: MIT
servers:
- url: http://localhost:6925/api
description: Local development server
- url: https://storycove.hardegger.io/api
description: Production server
security:
- cookieAuth: []
paths:
# Authentication endpoints
/auth/login:
post:
tags: [Authentication]
summary: Login with password
description: Authenticate with application password and receive JWT token
security: [] # No authentication required
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password:
type: string
description: Application password
responses:
'200':
description: Authentication successful
headers:
Set-Cookie:
description: JWT token cookie
schema:
type: string
example: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Max-Age=86400
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Authentication successful
token:
type: string
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
'401':
$ref: '#/components/responses/Unauthorized'
/auth/logout:
post:
tags: [Authentication]
summary: Logout and clear token
description: Clear authentication token and logout
responses:
'200':
description: Logout successful
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
/auth/verify:
get:
tags: [Authentication]
summary: Verify token validity
description: Check if current JWT token is valid
responses:
'200':
description: Token is valid
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
# Story endpoints
/stories:
get:
tags: [Stories]
summary: List all stories
description: Get paginated list of all stories
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/SizeParam'
- $ref: '#/components/parameters/SortByParam'
- $ref: '#/components/parameters/SortDirParam'
responses:
'200':
description: Stories retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/PagedStories'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Stories]
summary: Create new story
description: Create a new story with optional author, series, and tags
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateStoryRequest'
responses:
'201':
description: Story created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/StoryDto'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/stories/{id}:
get:
tags: [Stories]
summary: Get story by ID
description: Retrieve a specific story by its UUID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Story UUID
responses:
'200':
description: Story retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/StoryDto'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
put:
tags: [Stories]
summary: Update story
description: Update an existing story
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateStoryRequest'
responses:
'200':
description: Story updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/StoryDto'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
delete:
tags: [Stories]
summary: Delete story
description: Delete a story and all its relationships
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Story deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
/stories/search:
get:
tags: [Stories]
summary: Search stories
description: Search stories using Typesense full-text search
parameters:
- name: query
in: query
required: true
schema:
type: string
description: Search query
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/SizeParam'
- name: authors
in: query
schema:
type: array
items:
type: string
description: Filter by author names
- name: tags
in: query
schema:
type: array
items:
type: string
description: Filter by tag names
- name: minRating
in: query
schema:
type: integer
minimum: 1
maximum: 5
- name: maxRating
in: query
schema:
type: integer
minimum: 1
maximum: 5
responses:
'200':
description: Search results
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResult'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: token
parameters:
PageParam:
name: page
in: query
schema:
type: integer
minimum: 0
default: 0
description: Page number (0-based)
SizeParam:
name: size
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
description: Page size
SortByParam:
name: sortBy
in: query
schema:
type: string
default: createdAt
description: Field to sort by
SortDirParam:
name: sortDir
in: query
schema:
type: string
enum: [asc, desc]
default: desc
description: Sort direction
schemas:
StoryDto:
type: object
properties:
id:
type: string
format: uuid
title:
type: string
summary:
type: string
nullable: true
description:
type: string
nullable: true
contentHtml:
type: string
nullable: true
contentPlain:
type: string
nullable: true
sourceUrl:
type: string
nullable: true
coverPath:
type: string
nullable: true
wordCount:
type: integer
rating:
type: integer
minimum: 1
maximum: 5
nullable: true
volume:
type: integer
nullable: true
authorId:
type: string
format: uuid
nullable: true
authorName:
type: string
nullable: true
seriesId:
type: string
format: uuid
nullable: true
seriesName:
type: string
nullable: true
tags:
type: array
items:
$ref: '#/components/schemas/TagDto'
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateStoryRequest:
type: object
required: [title]
properties:
title:
type: string
maxLength: 255
summary:
type: string
description:
type: string
maxLength: 1000
contentHtml:
type: string
sourceUrl:
type: string
volume:
type: integer
authorId:
type: string
format: uuid
authorName:
type: string
seriesId:
type: string
format: uuid
seriesName:
type: string
tagNames:
type: array
items:
type: string
UpdateStoryRequest:
type: object
properties:
title:
type: string
maxLength: 255
summary:
type: string
description:
type: string
maxLength: 1000
contentHtml:
type: string
sourceUrl:
type: string
volume:
type: integer
authorId:
type: string
format: uuid
seriesId:
type: string
format: uuid
tagNames:
type: array
items:
type: string
TagDto:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
storyCount:
type: integer
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
PagedStories:
type: object
properties:
content:
type: array
items:
$ref: '#/components/schemas/StoryDto'
pageable:
$ref: '#/components/schemas/Pageable'
totalElements:
type: integer
totalPages:
type: integer
last:
type: boolean
first:
type: boolean
numberOfElements:
type: integer
size:
type: integer
number:
type: integer
empty:
type: boolean
Pageable:
type: object
properties:
sort:
$ref: '#/components/schemas/Sort'
pageNumber:
type: integer
pageSize:
type: integer
offset:
type: integer
paged:
type: boolean
unpaged:
type: boolean
Sort:
type: object
properties:
sorted:
type: boolean
unsorted:
type: boolean
SearchResult:
type: object
properties:
results:
type: array
items:
$ref: '#/components/schemas/StoryDto'
totalHits:
type: integer
format: int64
page:
type: integer
perPage:
type: integer
query:
type: string
searchTimeMs:
type: integer
format: int64
MessageResponse:
type: object
properties:
message:
type: string
ErrorResponse:
type: object
properties:
error:
type: string
details:
type: object
additionalProperties:
type: string
responses:
BadRequest:
description: Bad request - validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Authentication required or invalid token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
tags:
- name: Authentication
description: Authentication endpoints
- name: Stories
description: Story management endpoints
- name: Authors
description: Author management endpoints
- name: Tags
description: Tag management endpoints
- name: Series
description: Series management endpoints
- name: Files
description: File upload and management endpoints
- name: Search
description: Search and indexing endpoints
- name: Configuration
description: Application configuration endpoints