openapi: 3.2.0
info:
  title: API — Слово.Проповеди
  version: 0.17.0
  description: |
    REST API сервиса «Слово.Проповеди».
    Позволяет управлять проповедями, плейлистами, разделами, загружать файлы и работать с пользователями.
servers:
  - url: https://api.slovo-propovedi.ru
    description: Продакшн сервер
  - url: http://localhost:3000
    description: Локальный сервер разработки
tags:
  - name: Auth
    description: Аутентификация пользователей
  - name: Files
    description: Загрузка и получение файлов через MinIO
  - name: Sermons
    description: Управление проповедями
  - name: Sections
    description: Управление разделами
  - name: Playlists
    description: Управление плейлистами
  - name: Users
    description: Управление пользователями (админами)
  - name: Basic
    description: Базовые роуты
paths:
  /health:
    get:
      operationId: HealthController_check
      summary: Проверить состояние сервиса
      parameters: []
      responses:
        "200":
          description: "Состояние сервиса"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
      tags:
        - Basic
  /files:
    post:
      operationId: AppController_uploadFile
      summary: "Загрузить файл (изображение, аудио MP3, PDF, FB2)"
      description: "Файл сохраняется в MinIO. Допустимые форматы: JPEG, PNG, WebP (изображения), MP3 (аудио), PDF, FB2 (документы)."
      parameters: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: "Допустимые форматы — JPEG, PNG, WebP, MP3, PDF, FB2. Другие форматы будут отклонены."
      responses:
        "200":
          description: Файл успешно загружен
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IFileResponseDto"
      tags:
        - Files
      security:
        - bearer: []
    get:
      operationId: getFiles
      summary: List image files
      description: "Returns a list of all image files in storage (for cover reuse feature)"
      tags:
        - Files
      responses:
        "200":
          description: List of image files
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AllFilesResponse"
  /files/{fileName}/stream-url:
    get:
      operationId: AppController_getStreamUrl
      summary: Получить URL потока для файла
      parameters:
        - name: fileName
          required: true
          in: path
          schema:
            type: string
      responses:
        "200":
          description: "Предварительно подписанный URL потока"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StreamUrlResponse"
      tags:
        - Files
  /files/{fileName}:
    get:
      operationId: AppController_getFile
      summary: Получить публичный URL файла
      parameters:
        - name: fileName
          required: true
          in: path
          schema:
            type: string
      responses:
        "200":
          description: Информация о файле
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IFileResponseDto"
      tags:
        - Files
  /section:
    post:
      operationId: SectionController_create
      summary: Создать раздел
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSectionDto"
      responses:
        "200":
          description: Раздел создан
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SectionEntity"
      tags:
        - Sections
      security:
        - bearer: []
    get:
      operationId: SectionController_findAll
      summary: Получить все разделы
      parameters: []
      responses:
        "200":
          description: Список разделов
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AllSectionsResponse"
      tags:
        - Sections
  /section/reorder:
    patch:
      operationId: reorderSections
      summary: Изменить порядок разделов
      description: Принимает список id разделов в новом порядке и обновляет позиции
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReorderSectionsDto"
      responses:
        "200":
          description: Порядок разделов обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSectionsResponse"
      tags:
        - Sections
      security:
        - bearer: []
  /section/{id}:
    get:
      operationId: SectionController_findOne
      summary: Получить раздел по ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Раздел
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SectionEntity"
      tags:
        - Sections
    patch:
      operationId: SectionController_update
      summary: "Обновить раздел (включая связанные плейлисты)"
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSectionDto"
      responses:
        "200":
          description: Раздел обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SectionEntity"
      tags:
        - Sections
      security:
        - bearer: []
    delete:
      operationId: SectionController_remove
      summary: Удалить раздел
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Раздел удалён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSectionsResponse"
      tags:
        - Sections
      security:
        - bearer: []
  /section/{id}/playlists/reorder:
    patch:
      operationId: reorderPlaylistsInSection
      summary: Изменить порядок плейлистов в разделе
      description: Принимает список id плейлистов в новом порядке и обновляет их позиции внутри раздела
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReorderPlaylistsDto"
      responses:
        "200":
          description: Порядок плейлистов в разделе обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSectionsResponse"
      tags:
        - Sections
      security:
        - bearer: []
  /playlists:
    post:
      operationId: PlaylistController_create
      summary: Создать плейлист
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePlaylistDto"
      responses:
        "200":
          description: Плейлист создан
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistEntity"
      tags:
        - Playlists
      security:
        - bearer: []
    get:
      operationId: PlaylistController_findAll
      summary: Получить все плейлисты
      parameters:
        - name: search
          required: false
          in: query
          description: Поисковый запрос по названию и описанию
          schema:
            type: string
            minLength: 1
        - name: page
          required: false
          in: query
          description: Номер страницы для оффсетной пагинации
          schema:
            type: integer
            minimum: 1
        - name: limit
          required: false
          in: query
          description: Размер страницы; если указан без page, используется первая страница
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Список плейлистов; count — общее число; сортировка по убыванию id (стабильный порядок; id — UUID, не хронология); при поиске — по релевантности, затем по убыванию id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AllPlaylistsResponse"
      tags:
        - Playlists
  /playlists/{id}:
    get:
      operationId: PlaylistController_findOne
      summary: "Получить плейлист по ID (с проповедями)"
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Плейлист
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistEntity"
      tags:
        - Playlists
    patch:
      operationId: PlaylistController_update
      summary: Обновить плейлист
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePlaylistDto"
      responses:
        "200":
          description: Плейлист обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaylistEntity"
      tags:
        - Playlists
      security:
        - bearer: []
    delete:
      operationId: PlaylistController_remove
      summary: Удалить плейлист
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Плейлист удалён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusPlaylistResponse"
      tags:
        - Playlists
      security:
        - bearer: []
  /playlists/{id}/sermons/reorder:
    patch:
      operationId: reorderSermonsInPlaylist
      summary: Изменить порядок проповедей в плейлисте
      description: Принимает список id проповедей в новом порядке и обновляет их позиции внутри плейлиста
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReorderSermonsDto"
      responses:
        "200":
          description: Порядок проповедей в плейлисте обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusPlaylistResponse"
      tags:
        - Playlists
      security:
        - bearer: []
  /sermons:
    post:
      operationId: SermonController_create
      summary: Создать новую проповедь
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSermonDto"
      responses:
        "200":
          description: Проповедь создана
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SermonEntity"
      tags:
        - Sermons
      security:
        - bearer: []
    get:
      operationId: SermonController_findAll
      summary: Получить список всех проповедей
      parameters:
        - name: take
          required: false
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          required: false
          in: query
          schema:
            type: string
            format: uuid
        - name: search
          required: false
          in: query
          description: Поисковый запрос по названию, проповеднику, книге и описанию
          schema:
            type: string
            minLength: 1
        - name: page
          required: false
          in: query
          description: Номер страницы для оффсетной пагинации; взаимоисключителен с take и cursor (одновременное использование → 400)
          schema:
            type: integer
            minimum: 1
        - name: limit
          required: false
          in: query
          description: Размер страницы; если указан без page, используется первая страница; взаимоисключителен с take и cursor (одновременное использование → 400)
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Список проповедей с количеством; в оффсетном режиме count — общее число записей, nextCursor — null
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AllSermonsResponse"
      tags:
        - Sermons
  /sermons/distinct-values:
    get:
      operationId: SermonController_getDistinctValues
      summary: Получить список ранее использованных проповедников и книг (для автодополнения)
      parameters: []
      responses:
        "200":
          description: Списки уникальных значений проповедников и книг
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SermonDistinctValuesResponse"
      tags:
        - Sermons
  /sermons/{id}/stream-url:
    get:
      operationId: SermonController_getStreamUrl
      summary: Получить URL потока для аудио проповеди
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "Предварительно подписанный URL потока"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StreamUrlResponse"
      tags:
        - Sermons
  /sermons/{id}:
    get:
      operationId: SermonController_findOne
      summary: Получить одну проповедь по ID
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Проповедь
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SermonEntity"
      tags:
        - Sermons
    patch:
      operationId: SermonController_update
      summary: Обновить проповедь
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSermonDto"
      responses:
        "200":
          description: Проповедь обновлена
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSermonResponse"
      tags:
        - Sermons
      security:
        - bearer: []
    delete:
      operationId: SermonController_remove
      summary: Удалить проповедь
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Проповедь удалена
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusSermonResponse"
      tags:
        - Sermons
      security:
        - bearer: []
  /auth/login:
    post:
      operationId: AuthController_signIn
      summary: Вход в систему
      description: Возвращает JWT токен для дальнейших запросов
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignInRequestDto"
      responses:
        "200":
          description: Успешный вход
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
      tags:
        - Auth
  /auth/refresh:
    post:
      operationId: AuthController_refresh
      summary: Обновить access и refresh токены
      description: Принимает refresh токен и возвращает новую пару токенов
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshTokenDto"
      responses:
        "200":
          description: Новая пара токенов
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefreshResponse"
      tags:
        - Auth
  /auth/logout:
    post:
      operationId: AuthController_logout
      summary: Выход из системы
      description: Отзывает refresh-токен (denylist). Access-токен остаётся технически валидным до истечения срока (не более 30 минут); клиент обязан удалить оба токена.
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LogoutRequestDto"
      responses:
        "204":
          description: Сессия завершена, refresh-токен отозван
      tags:
        - Auth
      security:
        - bearer: []
  /auth/profile:
    get:
      operationId: AuthController_getProfile
      summary: Получить профиль текущего пользователя
      parameters: []
      responses:
        "200":
          description: Профиль пользователя
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResponse"
      tags:
        - Auth
      security:
        - bearer: []
  /users:
    get:
      tags:
        - Users
      operationId: UsersController_findAll
      security:
        - bearer: []
      parameters:
        - name: page
          required: false
          in: query
          description: Номер страницы для оффсетной пагинации
          schema:
            type: integer
            minimum: 1
        - name: limit
          required: false
          in: query
          description: Размер страницы; если указан без page, используется первая страница
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Список пользователей; count — общее число пользователей
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AllUsersResponse"
    post:
      tags:
        - Users
      operationId: UsersController_create
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUserRequest"
      responses:
        "201":
          description: Пользователь создан
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResponse"
  /users/{id}:
    get:
      tags:
        - Users
      operationId: UsersController_findOne
      security:
        - bearer: []
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Пользователь
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResponse"
    patch:
      tags:
        - Users
      operationId: UsersController_update
      security:
        - bearer: []
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateUserRequest"
      responses:
        "200":
          description: Пользователь обновлён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserResponse"
    delete:
      tags:
        - Users
      operationId: UsersController_remove
      security:
        - bearer: []
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Пользователь удалён
  /users/{id}/password:
    patch:
      tags:
        - Users
      operationId: UsersController_changePassword
      security:
        - bearer: []
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangePasswordRequest"
      responses:
        "204":
          description: Пароль изменён
