/*
  Сервис реализует функционал управления баннерами.
  Используются как источник Критичнх уведомления, White label компаний и объявления рекламных акций
*/
syntax = "proto3";

import "google/protobuf/timestamp.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/descriptor.proto";
import "google/protobuf/wrappers.proto";

package keyapis.banner.v1;

option java_package = "ru.keyapis.banner.v1";
option java_outer_classname = "KeyapisBannerV1Proto";
option java_multiple_files = false;
option java_string_check_utf8 = true;
option go_package = "/keyapis_banner_v1";
option cc_enable_arenas = true;
option csharp_namespace = "Keyapis.Banner.V1";
option objc_class_prefix = "KEYAPISBANNERV1";
option php_namespace = "Keyapis\\Banner\\V1";
option ruby_package = "Keyapis::Banner::V1";
option optimize_for = LITE_RUNTIME;

// Сервис управления баннерами
service BannerService {
  // Метод сохранения баннера.
  // Поддерживает создание и обновление.
  // Метод доступен для: admin, manager, service
  rpc PostBanner(PostBannerRequest) returns (PostBannerResponse) {
    option (google.api.http) = {
      post: "/banner/api/v1/banner"
      body: "*"
    };
  }
  // Метод получения баннера.
  // Метод доступен для: admin, manager, service
  rpc GetBanner(GetBannerRequest) returns (GetBannerResponse) {
    option (google.api.http) = {
      get: "/banner/api/v1/banner/{id}"
    };
  }
  // Метод получения упрощённого баннера.
  // Не требует авторизации
  rpc GetBannerLite(GetBannerLiteRequest) returns (GetBannerLiteResponse) {
    option (google.api.http) = {
      get: "/banner/api/v1/banner_lite/{id}"
    };
  }
  // Метод получения списка баннеров.
  // Метод доступен для: admin, manager, service
  rpc GetBannerList(GetBannerListRequest) returns (stream GetBannerListResponse) {
    option (google.api.http) = {
      get: "/banner/api/v1/banner/list"
    };
  }
  // Метод получения списка упрощённых баннеров.
  // Возвращает всегда активные баннеры.
  // По умолчанию возвращает первые 20 баннеров осортированных по приоритету, затем по дате начала акции(от самых важных к не важным, затем от самых новых акций к самым старым).
  // Если запрос выполнен хотя бы без одного изпризнака приложения возвращает пустой список.
  // Если запрос выполнен без признаков пользователя метод возвращает пустой список.
  // Не требует авторизации
  rpc GetBannerLiteList(GetBannerLiteListRequest) returns (stream GetBannerLiteListResponse) {
    option (google.api.http) = {
      get: "/banner/api/v1/banner_lite/list"
    };
  }
  // Метод получения количества баннеров.
  // Метод доступен для: admin, manager, service
  rpc GetBannerCount(GetBannerCountRequest) returns (GetBannerCountResponse) {
    option (google.api.http) = {
      get: "/banner/api/v1/banner/count"
    };
  }
  // Метод загрузки картинки баннера.
  // Метод доступен для: admin, manager, service
  rpc PostBannerUploadImage(PostBannerUploadImageRequest) returns (PostBannerUploadImageResponse) {
    option (google.api.http) = {
      post: "/banner/api/v1/banner/{banner_id}/upload_image"
      body: "upload_image"
    };
  }
}

