openapi: 3.0.3
info:
  title: GLAS API публикаций
  version: 1.0.0-draft
  description: |
    Публичный API для работы с публикациями и медиа.
    Спека намеренно сфокусирована на простом сценарии: создать черновик -> обновить -> загрузить медиа -> опубликовать.
    Для публичного API основной формат контента — поле `body` (строка). Вложенный формат `blocks` не обязателен.
    
    API-ключ создается в кабинете пользователя: `/cabinet` (раздел API keys).
    После создания ключ показывается один раз.
servers:
  - url: /
    description: Текущий хост

tags:
  - name: Documents
    description: Работа с черновиками и публикацией
  - name: Media
    description: Загрузка и доступ к медиа черновика

paths:
  /api/public/v1/publications:
    post:
      tags: [Documents]
      summary: Быстрая публикация (один запрос)
      description: |
        Быстрый endpoint для конечного сценария "написал и сразу опубликовал".
        Содержательно эквивалентен цепочке:
        1) `POST /api/public/v1/documents`
        2) `POST /api/public/v1/documents/{document_id}/publish`
        Для API-ключа принятие актуальной версии условий фиксируется на бэкенде автоматически.

        Пример запроса:
        ```bash
        curl -X POST https://glas.su/api/public/v1/publications \
          -H "Content-Type: application/json" \
          -d '{"title":"Запуск продукта","author":"Иван Петров","body":"Вводная часть..."}'
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuickPublishRequest'
            example:
              title: "Запуск продукта"
              author: "Иван Петров"
              body: "Вводная часть..."
      responses:
        '202':
          description: Публикация принята в обработку
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuickPublishResponse'
              examples:
                accepted:
                  summary: Черновик создан, публикация поставлена в очередь
                  value:
                    document_id: "019e01dc-4c02-74bd-b4a7-c451f4e3af90"
                    status: "accepted"
                    slug: "zapusk-produkta"
                    canonical_path: "https://glas.su/zapusk-produkta"
                    short_path: "https://glas.su/s/3k9f2h"
        '400':
          description: Некорректное тело запроса
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalid:
                  value:
                    error: "invalid request body"
        '403':
          description: Недоверенный источник запроса (Origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Превышен лимит запросов
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/public/v1/documents:
    post:
      tags: [Documents]
      summary: Создать черновик публикации
      description: |
        Создает черновик публикации. Для удобной интеграции достаточно передать `title`, `author`, `body`.
        
        Пример запроса:
        ```bash
        curl -X POST https://glas.su/api/public/v1/documents \
          -H "Content-Type: application/json" \
          -d '{"title":"Черновик","author":"Иван Петров","body":"Текст публикации"}'
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DraftUpsertRequest'
            example:
              title: "5 практик запуска авторского блога"
              author: "Иван Петров"
              body: "# Введение\n\nВ этом материале разберем..."
      responses:
        '201':
          description: Черновик создан
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftResponse'
              examples:
                created:
                  value:
                    document_id: "019e01dc-4c02-74bd-b4a7-c451f4e3af90"
                    title: "Черновик"
                    author: "Иван Петров"
                    body: "Текст публикации"
                    attachments: []
                    legal_terms:
                      version: "v1"
                      accepted_current: false
                    created_at: "2026-04-27T10:01:12Z"
                    updated_at: "2026-04-27T10:01:12Z"
        '400':
          description: Некорректное тело запроса
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Недоверенный источник запроса (Origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Превышен лимит создания черновиков
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/public/v1/documents/{document_id}:
    parameters:
      - $ref: '#/components/parameters/DocumentID'
    get:
      tags: [Documents]
      summary: Получить черновик публикации
      description: |
        Возвращает данные черновика.
        Для защищенных сценариев сервис может использовать внутренний токен доступа автоматически.
        
        Пример запроса:
        ```bash
        curl https://glas.su/api/public/v1/documents/019e01dc-4c02-74bd-b4a7-c451f4e3af90 \
          -H "Authorization: Bearer <API_KEY>"
        ```
      responses:
        '200':
          description: Черновик найден
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftResponse'
        '403':
          description: Доступ запрещен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Черновик не найден
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags: [Documents]
      summary: Обновить черновик публикации
      description: |
        Обновляет черновик публикации. Рекомендуется использовать тот же простой формат: `title`, `author`, `body`.
        
        Пример запроса:
        ```bash
        curl -X PATCH https://glas.su/api/public/v1/documents/019e01dc-4c02-74bd-b4a7-c451f4e3af90 \
          -H "Content-Type: application/json" \
          -H "Authorization: Bearer <API_KEY>" \
          -d '{"title":"Черновик v2","author":"Иван Петров","body":"Обновленный текст"}'
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DraftUpsertRequest'
            example:
              title: "5 практик запуска авторского блога"
              author: "Иван Петров"
              body: "# Обновленный текст\n\nДобавили раздел с примерами..."
      responses:
        '200':
          description: Черновик обновлен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftResponse'
        '400':
          description: Некорректное тело запроса
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Доступ запрещен или недоверенный источник запроса (Origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Превышен лимит сохранения черновика
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/public/v1/documents/{document_id}/publish:
    post:
      tags: [Documents]
      summary: Опубликовать черновик
      description: |
        Публикует черновик и возвращает статус `accepted`.
        Для API-ключа принятие актуальной версии условий фиксируется на бэкенде автоматически.

        Пример запроса:
        ```bash
        curl -X POST https://glas.su/api/public/v1/documents/019e01dc-4c02-74bd-b4a7-c451f4e3af90/publish \
          -H "Authorization: Bearer <API_KEY>"
        ```
      parameters:
        - $ref: '#/components/parameters/DocumentID'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublishRequest'
      responses:
        '202':
          description: Публикация принята в обработку
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishResponse'
              examples:
                accepted:
                  value:
                    document_id: "019e01dc-4c02-74bd-b4a7-c451f4e3af90"
                    status: "accepted"
                    slug: "chernovik-v2"
                    canonical_path: "https://glas.su/chernovik-v2"
                    short_path: "https://glas.su/s/3k9f2h"
        '400':
          description: Некорректное тело запроса или пустой документ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Доступ запрещен или недоверенный источник запроса (Origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Превышен лимит публикаций
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/public/v1/documents/{document_id}/media:
    post:
      tags: [Media]
      summary: Загрузить медиа в черновик
      description: |
        Загружает файл в черновик и возвращает `asset_id` + обновленный список вложений.

        Пример запроса:
        ```bash
        curl -X POST https://glas.su/api/public/v1/documents/019e01dc-4c02-74bd-b4a7-c451f4e3af90/media \
          -H "Authorization: Bearer <API_KEY>" \
          -F "file=@cover.png"
        ```
      parameters:
        - $ref: '#/components/parameters/DocumentID'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '201':
          description: Медиа загружено
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MediaAssetResponse'
              examples:
                uploaded:
                  value:
                    asset_id: "019e01df-8a56-7d8d-a2b0-94bc1e2e3d90"
                    document_id: "019e01dc-4c02-74bd-b4a7-c451f4e3af90"
                    kind: "image"
                    filename: "cover.png"
                    content_type: "image/png"
                    size_bytes: 248112
                    preview_path: "/api/public/v1/media/019e01df-8a56-7d8d-a2b0-94bc1e2e3d90"
                    attachments:
                      - id: "019e01df-8a56-7d8d-a2b0-94bc1e2e3d90"
                        kind: "image"
                        filename: "cover.png"
                        content_type: "image/png"
                        size_bytes: 248112
                        preview_path: "/api/public/v1/media/019e01df-8a56-7d8d-a2b0-94bc1e2e3d90"
                    uploaded_at: "2026-04-27T10:03:44Z"
        '400':
          description: Некорректный запрос загрузки
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Доступ запрещен или недоверенный источник запроса (Origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/public/v1/media/{asset_id}:
    get:
      tags: [Media]
      summary: Получить приватный медиа-ассет черновика
      description: |
        Доступно владельцу соответствующего черновика.
        
        Пример запроса:
        ```bash
        curl https://glas.su/api/public/v1/media/019e01df-8a56-7d8d-a2b0-94bc1e2e3d90
        ```
      parameters:
        - $ref: '#/components/parameters/AssetID'
      responses:
        '200':
          description: Бинарное содержимое ассета
          content:
            image/*:
              schema:
                type: string
                format: binary
            application/octet-stream:
              schema:
                type: string
                format: binary
        '403':
          description: Доступ запрещен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Ассет не найден
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  parameters:
    DocumentID:
      name: document_id
      in: path
      required: true
      schema:
        type: string
      description: ID документа-черновика
    AssetID:
      name: asset_id
      in: path
      required: true
      schema:
        type: string
      description: ID загруженного медиа-ассета

  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: метод не поддерживается

    Attachment:
      type: object
      required: [id, kind, filename, content_type, size_bytes, preview_path]
      properties:
        id:
          type: string
        kind:
          type: string
        filename:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        preview_path:
          type: string

    Publication:
      type: object
      required: [slug, canonical_path, published_at]
      properties:
        slug:
          type: string
        canonical_path:
          type: string
          format: uri
        short_path:
          type: string
          format: uri
        published_at:
          type: string
          format: date-time

    LegalTermsState:
      type: object
      required: [version, accepted_current]
      properties:
        version:
          type: string
        accepted_current:
          type: boolean

    DraftUpsertRequest:
      type: object
      required: [title, author, body]
      properties:
        title:
          type: string
          example: "5 практик запуска авторского блога"
        author:
          type: string
          example: "Иван Петров"
        body:
          type: string
          description: |
            Основной и рекомендуемый формат контента для публичного API.
            Передается как единый текстовый блок.
          example: "# Введение\n\nРазбираем рабочий сценарий публикации материала..."

    QuickPublishRequest:
      type: object
      required: [title, author, body]
      properties:
        title:
          type: string
          example: "Запуск продукта"
        author:
          type: string
          example: "Иван Петров"
        body:
          type: string
          example: "Вводная часть..."

    DraftResponse:
      type: object
      required: [document_id, title, author, body, attachments, legal_terms, created_at, updated_at]
      properties:
        document_id:
          type: string
        title:
          type: string
        author:
          type: string
        body:
          type: string
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
        publication:
          $ref: '#/components/schemas/Publication'
        legal_terms:
          $ref: '#/components/schemas/LegalTermsState'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PublishRequest:
      type: object
      description: Опциональное тело запроса. Сейчас не требует полей.

    PublishResponse:
      type: object
      required: [document_id, status]
      properties:
        document_id:
          type: string
        status:
          type: string
          enum: [accepted]
        slug:
          type: string
        canonical_path:
          type: string
          format: uri
        short_path:
          type: string
          format: uri

    QuickPublishResponse:
      type: object
      required: [document_id, status]
      properties:
        document_id:
          type: string
        status:
          type: string
          enum: [accepted]
        slug:
          type: string
        canonical_path:
          type: string
          format: uri
        short_path:
          type: string
          format: uri

    MediaAssetResponse:
      type: object
      required: [asset_id, document_id, kind, filename, content_type, size_bytes, preview_path, attachments, uploaded_at]
      properties:
        asset_id:
          type: string
        document_id:
          type: string
        kind:
          type: string
        filename:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
          format: int64
        preview_path:
          type: string
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
        uploaded_at:
          type: string
          format: date-time
