openapi: 3.0.3
info:
  title: Документация внутреннего API Felmee
  version: 0.0.2
  description: |

    Общий формат ответа для всех REST endpoints:

    ```json
    {
      "success": true,
      "message": "...",
      "codeError": null,
      "data": {}
    }
    ```
servers:
  - url: https://felmee.com
    description: Production
tags:
  - name: API Аунтефикации
    description: Авторизация и токены
  - name: API работы с файлами
    description: Загрузка и получение файлов
paths:
  /api/auth/phone/send/otp:
    post:
      tags:
        - API Аунтефикации
      summary: Отправить OTP на телефон
      description: Отправляет одноразовый код (OTP) на номер телефона.
      parameters:
        - $ref: '#/components/parameters/CaptchaToken'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PhoneSendOtpRequest'
            examples:
              example:
                value:
                  recipient: '+79991234567'
      responses:
        '200':
          description: OTP отправлен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessNullResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: OTP code sent successfully
                    codeError: null
                    data: null
        '400':
          $ref: '#/components/responses/InvalidBody'
        '403':
          $ref: '#/components/responses/InvalidCaptcha'
        '429':
          $ref: '#/components/responses/TooManyRequest'
  /api/auth/phone/login:
    post:
      tags:
        - API Аунтефикации
      summary: Логин по телефону и OTP
      description: Возвращает пару токенов JWT (access) и RT (refresh).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PhoneLoginRequest'
            examples:
              example:
                value:
                  phone: '+79991234567'
                  otp: '123456'
      responses:
        '200':
          description: Успешная авторизация (JWT + RT)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPairResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: The authorization process has been completed successfully!
                    codeError: null
                    data:
                      JWT: <access_token>
                      RT: <refresh_token>
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '410':
          $ref: '#/components/responses/CodeIsGone'
  /api/auth/email/send/login-link:
    post:
      tags:
        - API Аунтефикации
      summary: Отправить email со ссылкой для входа
      description: |
        Отправляет письмо со ссылкой на вход (если пользователь существует).
        Сервер не раскрывает факт наличия аккаунта.
      parameters:
        - $ref: '#/components/parameters/CaptchaToken'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRecipientRequest'
            examples:
              example:
                value:
                  recipient: user@example.com
      responses:
        '200':
          description: Ответ всегда успешный (если аккаунт есть — письмо будет отправлено)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessNullResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: >-
                      If an account with this email exists, we will send a login
                      link to this address.
                    codeError: null
                    data: null
        '400':
          $ref: '#/components/responses/InvalidBody'
        '403':
          $ref: '#/components/responses/InvalidCaptcha'
        '429':
          $ref: '#/components/responses/TooManyRequest'
  /api/auth/email/send/otp:
    post:
      tags:
        - API Аунтефикации
      summary: Отправить OTP для привязки email
      description: >
        Отправляет OTP для привязки email. Код отправляется только если email
        ещё не зарегистрирован.

        Требуется JWT.
      security:
        - bearerAuth: []
        - jwtCookie: []
      parameters:
        - $ref: '#/components/parameters/CaptchaToken'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRecipientRequest'
            examples:
              example:
                value:
                  recipient: user@example.com
      responses:
        '200':
          description: Ответ успешный (код отправится только при доступности email)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessNullResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: >-
                      If the address is available for binding, we will send a
                      code to it.
                    codeError: null
                    data: null
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          description: >-
            Нет/невалидный JWT (может отличаться от codeError unauthorized по
            реализации)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unauthorized:
                  value:
                    success: false
                    message: Unauthorized
                    codeError: unauthorized
                    data: null
        '403':
          $ref: '#/components/responses/InvalidCaptcha'
        '429':
          $ref: '#/components/responses/TooManyRequest'
  /api/auth/email/add:
    put:
      tags:
        - API Аунтефикации
      summary: Привязать email к текущему пользователю
      description: Подтверждение email через OTP и привязка к текущему пользователю.
      security:
        - bearerAuth: []
        - jwtCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailAddRequest'
            examples:
              example:
                value:
                  email: user@example.com
                  otp: '123456'
      responses:
        '200':
          description: Email успешно добавлен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessNullResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: Email successfully added!
                    codeError: null
                    data: null
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '410':
          $ref: '#/components/responses/CodeIsGone'
  /api/auth/refresh-token:
    post:
      tags:
        - API Аунтефикации
      summary: Обновить access token по refresh token
      description: |
        Обновляет пару токенов (JWT + RT) по refresh token.
        Refresh token передаётся:
        - header: `RT: Bearer <refresh_token>` или
        - cookie: `RT=<refresh_token>`
      security:
        - refreshTokenHeader: []
        - refreshTokenCookie: []
      responses:
        '200':
          description: Токены успешно обновлены
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPairResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: Token refreshed successfully
                    codeError: null
                    data:
                      JWT: <new_access_token>
                      RT: <new_refresh_token>
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/file/upload:
    post:
      tags:
        - API работы с файлами
      summary: Загрузить файл
      description: |
        Загрузка файла в хранилище.
        `purpose` определяет назначение и ограничения по типу/размеру.
      security:
        - bearerAuth: []
        - jwtCookie: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/FileUploadRequest'
            encoding:
              file:
                contentType: application/octet-stream
            examples:
              example:
                value:
                  purpose: AVATAR
      responses:
        '200':
          description: Файл успешно загружен
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileUploadResponse'
              examples:
                ok:
                  value:
                    success: true
                    message: File upload successfully
                    codeError: null
                    data:
                      file_id: <object_key>
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequest'
  /file/{id}:
    get:
      tags:
        - API работы с файлами
      summary: Получить файл по id (публичная ссылка)
      description: >
        Получить доступ к файлу по `id`. Требуется JWT.

        Возможная реализация: 302 redirect на реальный URL файла или выдача
        контента файла.
      security:
        - bearerAuth: []
        - jwtCookie: []
      parameters:
        - name: id
          in: path
          required: true
          description: Идентификатор файла
          schema:
            type: string
      responses:
        '200':
          description: Контент файла (если сервер отдаёт файл напрямую)
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '302':
          description: Redirect на URL файла (если используется редирект)
          headers:
            Location:
              description: URL для скачивания/доступа к файлу
              schema:
                type: string
                format: uri
        '400':
          description: Пустой/некорректный id
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Нет прав
        '404':
          description: Файл не найден
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Authorization: Bearer ${JWT}'
    jwtCookie:
      type: apiKey
      in: cookie
      name: JWT
      description: Cookie JWT=${token}
    refreshTokenHeader:
      type: apiKey
      in: header
      name: RT
      description: 'RT: Bearer <refresh_token>'
    refreshTokenCookie:
      type: apiKey
      in: cookie
      name: RT
      description: Cookie RT=<refresh_token>
  parameters:
    CaptchaToken:
      name: captcha-token
      in: header
      required: true
      description: Токен капчи
      schema:
        type: string
  responses:
    InvalidBody:
      description: Невалидное тело запроса / неверный формат
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid_body:
              value:
                success: false
                message: Invalid body
                codeError: invalid_body
                data: null
    Unauthorized:
      description: Неавторизован / неверный OTP / невалидный refresh token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            unauthorized:
              value:
                success: false
                message: Invalid OTP
                codeError: unauthorized
                data: null
    TooManyRequest:
      description: >-
        Превышены лимиты (rate limit). В data.TTL — секунды до следующей
        попытки.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/TooManyRequestResponse'
          examples:
            too_many:
              value:
                success: false
                message: Too many requests
                codeError: too_many_request
                data:
                  TTL: 60
    CodeIsGone:
      description: Одноразовый код истёк/удалён
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            code_is_gone:
              value:
                success: false
                message: The code is no longer valid. Please request a new one!
                codeError: code_is_gone
                data: null
    InvalidCaptcha:
      description: Captcha невалидна/не передана/проверка упала
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid_captcha:
              value:
                success: false
                message: Invalid Captcha
                codeError: invalid_captcha
                data: null
  schemas:
    BaseResponse:
      type: object
      required:
        - success
        - message
        - codeError
        - data
      properties:
        success:
          type: boolean
        message:
          type: string
        codeError:
          description: Код ошибки (null в случае успеха)
          oneOf:
            - type: string
            - type: 'null'
        data:
          description: Полезная нагрузка (может быть null)
          nullable: true
    SuccessNullResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
      example:
        success: true
        message: OK
        codeError: null
        data: null
    ErrorResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
      example:
        success: false
        message: Error
        codeError: invalid_body
        data: null
    TooManyRequestData:
      type: object
      required:
        - TTL
      properties:
        TTL:
          type: integer
          minimum: 0
          description: Секунды до следующей попытки
    TooManyRequestResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
      properties:
        data:
          $ref: '#/components/schemas/TooManyRequestData'
    TokenPair:
      type: object
      required:
        - JWT
        - RT
      properties:
        JWT:
          type: string
          description: Access token (JWT)
        RT:
          type: string
          description: Refresh token
    TokenPairResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
      properties:
        data:
          $ref: '#/components/schemas/TokenPair'
    PhoneSendOtpRequest:
      type: object
      required:
        - recipient
      properties:
        recipient:
          type: string
          description: Телефон в любом "человеческом" формате, сервер нормализует
          example: '+79991234567'
    PhoneLoginRequest:
      type: object
      required:
        - phone
        - otp
      properties:
        phone:
          type: string
          example: '+79991234567'
        otp:
          type: string
          description: OTP код
          example: '123456'
    EmailRecipientRequest:
      type: object
      required:
        - recipient
      properties:
        recipient:
          type: string
          format: email
          example: user@example.com
    EmailAddRequest:
      type: object
      required:
        - email
        - otp
      properties:
        email:
          type: string
          format: email
          example: user@example.com
        otp:
          type: string
          example: '123456'
    FilePurpose:
      type: string
      enum:
        - AVATAR
        - VOICE
        - MESSAGE
    FileUploadRequest:
      type: object
      required:
        - file
        - purpose
      properties:
        file:
          type: string
          format: binary
        purpose:
          $ref: '#/components/schemas/FilePurpose'
    FileUploadData:
      type: object
      required:
        - file_id
      properties:
        file_id:
          type: string
          description: Object key / идентификатор загруженного файла
    FileUploadResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
      properties:
        data:
          $ref: '#/components/schemas/FileUploadData'