// Баннер.
// # Описание модели
message Banner {
  // Идентификатор.
  // Если не передан создаётся сервером.
  // # Тип: Guid
  string id = 1;
  // Справочник типов баннеров.
  // # Тип: byte
  enum Type {
    // Значение не указано
    TYPE_UNKNOWN = 0;
    // Стандартный баннер
    STANDARD = 1;
  }
  // Тип баннера.
  // Обязательное
  Type type = 2 [(google.api.field_behavior) = REQUIRED];
  // Приоритет.
  // Обязательное.
  // # Диапазон: 1..255
  int32 priority = 3 [(google.api.field_behavior) = REQUIRED];
  // Справочник типов приоритетов.
  // # Тип: byte
  enum PriorityType {
    // Значение не указано
    PRIORITY_TYPE_UNKNOWN = 0;
    // Обычный.
    // Приоритет 31.
    // Вычисляется в логике приложения как priority меньше 64.
    // Извлекаются из базы по фильтру end_priority = 64
    NORMAL = 1;
    // Вайт-лейбл.
    // Приоритет 127.
    // Вычисляется в логике приложения как priority больше или равно 64 и priority меньше 160.
    // Извлекаются из базы по фильтру begin_priority = 64 и end_priority = 160
    WHITE_LABEL = 2;
    // Критикл.
    // Приоритет 191.
    // Вычисляется в логике приложения как priority больше или равно 160.
    // Извлекаются из базы по фильтру begin_priority = 160
    CRITICAL = 3;
  }
  // Тип приоритета.
  // # Поле вычисляется в коде приложения
  PriorityType priority_type = 4 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Название.
  // Обязательное.
  // Обычно это название рекламной компании.
  // Не отображается пользователям.
  // # Диапазон: 3..256
  string title = 5 [(google.api.field_behavior) = REQUIRED];
  // Заголовок.
  // # Диапазон: 3..64
  google.protobuf.StringValue header = 6;
  // Цвет текста заголовка.
  // Все цвета должны быть в формате hex #RRGGBBAA.
  // Пример: #7e00c380.
  // # Диапазон: 9..9.
  // # Паттерн: /^#[0-9a-fA-F]{8}$/
  google.protobuf.StringValue header_color = 7;
  // Подзаголовок.
  // # Диапазон: 3..256
  google.protobuf.StringValue subtitle = 8;
  // Цвет текста подзаголовка.
  // Все цвета должны быть в формате hex #RRGGBBAA.
  // Пример: #7e00c380.
  // # Диапазон: 9..9.
  // # Паттерн: /^#[0-9a-fA-F]{8}$/
  google.protobuf.StringValue subtitle_color = 9;
  // Цвет заднего плана(подложки).
  // Отрисовывается до загрузки картинки и градиента.
  // Все цвета должны быть в формате hex #RRGGBBAA.
  // Пример: #7e00c380.
  // # Диапазон: 9..9.
  // # Паттерн: /^#[0-9a-fA-F]{8}$/
  google.protobuf.StringValue background_color = 10;
  // Градиент
  message Gradient {
    // Линейный градиент
    message Linear {
      // Угол направления линейного градиента в градусах.
      // # Диапазон: 0..360
      float angle = 1;
      // Точка
      message Point {
        // Цвет линейного градиента.
        // Обязательное.
        // Отрисовывается до загрузки картинки и градиента.
        // Все цвета должны быть в формате hex #RRGGBBAA.
        // Пример: #7e00c380.
        // # Диапазон: 9..9.
        // # Паттерн: /^#[0-9a-fA-F]{8}$/
        string color = 1 [(google.api.field_behavior) = REQUIRED];
        // Стоп линейного градиента.
        // Записывается в процентах.
        // # Диапазон: 0..100
        float stop = 2;
      }
      // Точки градиента.
      // Обязательное.
      // # Диапазон: 2..20
      repeated Point points = 2 [(google.api.field_behavior) = REQUIRED];
    }
    // Тип градиента
    oneof type {
      // Линейный градиент
      Linear linear = 1;
    }
  }
  // Градиент
  Gradient background_gradient = 11;
  // Количество дней через которое можно повторно показать баннер.
  // Если null, то закрыть нельзя, баннер повторно выводится каждый раз.
  // # Диапазон: 0..366
  google.protobuf.Int32Value repeat_after_days = 12;
  // Дата начала показа.
  // Обязательное.
  // Бессрочно если значение 2000-01-01.
  // # Тип: DateOnly.
  // # Диапазон: 730119..1095362
  google.protobuf.Timestamp show_start_at = 13 [(google.api.field_behavior) = REQUIRED];
  // Дата конца показа.
  // Обязательное.
  // Бессрочно если значение 3000-01-01.
  // # Тип: DateOnly.
  // # Диапазон: 730119..1095362
  google.protobuf.Timestamp show_ended_at = 14 [(google.api.field_behavior) = REQUIRED];
  // Справочник статусов баннеров.
  // # Тип: byte
  enum StatusType {
    // Значение не указано
    STATUS_TYPE_UNKNOWN = 0;
    // Отложен.
    // Вычисляется в логике приложения как сurr_date меньше show_start_at.
    // Извлекаются из базы по фильтру end_show_start_at = сurr_date
    DEFERRED = 1;
    // Активен.
    // Вычисляется в логике приложения как сurr_date больше или равно show_start_at и сurr_date меньше show_ended_at.
    // Извлекаются из базы по фильтру begin_show_start_at = сurr_date и end_show_ended_at = сurr_date
    ACTIVE = 2;
    // Истёк.
    // Вычисляется в логике приложения как сurr_date больше или равно show_ended_at.
    // Извлекаются из базы по фильтру begin_show_ended_at = сurr_date
    EXPIRED = 3;
  }
  // Статус баннера.
  // # Поле вычисляется в коде приложения
  StatusType status_type = 15 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Объект картинки
  message Image {
    // Справочник типов картинок.
    // # Тип: byte
    enum Type {
      // Значение не указано
      TYPE_UNKNOWN = 0;
      // Пререндеренный баннер в портретном режиме.
      // В портретном режиме ширина меньше или равна высоте.
      // Представление характерно для телефонов.
      // Может содержать дополнительные параметры запроса в ссылке(https://docs.imgproxy.net/generating_the_url?id=processing-options).
      // После загрузки изображения отрисовывается поверх цвета и градиента.
      // Если ссылки нет то нужно использовать ссылку из ландшафтного режима
      PORTRAIT = 1;
      // Пререндеренный баннер в ландшафтном режиме.
      // В ландшафтном режиме ширина больше высоты.
      // Представление характерно для планшетов и декстопа.
      // Может содержать дополнительные параметры запроса в ссылке(https://docs.imgproxy.net/generating_the_url?id=processing-options).
      // После загрузки изображения отрисовывается поверх цвета и градиента.
      // Если ссылки нет то нужно использовать ссылку из портретного режима
      LANDSCAPE = 2;
      // Иконка.
      // Может содержать дополнительные параметры запроса в ссылке(https://docs.imgproxy.net/generating_the_url?id=processing-options).
      // После загрузки изображения отрисовывается поверх цвета и градиента
      ICON = 3;
    }
    // Тип картинки.
    // Обязательное
    Type type = 1 [(google.api.field_behavior) = REQUIRED];
    // Значение ссылки на картинку.
    // Обязательное.
    // # Диапазон: 12..512
    string url = 2 [(google.api.field_behavior) = REQUIRED];
  }
  // Картинки.
  // # Диапазон: 0..20
  repeated Image images = 16;
  // Справочник платформ.
  // # Тип: byte
  enum PlatformType {
    // Значение не указано
    PLATFORM_TYPE_UNKNOWN = 0;
    // Платформа iOS.
    // Приложение размещается в App Store
    IOS = 1;
    // Платформа Android.
    // Приложение размещается в Google Play, App Gallery или прочих сторах
    ANDROID = 2;
    // Платформа Web.
    // Приложение размещается по интернет адресу
    WEB = 3;
  }
  // Ссылка
  message Link {
    // Тип платформы.
    // Обязательное
    PlatformType platform_type = 1 [(google.api.field_behavior) = REQUIRED];
    // Справочник типов ссылок.
    // # Тип: byte
    enum Type {
      // Значение не указано
      TYPE_UNKNOWN = 0;
      // Ссылка для перехода на раздел текущего приложения
      INTERNAL_APP = 1;
      // Ссылка для перехода на стороннее приложение
      EXTERNAL_APP = 2;
      // Ссылка для перехода на веб-вью внутренних (своих) приложений.
      // В окне отсутствует адресаная строка.
      // Для iOS используется WKWebView, для Android используется WebView
      WEB_VIEW = 3;
      // Ссылка для перехода на веб-браузер
      WEB_BROWSER = 4;
      // Ссылка для перехода на веб-вью внешних (партнёрских) приложений.
      // В окне присутствует адресаная строка.
      // Для iOS используется SFSafariViewController, для Android используется ChromeCustomTabs
      EXTERNAL_WEB_VIEW = 5;
    }
    // Тип ссылки.
    // Обязательное
    Type type = 2 [(google.api.field_behavior) = REQUIRED];
    // Значение ссылки для перехода.
    // Обязательное.
    // # Диапазон: 12..512
    string url = 3 [(google.api.field_behavior) = REQUIRED];
    // Список пакетов.
    // Если пусто попытка перехода осуществляется безусловно.
    // Если заполнено, то переход осуществляется только в случае если хоть один пакет есть на устройстве, иначе обработка переходит к следующей ссылке.
    // # Диапазон: 0..20
    repeated string packages = 4;
  }
  // Ссылки для переходов.
  // # Диапазон: 0..100
  repeated Link links = 17;
  // Список тегов пользователей.
  // Обязательное.
  // Описывает кто может видеть баннер.
  // Тэг - максимум 5 символов, значение - максимум 19 символов, количество сегментов - максимум 10.
  // # Диапазон: 1..100.
  // # Паттерн: /^[A-Z-]{1,5}_[0-9A-Z-]{1,19}(?:\.[A-Z-]{1,5}_[0-9A-Z-]{1,19}){0,9}$/
  repeated string user_tags = 18 [(google.api.field_behavior) = REQUIRED];
  // Список тегов приложений.
  // Обязательное.
  // Описывает какие клиенты могут видеть баннер.
  // Тэг - максимум 5 символов, значение - максимум 19 символов, количество сегментов - максимум 10.
  // # Диапазон: 1..20.
  // # Паттерн: /^[A-Z-]{1,5}_[0-9A-Z-]{1,19}(?:\.[A-Z-]{1,5}_[0-9A-Z-]{1,19}){0,9}$/
  repeated string app_tags = 19 [(google.api.field_behavior) = REQUIRED];
  // Дата создания.
  // # Тип: DateTime
  google.protobuf.Timestamp created_at = 20 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата последнего изменения.
  // Заполняется и обновляется сервером.
  // Заполняется при создании и изменении.
  // Является версией объекта.
  // # Тип: DateTime
  google.protobuf.Timestamp changed_at = 21 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Ошибка сохранения.
  // Эти проверки выполняются при работе с базой данных и сторонними сервисами
  message SavingError {
    // Конфликт версий.
    // Причины:
    // - В базе хранится другая версия строки, значения changed_at отличаются
    message Conflict {}

    // Причина ошибки
    oneof reason {
      // Конфликт версий
      Conflict conflict = 1;
    }
  }
}
// Облегчённый баннер
message BannerLite {
  // Идентификатор.
  // # Тип: Guid
  string id = 1;
  // Количество дней через которое можно повторно показать баннер.
  // Если Значение не указано то повторного показа баннера не будет
  google.protobuf.Int32Value repeat_after_days = 2;
  // Упрощённый объект картинки
  message ImageLite {
    // Тип картинки.
    // Обязательное
    Banner.Image.Type type = 1 [(google.api.field_behavior) = REQUIRED];
    // Значение ссылки на картинку.
    // Обязательное
    string url = 2 [(google.api.field_behavior) = REQUIRED];
  }
  // Картинки
  repeated ImageLite images = 3;
  // Упрощённая ссылка
  message LinkLite {
    // Тип ссылки.
    // Обязательное
    Banner.Link.Type type = 1 [(google.api.field_behavior) = REQUIRED];
    // Значение ссылки.
    // Обязательное
    string url = 2 [(google.api.field_behavior) = REQUIRED];
    // Список пакетов.
    // Если пусто попытка перехода осуществляется безусловно.
    // Если заполнено, то переход осуществляется только в случае если хоть один пакет есть на устройстве иначе обработка переходит к следующей ссылке
    repeated string packages = 3;
    // Тип платформы.
    // Обязательное
    Banner.PlatformType platform_type = 4 [(google.api.field_behavior) = REQUIRED];
  }
  // Ссылки для переходов.
  // Возвращаются отфильтрованные по платформе
  repeated LinkLite links = 4;
}
// Фильтр баннеров
message BannerFilter {
  // По тексту.
  // Если значение не передано то поиск по нему не производится.
  // # Диапазон: 3..64.
  // # Поиск производится по полям:
  // # - Название;
  // # - Заголовок;
  // # - Подзаголовок;
  // # - Список тегов пользователей;
  // # - Список тегов приложений
  google.protobuf.StringValue text = 1;
  // По типам баннеров
  repeated Banner.Type types = 2;
  // От приоритета включительно (больше или равно)
  google.protobuf.Int32Value begin_priority = 3;
  // До приоритета (меньше)
  google.protobuf.Int32Value end_priority = 4;
  // По типам приоритетов.
  // Переписывает значения фильтров по приоритету.
  // Если переданы все типы, то фильтр игнорируется(так как нужно вернуть всё)
  repeated Banner.PriorityType priority_types = 5;
  // От даты начала показа включительно (больше или равно)
  google.protobuf.Timestamp begin_show_start_at = 6;
  // До даты начала показа (меньше)
  google.protobuf.Timestamp end_show_start_at = 7;
  // От даты конца показа включительно (больше или равно)
  google.protobuf.Timestamp begin_show_ended_at = 8;
  // До даты конца показа (меньше)
  google.protobuf.Timestamp end_show_ended_at = 9;
  // По статусам баннеров.
  // Переписывает значения фильтров по датам.
  // Если переданы все типы, то фильтр игнорируется(так как нужно вернуть всё)
  repeated Banner.StatusType status_types = 10;
  // По пользовательским тегам.
  // Тэг - максимум 5 символов, значение - максимум 19 символов, количество сегментов - максимум 10.
  // # Диапазон: 0..20.
  // # Паттерн: /^[A-Z-]{1,5}_[0-9A-Z-]{1,19}(?:\.[A-Z-]{1,5}_[0-9A-Z-]{1,19}){0,9}$/
  repeated string user_tags = 11;
  // По тегам приложений.
  // Тэг - максимум 5 символов, значение - максимум 19 символов, количество сегментов - максимум 10.
  // # Диапазон: 0..20.
  // # Паттерн: /^[A-Z-]{1,5}_[0-9A-Z-]{1,19}(?:\.[A-Z-]{1,5}_[0-9A-Z-]{1,19}){0,9}$/
  repeated string app_tags = 12;
}
// Упрощённый фильтр по баннерам
message BannerLiteFilter {
  // По типам баннеров
  repeated Banner.Type types = 1;
  // Название приложения.
  // # Диапазон: 1..19.
  // # Паттерн: /^[0-9A-Z-]{1,19}$/
  google.protobuf.StringValue app_name = 2;
  // Тип платформы
  Banner.PlatformType platform_type = 3;
  // Версия приложения.
  // # Диапазон: 1..19.
  // # Паттерн: /^[0-9A-Z-]{1,19}$/
  google.protobuf.StringValue version = 4;
  // По тегам пользователя.
  // Тэг - максимум 5 символов, значение - максимум 19 символов, количество сегментов - максимум 10.
  // # Диапазон: 1..20.
  // # Паттерн: /^[A-Z-]{1,5}_[0-9A-Z-]{1,19}(?:\.[A-Z-]{1,5}_[0-9A-Z-]{1,19}){0,9}$/
  repeated string user_tags = 5;
}
// Пагинация баннеров
message BannerPaging {
  // Справочник типов значений сортировки.
  // # Тип: byte
  enum OrderByType {
    // Значение не указано
    ORDER_BY_TYPE_UNKNOWN = 0;
    // Дата последнего изменения
    CHANGED_AT = 1;
    // По приоритету, затем по дате начала показа
    PRIORITY_THEN_SHOW_START_AT = 2;
    // Дата создания
    CREATED_AT = 3;
    // Дата начала показа
    SHOW_START_AT = 4;
    // Дата конца показа
    SHOW_ENDED_AT = 5;
    // По рангу для поиска по тексту.
    // Применяется когда передано поле для поиска по тексту.
    // В случае если текстовое поле не передано, применяется значение по умолчанию
    RANK = 6;
  }
  // Тип значения сортировки.
  // Если значение не передано, то будет взято значение по умолчанию.
  // # По умолчанию: CHANGED_AT
  OrderByType order_by_type = 1;
  // Справочник типов направлений сортировки.
  // # Тип: byte
  enum DirectionType {
    // Значение не указано
    DIRECTION_TYPE_UNKNOWN = 0;
    // От большего к меньшему
    DESC = 1;
    // От меньшего к большему
    ASC = 2;
  }
  // Тип направления сортировки.
  // # По умолчанию: DESC
  DirectionType direction_type = 2;
  // Количество записей на страницу.
  // Если значение 0 (не передано), то будет взято значение по умолчанию.
  // # Диапазон: 0..100.
  // # По умолчанию: 20
  int32 limit = 3;
  // Сдвиг.
  // # Диапазон: 0..2147483647
  int32 offset = 4;
}
// Запрос сохранения баннера
message PostBannerRequest {
  // Баннер
  Banner data = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос сохранения баннера
message PostBannerResponse {
  // Ошибка запроса сохранения баннера
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
      // Ошибка сохранения
      Banner.SavingError saving = 2;
    }
  }
  // Тип результата
  oneof type {
    // Баннер
    Banner data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения баннера
message GetBannerRequest {
  // Идентификатор баннера.
  // # Тип: Guid
  string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос получения баннера
message GetBannerResponse {
  // Ошибка запроса получения баннера
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Баннер
    Banner data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения упрощённого баннера
message GetBannerLiteRequest {
  // Идентификатор баннера.
  // # Тип: Guid
  string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос получения упрощённого баннера
message GetBannerLiteResponse {
  // Ошибка запроса получения упрощённого баннера
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Упрощённый баннер
    BannerLite data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения списка баннеров
message GetBannerListRequest {
  // Фильтр
  BannerFilter filter = 1;
  // Вариант разбиения на страницы
  oneof pagination {
    // Пагинация
    BannerPaging paging = 2;
  }
}
// Ответ на запрос получения списка баннеров
message GetBannerListResponse {
  // Ошибка запроса получения списка баннеров
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Баннер
    Banner data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения списка упрощённого баннеров
message GetBannerLiteListRequest {
  // Упрощённый фильтр
  BannerLiteFilter filter = 1;
  // Признак отключения фильтрации по типу платформы
  bool is_link_filter_disabled = 2;
}

// Ответ на запрос получения списка упрощённых баннеров
message GetBannerLiteListResponse {
  // Ошибка запроса получения списка баннеров
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Упрощённый баннер
    BannerLite data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения количества баннеров
message GetBannerCountRequest {
  // Фильтр
  BannerFilter filter = 1;
}
// Ответ на запрос получения количества баннеров
message GetBannerCountResponse {
  // Ошибка запроса получения количества баннеров
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Всего баннеров
    int32 data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Объект загружаемой картинки
message UploadImage {
  // Картинка в формате инлайн base64.
  // Обязательное.
  // Пример: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA...".
  // # Паттерн: /^data:\s*image\/(png|jpeg|jpg);\s*base64/
  string base64 = 1 [(google.api.field_behavior) = REQUIRED];
}
// Запрос загрузки картинки баннера
message PostBannerUploadImageRequest {
  // Идентификатор баннера.
  // Используется для складывания картинок баннера в папку баннера в бакете.
  // Название картинки в папке создаётся из случайного Guid.
  // # Тип: Guid
  string banner_id = 1 [(google.api.field_behavior) = REQUIRED];
  // Загружаемая картинка
  UploadImage upload_image = 2 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос загрузки картинки баннера
message PostBannerUploadImageResponse {
  // Ошибка запроса загрузки картинки баннера
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Ссылка на баннер на imageproxy cdn сервер.
    // Общедоступна в интернете
    string data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Ошибки валидации.
// Эти проверки выполняются до обращения в базу данных
message ValidationError {
  // Путь к полю в формате наименования прото
  string path = 1 [(google.api.field_behavior) = REQUIRED];
  // Валидационное сообщение
  string message = 2 [(google.api.field_behavior) = REQUIRED];
}
