/*
  Сервис реализует функционал управления приложениями
*/
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";
import "google/api/visibility.proto";

package keyapis.multiapp.v1;

option java_package = "ru.keyapis.multiapp.v1";
option java_outer_classname = "KeyapisMultiappV1Proto";
option java_multiple_files = false;
option java_string_check_utf8 = true;
option go_package = "/keyapis_multiapp_v1";
option cc_enable_arenas = true;
option csharp_namespace = "Keyapis.Multiapp.V1";
option objc_class_prefix = "KEYAPISMULTIAPPV1";
option php_namespace = "Keyapis\\Multiapp\\V1";
option ruby_package = "Keyapis::Multiapp::V1";
option optimize_for = LITE_RUNTIME;

// Сервис управления приложениями для суперапа
service AppService {
  // Метод сохранения приложения.
  // Поддерживает создание и обновление.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=664695668.
  // Метод доступен для: admin, manager, service
  rpc PostApp(PostAppRequest) returns (PostAppResponse) {
    option (google.api.http) = {
      post: "/multiapp/api/v1/app"
      body: "*"
    };
  }
  // Метод получения приложения.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=682907591.
  // Метод доступен для: admin, manager, service, ltp_first
  rpc GetApp(GetAppRequest) returns (GetAppResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app/{id}"
    };
  }
  // Метод получения упрощённого приложения.
  // Не требует авторизации
  rpc GetAppLite(GetAppLiteRequest) returns (GetAppLiteResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app_lite/{id}"
    };
  }
  // Метод получения списка приложений.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=682907591.
  // Метод доступен для: admin, manager, service, ltp_first
  rpc GetAppList(GetAppListRequest) returns (stream GetAppListResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app/list"
    };
  }
  // Метод получения списка упрощённых приложений.
  // Возвращает приложения не в статусе DELETED.
  // Если не передан параметр app_lite_tag.platform_type возвращает пустой список.
  // Если не передан параметр app_lite_tag.app_name возвращает пустой список.
  // Если не передан параметр user_tags, либо user_tags пустой, возвращает пустой список.
  // На основе переданных параметра app_lite_tag формируются app_tags по правилу: https://confluence.rt.ru/pages/viewpage.action?pageId=734713874.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=664695692.
  // Не требует авторизации
  rpc GetAppLiteList(GetAppLiteListRequest) returns (stream GetAppLiteListResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app_lite/list"
    };
  }
  // Метод получения количества упрощённых приложений.
  // Учитывает приложения не в статусе DELETED.
  // Если не передан параметр app_lite_tag.platform_type возвращает 0.
  // Если не передан параметр app_lite_tag.app_name возвращает 0.
  // Если не передан параметр user_tags, либо user_tags пустой, возвращает 0.
  // На основе переданных параметра app_lite_tag формируются app_tags по правилу: https://confluence.rt.ru/pages/viewpage.action?pageId=734713874.
  // Не требует авторизации
  rpc GetAppLiteCount(GetAppLiteCountRequest) returns (GetAppLiteCountResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app_lite/count"
    };
  }
  // Метод получения количества приложений.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=664695692.
  // Метод доступен для: admin, manager, service, ltp_first
  rpc GetAppCount(GetAppCountRequest) returns (GetAppCountResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app/count"
    };
  }
  // Метод получения кода для перехода в приложение.
  // Логика работы метода: https://confluence.rt.ru/pages/viewpage.action?pageId=664695692.
  // Метод доступен для: admin, service, master, slave
  rpc GetAppCode(GetAppCodeRequest) returns (GetAppCodeResponse) {
    option (google.api.http) = {
      get: "/multiapp/api/v1/app/{id}/code"
    };
  }
  // Метод удаления приложения.
  // Soft delete, из базы не удаляется, проставляется статус.
  // Сценарий использования метода: https://confluence.rt.ru/pages/viewpage.action?pageId=682907591.
  // Метод доступен для: admin, manager, service
  rpc DeleteApp(DeleteAppRequest) returns (DeleteAppResponse) {
    option (google.api.http) = {
      delete: "/multiapp/api/v1/app/{id}"
    };
  }
}