components:
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http
      description: JWT токен, полученный после успешного логина
  schemas:
    HealthResponse:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
      required:
        - status
    StreamUrlResponse:
      type: object
      properties:
        url:
          type: string
      required:
        - url
    IFileResponseDto:
      type: object
      additionalProperties: false
      properties:
        fileName:
          type: string
        fileUrl:
          type: string
      required:
        - fileName
        - fileUrl
    FileMetadataDto:
      type: object
      properties:
        fileName:
          type: string
        fileUrl:
          type: string
        size:
          type: [integer, 'null']
          format: int64
        lastModified:
          type: [string, 'null']
          format: date-time
      required:
        - fileName
        - fileUrl
        - size
        - lastModified

    AllFilesResponse:
      type: object
      properties:
        files:
          type: array
          items:
            $ref: "#/components/schemas/FileMetadataDto"
        count:
          type: integer
      required:
        - files
        - count
    CreateSectionDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        itemsSize:
          type: string
          enum:
            - small
            - middle
            - large
            - xLarge
        itemsRows:
          type: [number, 'null']
        transform:
          type: string
          enum:
            - high
            - middle
            - short
        isDescriptionTitleOnSlideLarge:
          type: boolean
        whereIsSlideTitleLocated:
          type: string
          enum:
            - "on"
            - under
            - bothOnAndUnder
        borderRadius:
          type: boolean
      required:
        - title
        - itemsSize
        - transform
        - description
        - itemsRows
    SectionEntity:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: [string, 'null']
        # Global order of sections (drag-and-drop reordering).
        position:
          type: integer
        itemsSize:
          type: string
          enum:
            - small
            - middle
            - large
            - xLarge
        itemsRows:
          type: [number, 'null']
        transform:
          type: string
          enum:
            - high
            - middle
            - short
        isDescriptionTitleOnSlideLarge:
          type: boolean
          default: false
        whereIsSlideTitleLocated:
          type: string
          enum:
            - "on"
            - under
            - bothOnAndUnder
          default: under
        borderRadius:
          type: boolean
          default: false
        playlists:
          type: array
          items:
            $ref: "#/components/schemas/SectionPlaylist"
      required:
        - id
        - title
        - description
        - itemsSize
        - itemsRows
        - transform
        - playlists
        - position
    SectionRef:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
      required:
        - id
        - title
    SectionPlaylist:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
        artwork:
          type: string
        # Position of this playlist within the section that references it.
        position:
          type: integer
        sections:
          type: array
          items:
            $ref: "#/components/schemas/SectionRef"
        sermons:
          type: array
          items:
            $ref: "#/components/schemas/PlaylistSermon"
      required:
        - id
        - title
        - description
        - artwork
        - sections
        - sermons
        - position
    SermonEntity:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
        textFileUrl:
          type: [string, 'null']
        audioUrl:
          type: [string, 'null']
        youtubeUrl:
          type: [string, 'null']
        artist:
          type: string
        artwork:
          type: string
        book:
          type: [string, 'null']
        chapter:
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: 'null'
        verse:
          description: "Стих или стихи проповеди. Массив из двух целых чисел трактуется как диапазон от–до; массив, содержащий кортежи или смесь целых и кортежей, трактуется как список разрозненных отрезков (например [9,18] — диапазон, [[9,18],20] — отрезок 9–18 и стих 20)."
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: array
              minItems: 1
              items:
                oneOf:
                  - type: integer
                  - type: array
                    items:
                      type: integer
                    minItems: 2
                    maxItems: 2
            - type: 'null'
        playlists:
          type: array
          items:
            $ref: "#/components/schemas/PlaylistEntity"
      required:
        - id
        - title
        - description
        - textFileUrl
        - audioUrl
        - youtubeUrl
        - artist
        - artwork
        - book
        - chapter
        - verse
        - playlists
    PlaylistSermon:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
        textFileUrl:
          type: [string, 'null']
        audioUrl:
          type: [string, 'null']
        youtubeUrl:
          type: [string, 'null']
        artist:
          type: string
        artwork:
          type: string
        book:
          type: [string, 'null']
        chapter:
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: 'null'
        verse:
          description: "Стих или стихи проповеди. Массив из двух целых чисел трактуется как диапазон от–до; массив, содержащий кортежи или смесь целых и кортежей, трактуется как список разрозненных отрезков (например [9,18] — диапазон, [[9,18],20] — отрезок 9–18 и стих 20)."
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: array
              minItems: 1
              items:
                oneOf:
                  - type: integer
                  - type: array
                    items:
                      type: integer
                    minItems: 2
                    maxItems: 2
            - type: 'null'
        # Position of this sermon within the playlist that references it.
        position:
          type: integer
        playlists:
          type: array
          items:
            type: object
            additionalProperties: false
            properties:
              id:
                type: string
              title:
                type: string
            required:
              - id
              - title
      required:
        - id
        - title
        - description
        - textFileUrl
        - audioUrl
        - youtubeUrl
        - artist
        - artwork
        - book
        - chapter
        - verse
        - position
        - playlists
    PlaylistEntity:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: string
        artwork:
          type: string
        sections:
          type: array
          items:
            $ref: "#/components/schemas/SectionEntity"
        sermons:
          type: array
          items:
            $ref: "#/components/schemas/PlaylistSermon"
      required:
        - id
        - title
        - description
        - artwork
        - sections
        - sermons
    AllSectionsResponse:
      type: object
      additionalProperties: false
      properties:
        sections:
          type: array
          items:
            $ref: "#/components/schemas/SectionEntity"
        count:
          type: number
      required:
        - sections
        - count
    UpdateSectionDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        playlistsIds:
          type: array
          items:
            type: string
        itemsSize:
          type: string
          enum:
            - small
            - middle
            - large
            - xLarge
        itemsRows:
          type: [number, 'null']
        transform:
          type: string
          enum:
            - high
            - middle
            - short
        isDescriptionTitleOnSlideLarge:
          type: boolean
        whereIsSlideTitleLocated:
          type: string
          enum:
            - "on"
            - under
            - bothOnAndUnder
        borderRadius:
          type: boolean
      required:
        - title
        - description
        - playlistsIds
        - itemsSize
        - itemsRows
        - transform
        - isDescriptionTitleOnSlideLarge
        - whereIsSlideTitleLocated
        - borderRadius
    StatusSectionsResponse:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
      required:
        - status
    CreatePlaylistDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        artwork:
          type: string
        sermonsIds:
          type: array
          items:
            type: string
        sectionsIds:
          type: array
          items:
            type: string
      required:
        - title
        - description
        - artwork
    AllPlaylistsResponse:
      type: object
      additionalProperties: false
      properties:
        playlists:
          type: array
          items:
            $ref: "#/components/schemas/PlaylistEntity"
        count:
          type: number
      required:
        - playlists
        - count
    UpdatePlaylistDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        artwork:
          type: string
        sermonsIds:
          type: array
          items:
            type: string
        sectionsIds:
          type: array
          items:
            type: string
      required:
        - title
        - description
        - artwork
        - sermonsIds
    StatusPlaylistResponse:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
      required:
        - status
    ReorderSectionsDto:
      type: object
      properties:
        ids:
          type: array
          items:
            type: string
            format: uuid
      required:
        - ids
    ReorderSermonsDto:
      type: object
      properties:
        sermonIds:
          type: array
          items:
            type: string
            format: uuid
      required:
        - sermonIds
    ReorderPlaylistsDto:
      type: object
      properties:
        playlistIds:
          type: array
          items:
            type: string
            format: uuid
      required:
        - playlistIds
    CreateSermonDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        textFileUrl:
          type: [string, 'null']
        audioUrl:
          type: [string, 'null']
        youtubeUrl:
          type: [string, 'null']
        artist:
          type: string
        artwork:
          type: string
        book:
          type: [string, 'null']
          example: Genesis
        chapter:
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: 'null'
        verse:
          description: "Стих или стихи проповеди. Массив из двух целых чисел трактуется как диапазон от–до; массив, содержащий кортежи или смесь целых и кортежей, трактуется как список разрозненных отрезков (например [9,18] — диапазон, [[9,18],20] — отрезок 9–18 и стих 20)."
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: array
              minItems: 1
              items:
                oneOf:
                  - type: integer
                  - type: array
                    items:
                      type: integer
                    minItems: 2
                    maxItems: 2
            - type: 'null'
        playlistsIds:
          type: array
          items:
            type: string
      required:
        - title
        - description
        - artist
        - artwork
        - book
        - textFileUrl
        - audioUrl
        - youtubeUrl
    AllSermonsResponse:
      type: object
      additionalProperties: false
      properties:
        sermons:
          type: array
          items:
            $ref: "#/components/schemas/SermonEntity"
        count:
          type: [number, 'null']
        nextCursor:
          type: [string, 'null']
      required:
        - sermons
        - count
        - nextCursor
    SermonDistinctValuesResponse:
      type: object
      additionalProperties: false
      description: Списки уникальных значений проповедников и книг
      properties:
        artists:
          type: array
          items:
            type: string
        books:
          type: array
          items:
            type: string
      required:
        - artists
        - books
    UpdateSermonDto:
      type: object
      properties:
        title:
          type: string
        description:
          type: [string, 'null']
        textFileUrl:
          type: [string, 'null']
        audioUrl:
          type: [string, 'null']
        youtubeUrl:
          type: [string, 'null']
        artist:
          type: string
        artwork:
          type: string
        book:
          type: [string, 'null']
          example: Genesis
        chapter:
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: 'null'
        verse:
          description: "Стих или стихи проповеди. Массив из двух целых чисел трактуется как диапазон от–до; массив, содержащий кортежи или смесь целых и кортежей, трактуется как список разрозненных отрезков (например [9,18] — диапазон, [[9,18],20] — отрезок 9–18 и стих 20)."
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
              minItems: 2
              maxItems: 2
            - type: array
              minItems: 1
              items:
                oneOf:
                  - type: integer
                  - type: array
                    items:
                      type: integer
                    minItems: 2
                    maxItems: 2
            - type: 'null'
        playlistsIds:
          type: array
          items:
            type: string
      required:
        - title
        - description
        - textFileUrl
        - audioUrl
        - youtubeUrl
        - artist
        - artwork
        - book
        - playlistsIds
    StatusSermonResponse:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
      required:
        - status
    SignInRequestDto:
      type: object
      additionalProperties: false
      properties:
        username:
          type: string
          description: Имя пользователя для входа
          example: admin
        password:
          type: string
      required:
        - username
        - password
    UserRole:
      type: string
      enum:
        - admin
        - moderator
        - user
      description: Роль пользователя в системе
    AllUsersResponse:
      type: object
      additionalProperties: false
      properties:
        users:
          type: array
          items:
            $ref: "#/components/schemas/UserResponse"
        count:
          type: integer
      required:
        - users
        - count
    UserResponse:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        name:
          type: string
        username:
          type: string
          description: Имя пользователя для входа в систему
        email:
          type: string
        role:
          $ref: "#/components/schemas/UserRole"
      required:
        - id
        - name
        - username
        - email
        - role
    CreateUserRequest:
      type: object
      additionalProperties: false
      required:
        - name
        - email
        - username
        - password
      properties:
        name:
          type: string
        email:
          type: string
        username:
          type: string
        password:
          type: string
        role:
          $ref: "#/components/schemas/UserRole"
    UpdateUserRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        email:
          type: string
        username:
          type: string
        role:
          $ref: "#/components/schemas/UserRole"
    ChangePasswordRequest:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
    AuthResponse:
      type: object
      additionalProperties: false
      properties:
        accessToken:
          type: string
        refreshToken:
          type: string
        user:
          $ref: "#/components/schemas/UserResponse"
      required:
        - accessToken
        - refreshToken
        - user
    RefreshTokenDto:
      type: object
      properties:
        refreshToken:
          type: string
      required:
        - refreshToken
    LogoutRequestDto:
      type: object
      additionalProperties: false
      properties:
        refreshToken:
          type: string
          description: Refresh-токен, который нужно отозвать
      required:
        - refreshToken
    RefreshResponse:
      type: object
      additionalProperties: false
      properties:
        accessToken:
          type: string
        refreshToken:
          type: string
      required:
        - accessToken
        - refreshToken
