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