// Приложение.
// # Описание модели
message App {
  // Идентификатор.
  // Если не передан создаётся сервером.
  // # Тип: Guid
  string id = 1;
  // Идентификатор партнера.
  // # Тип: Guid
  string partner_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Справочник типов приложений.
  // # Тип: byte
  enum Type {
    // Значение не указано
    TYPE_UNKNOWN = 0;
    // Стандартное приложение
    STANDARD = 1;
  }
  // Тип приложения
  Type type = 3 [(google.api.field_behavior) = REQUIRED];
  // Системное наименование.
  // Не отображается пользователям.
  // # Диапазон: 3..256
  string title = 4 [(google.api.field_behavior) = REQUIRED];
  // Заголовок.
  // # Диапазон: 3..30
  google.protobuf.StringValue header = 5;
  // Подзаголовок.
  // # Диапазон: 3..60
  google.protobuf.StringValue subtitle = 6;
  // Цвет заднего плана(подложки).
  // Все цвета должны быть в формате hex #RRGGBBAA.
  // Пример: #7e00c380.
  // # Диапазон: 9..9.
  // # Паттерн: /^#[0-9a-fA-F]{8}$/
  google.protobuf.StringValue background_color = 7;
  // Справочник статусов приложений.
  // # Тип: byte
  enum StatusType {
    // Значение не указано
    STATUS_TYPE_UNKNOWN = 0;
    // Заблокирован
    BLOCKED = 1;
    // Активен
    ACTIVE = 2;
    // Удален
    DELETED = 3;
  }
  // Статус приложения
  StatusType status_type = 8 [(google.api.field_behavior) = REQUIRED];
  // Справочник платформ.
  // # Тип: 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 = 9;
  // Список тегов пользователей.
  // Обязательное.
  // Описывает кто может видеть приложение.
  // Тэг - максимум 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 = 10 [(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 = 11 [(google.api.field_behavior) = REQUIRED];
  // Дата создания.
  // # Тип: DateTime
  google.protobuf.Timestamp created_at = 12 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата последнего изменения.
  // Заполняется и обновляется сервером.
  // При изменении должен совпадать со значением из БД.
  // Является версией объекта.
  // # Тип: DateTime
  google.protobuf.Timestamp changed_at = 13;
  // Разрешения
  message Permissions {
    // Справочник разрешенных типов токена
    enum AllowedTokenType {
      // Значение не указано
      ALLOWED_TOKEN_TYPE_UNKNOWN = 0;
      // Идентификационный
      ID = 1;
      // Авторизационный
      ACCESS = 2;
    }
    // Список разрешенных типов токена
    repeated AllowedTokenType allowed_token_types = 1;
  }

  // Разрешения
  Permissions permissions = 14 [deprecated = true, (google.api.field_visibility).restriction = "DEPRECATED"];

  // Справочник разрешений
  enum GrantType {
    // Значение не указано
    GRANT_TYPE_UNKNOWN = 0;
    // Разрешение на получение авторизационного токена посредством метода GET /multiapp/api/v1/partner/token
    GET_ACCESS_TOKEN = 1;
    // Разрешение на получение идентификационного доступа посредством метода GET /multiapp/api/v1/partner/token
    GET_ID_TOKEN = 2;
    // Разрешение на отправку уведомлений без проверки назначений приложения методом POST /multiapp/api/v1/notification
    POST_NOTIF_NO_ASSIGN_CHECK = 3;
  }
  // Список разрешений
  repeated GrantType grant_types = 15;

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

    // Переданный партнер не существует.
    // Причины:
    // - В базе нет партнера с переданным partner_id
    message PartnerIsNotExist {}

    // Переданный партнер удален.
    // Причины:
    // - В базе удален партнер с переданным partner_id
    message PartnerIsDeleted {}

    // Причина ошибки
    oneof reason {
      // Конфликт версий
      Conflict conflict = 1;
      // Переданный партнер не существует
      PartnerIsNotExist partner_is_not_exist = 2;
      // Переданный партнер удален
      PartnerIsDeleted partner_is_deleted = 3;
    }
  }
}
// Облегчённое приложение
message AppLite {
  // Идентификатор.
  // # Тип: Guid
  string id = 1;
  // Наименование.
  // Не отображается пользователям.
  // # Диапазон: 3..256
  string title = 2 [(google.api.field_behavior) = REQUIRED];
  // Заголовок.
  // # Диапазон: 3..30
  google.protobuf.StringValue header = 3;
  // Подзаголовок.
  // # Диапазон: 3..60
  google.protobuf.StringValue subtitle = 4;
  // Цвет заднего плана(подложки).
  // Все цвета должны быть в формате hex #RRGGBBAA.
  // Пример: #7e00c380.
  // # Диапазон: 9..9.
  // # Паттерн: /^#[0-9a-fA-F]{8}$/
  google.protobuf.StringValue background_color = 5;
  // Статус приложения
  App.StatusType status_type = 6 [(google.api.field_behavior) = REQUIRED];
  // Упрощённая ссылка
  message LinkLite {
    // Тип платформы.
    // Обязательное
    App.PlatformType platform_type = 1 [(google.api.field_behavior) = REQUIRED];
    // Тип ссылки.
    // Обязательное
    App.Link.Type type = 2 [(google.api.field_behavior) = REQUIRED];
    // Значение ссылки для перехода.
    // Обязательное
    string url = 3 [(google.api.field_behavior) = REQUIRED];
    // Список пакетов.
    // Если пусто попытка перехода осуществляется безусловно.
    // Если заполнено, то переход осуществляется только в случае если хоть один пакет есть на устройстве, иначе обработка переходит к следующей ссылке
    repeated string packages = 4;
  }
  // Ссылки для переходов
  repeated LinkLite links = 7;
}
// Фильтр приложений
message AppFilter {
  // По тексту.
  // Если значение не передано то поиск по нему не производится.
  // # Диапазон: 3..64.
  // # Поиск производится по полям:
  // # - Системное наименование;
  // # - Заголовок;
  // # - Подзаголовок;
  // # - Список тегов пользователей;
  // # - Список тегов приложений
  google.protobuf.StringValue text = 1;
  // По типам приложений
  repeated App.Type types = 2;
  // По идентификаторам партнера.
  // # Тип: Guid
  repeated string partner_ids = 3;
  // По статусам приложений.
  // Если переданы все типы, то фильтр игнорируется(так как нужно вернуть всё)
  repeated App.StatusType status_types = 4;
  // По пользовательским тегам.
  // Тэг - максимум 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 = 5;
  // По тегам приложений.
  // Тэг - максимум 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 = 6;
}
// Упрощённый фильтр по приложениям
message AppLiteFilter {
  // По типам приложений
  repeated App.Type types = 1;
  // По тегам пользователя.
  // Тэг - максимум 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 = 2;
  // Тег для приложения
  message AppLiteTag {
    // Тип платформы
    App.PlatformType platform_type = 1;
    // Название приложения, которое работает с данным сервисом.
    // Для МП и PWA значение "1".
    // # Паттерн: /^[0-9A-Z-]{1,19}$/
    google.protobuf.StringValue app_name = 2;
  }
  // Тег для приложения
  AppLiteTag app_lite_tag = 3;
}
// Пагинация приложений
message AppPaging {
  // Справочник типов значений сортировки.
  // # Тип: byte
  enum OrderByType {
    // Значение не указано
    ORDER_BY_TYPE_UNKNOWN = 0;
    // Дата последнего изменения
    CHANGED_AT = 1;
    // Дата создания
    CREATED_AT = 2;
    // По рангу для поиска по тексту.
    // Применяется когда передано поле для поиска по тексту.
    // В случае если текстовое поле не передано, применяется значение по умолчанию
    RANK = 3;
  }
  // Тип значения сортировки.
  // Если значение не передано, то будет взято значение по умолчанию.
  // # По умолчанию: 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 AppLitePaging {
  // Справочник типов значений сортировки.
  // # Тип: byte
  enum OrderByType {
    // Значение не указано
    ORDER_BY_TYPE_UNKNOWN = 0;
    // Дата последнего изменения
    CHANGED_AT = 1;
    // Дата создания
    CREATED_AT = 2;
  }
  // Тип значения сортировки.
  // Если значение не передано, то будет взято значение по умолчанию.
  // # По умолчанию: CREATED_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 PostAppRequest {
  // Приложение
  App data = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос сохранения приложения
message PostAppResponse {
  // Ошибка запроса сохранения приложения
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
      // Ошибка сохранения
      App.SavingError saving = 2;
    }
  }
  // Тип результата
  oneof type {
    // Приложение
    App data = 1;
    // Ошибка
    Error error = 2;
  }
}

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

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

