583 lines
14 KiB
YAML
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 |