// Запрос получения списка приложений
message GetAppListRequest {
  // Фильтр
  AppFilter filter = 1;
  // Вариант разбиения на страницы
  oneof pagination {
    // Пагинация
    AppPaging paging = 2;
  }
}
// Ответ на запрос получения списка приложений
message GetAppListResponse {
  // Ошибка запроса получения списка приложений
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Приложение
    App data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения списка упрощённого приложений
message GetAppLiteListRequest {
  // Упрощённый фильтр
  AppLiteFilter filter = 1;
  // Вариант разбиения на страницы
  oneof pagination {
    // Пагинация
    AppLitePaging paging = 2;
  }
}
// Ответ на запрос получения списка упрощённых приложений
message GetAppLiteListResponse {
  // Ошибка запроса получения списка приложений
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Упрощённое приложение
    AppLite data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения количества упрощённых приложений
message GetAppLiteCountRequest {
  // Упрощённый фильтр
  AppLiteFilter filter = 1;
}
// Ответ на запрос получения количества упрощённых приложений
message GetAppLiteCountResponse {
  // Ошибка запроса получения списка приложений
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Всего упрощенных приложений
    int32 data = 1;
    // Ошибка
    Error error = 2;
  }
}

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

// Запрос на получение кода для перехода в приложение
message GetAppCodeRequest {
  // Идентификатор приложения.
  // # Тип: Guid
  string id = 1 [(google.api.field_behavior) = REQUIRED];
}

// Ответ на запрос на получение кода для перехода в приложение
message GetAppCodeResponse {
  // Ошибка запроса получения ссылки на приложение
  message Error {
    // Ошибка наличия публичного ключа у партнера
    message PartnerKeyDoesNotExist {}
    // Приложение не в статусе ACTIVE
    message AppIsInactive {}
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
      // Ошибка наличия публичного ключа у партнера
      PartnerKeyDoesNotExist partner_key_does_not_exist = 2;
      // Приложение не в статусе ACTIVE
      AppIsInactive app_is_inactive = 3;
    }
  }
  // Тип результата
  oneof type {
    // Код
    string data = 1;
    // Ошибка
    Error error = 2;
  }
}

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