/*
  Сервис реализует функционал регистрации в системе RFID-ключей.
  Только зарегистрированные RFID-ключи доступны для активации пользователям
*/
syntax = "proto3";

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

package keyapis.rfidregistry.v1;

option java_package = "ru.keyapis.rfidregistry.v1";
option java_outer_classname = "KeyapisRfidregistryV1Proto";
option java_multiple_files = false;
option java_string_check_utf8 = true;
option go_package = "/keyapis_rfidregistry_v1";
option cc_enable_arenas = true;
option csharp_namespace = "Keyapis.Rfidregistry.V1";
option objc_class_prefix = "KEYAPISRFIDREGISTRYV1";
option php_namespace = "Keyapis\\Rfidregistry\\V1";
option ruby_package = "Keyapis:Rfidregistry:V1";
option optimize_for = LITE_RUNTIME;

// Сервис реестра ключей
service RecordService {
  // Метод сохранения записи.
  // Нельзя отвязать ключ, который привязал кто-то другой.
  // Поддерживает создание и обновление.
  // Метод доступен для: admin, service, device_admin
  rpc PostRecord(PostRecordRequest) returns (PostRecordResponse) {
    option (google.api.http) = {
      post: "/rfidregistry/api/v1/record"
      body: "*"
    };
  }
  // Метод получения записи.
  // Метод доступен для: admin, service, device_admin
  rpc GetRecord(GetRecordRequest) returns (GetRecordResponse) {
    option (google.api.http) = {
      get: "/rfidregistry/api/v1/record/{id}"
    };
  }
  // Метод получения списка записей.
  // Метод доступен для: admin, service, device_admin
  rpc GetRecordList(GetRecordListRequest) returns (stream GetRecordListResponse) {
    option (google.api.http) = {
      get: "/rfidregistry/api/v1/record/list"
    };
  }
  // Метод получения количества записей.
  // Метод доступен для: admin, service, device_admin
  rpc GetRecordCount(GetRecordCountRequest) returns (GetRecordCountResponse) {
    option (google.api.http) = {
      get: "/rfidregistry/api/v1/record/count"
    };
  }
  // Метод удаления записи.
  // Можно удалять только свои записи.
  // Нельзя удалить ключ, если его использует кто-то другой.
  // Метод доступен для: admin, service, device_admin
  rpc DeleteRecord(DeleteRecordRequest) returns (DeleteRecordResponse) {
    option (google.api.http) = {
      delete: "/rfidregistry/api/v1/record/{id}"
    };
  }
}

// Запись.
// # Описание модели
message Record {
  // Идентификатор.
  // Если не передан создаётся сервером.
  // # Тип: Guid
  string id = 1;
  // Справочник типов ключей.
  // # Тип: byte
  enum RfidType {
    // Значение не указано
    RFID_TYPE_UNKNOWN = 0;
    // Брелок
    FOB = 1;
    // Карта
    CARD = 2;
    // Браслет
    BAND = 3;
    // Стикер
    STICKER = 4;
    // Активная метка
    ACTIVE_TAG = 5;
  }
  // Тип ключа
  RfidType rfid_type = 2 [(google.api.field_behavior) = REQUIRED];
  // Справочник типов шифрования ключа.
  // # Тип: byte
  enum EncryptionType {
    // Значение не указано
    ENCRYPTION_TYPE_UNKNOWN = 0;
    // Без шифрования
    SL0 = 1;
    // С шифрованием, копируемый
    SL1 = 2;
    // С шифрованием, не копируемый
    SL3 = 3;
  }
  // Тип шифрования ключа.
  // Максимально поддерживаемый ключом тип шифрования
  EncryptionType encryption_type = 3 [(google.api.field_behavior) = REQUIRED];
  // Номер ключа.
  // Указывается при создании.
  // # Диапазон: 1..14
  string uid = 4 [(google.api.field_behavior) = REQUIRED];
  // Справочник типов статусов ключа.
  // # Тип: byte
  enum StatusType {
    // Значение не указано
    STATUS_TYPE_UNKNOWN = 0;
    // Новый
    NEW = 1;
    // Отгружен
    SHIPPED = 2;
    // Брак
    DEFECT = 3;
    // Использован
    IN_USE = 4;
  }
  // Тип статуса ключа
  StatusType status_type = 5 [(google.api.field_behavior) = REQUIRED];
  // Дата перевода в статус "Новый".
  // # Тип: DateTime
  google.protobuf.Timestamp new_at = 6 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата перевода в статус "Отгружен".
  // # Тип: DateTime
  google.protobuf.Timestamp shipped_at = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата перевода в статус "Брак".
  // # Тип: DateTime
  google.protobuf.Timestamp defect_at = 8 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата перевода в статус "Использован".
  // # Тип: DateTime
  google.protobuf.Timestamp in_use_at = 9 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Идентификатор привязанного ключа.
  // Заполняется при переводе в статус "Использован".
  // # Диапазон: 0..16
  google.protobuf.StringValue in_use_rfid_id = 10;
  // Содержимое ячейки ключа
  message DataCell {
    // Сектор.
    // # Диапазон: 0..127
    int32 section = 1 [(google.api.field_behavior) = REQUIRED];
    // Блок.
    // # Диапазон: 0..127
    int32 block = 2 [(google.api.field_behavior) = REQUIRED];
    // Данные.
    // # Паттерн: /^[A-Za-z0-9=]+$/
    string data = 3 [(google.api.field_behavior) = REQUIRED];
  }
  // Содержимое ячеек ключа
  repeated DataCell data_cells = 11;
  // Идентификатор владельца.
  // Заполняется сервером.
  // # Диапазон: 0..255
  string resource_owner_id = 12 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Номер коробки.
  // # Диапазон: 1..2147483647
  int32 package_info_box = 13;
  // Номер места в коробке.
  // # Диапазон: 1..2147483647
  int32 package_info_place = 14;
  // Дата последнего изменения.
  // Заполняется и обновляется сервером.
  // Заполняется при создании и изменении.
  // Является версией объекта.
  // # Тип: DateTime
  google.protobuf.Timestamp changed_at = 15 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Конфигурация шифрования ключа
  message AesKeyConfiguration {
    // Адрес.
    // # Паттерн: /^[A-Za-z0-9=]+$/
    string address = 1 [(google.api.field_behavior) = REQUIRED];
    // Данные.
    // # Паттерн: /^[A-Za-z0-9=]+$/
    string data = 2 [(google.api.field_behavior) = REQUIRED];
  }
  // Конфигурации шифрования ключа
  repeated AesKeyConfiguration aes_key_configurations = 16;
  // Контрольная сумма
  message Trailer {
    // Сектор.
    // # Диапазон: 0..15
    int32 sector = 1 [(google.api.field_behavior) = REQUIRED];
    // Ключ А.
    // # Диапазон: 1..256
    string key_a = 2 [(google.api.field_behavior) = REQUIRED];
    // Ключ Б.
    // # Диапазон: 1..256
    string key_b = 3 [(google.api.field_behavior) = REQUIRED];
    // Маска доступа.
    // # Диапазон: 1..512
    string access_mask = 4 [(google.api.field_behavior) = REQUIRED];
  }
  // Контрольные суммы.
  // Нужны для ключей SL1 и SL3
  repeated Trailer trailers = 17;
  // Номер заказа.
  // # Диапазон: 0..256
  string order_number = 18 [(google.api.field_behavior) = REQUIRED];
  // Дата заказа.
  // # Тип: DateTime
  google.protobuf.Timestamp order_at = 19 [(google.api.field_behavior) = REQUIRED];
  // Владелец привязанного ключа.
  // Заполняется сервером, значение берётся из токена при использовании ключа.
  // # Диапазон: 0..255
  google.protobuf.StringValue in_use_resource_owner_id = 20 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Признак, удалена ли запись
  bool is_deleted = 21 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Дата удаления.
  // # Тип: DateTime
  google.protobuf.Timestamp deleted_at = 22 [(google.api.field_behavior) = OUTPUT_ONLY];
  // Производитель.
  // # Диапазон: 3..100
  google.protobuf.StringValue vendor_name = 23;
  // Справочник методов идентификации.
  // # Тип: byte
  enum IdentificationMethodType {
    // Значение не указано
    IDENTIFICATION_METHOD_TYPE_UNKNOWN = 0;
    // Значение Mifare
    MIFARE = 1;
    // Значение Bluetooth low energy
    BLE = 2;
    // Значение Ultra high frequency
    UHF = 3;
    // Значение em-marine
    EM_MARINE = 4;
  }
  // Метод идентификации
  IdentificationMethodType identification_method_type = 24;


  // Ошибка сохранения записи.
  // Эти проверки выполняются при работе с базой данных и сторонними сервисами
  message SavingError {
    // Причины:
    // - Недопустимый перевод статуса.
    // - Не переданы обязательные поля https://openapi-key.deploy.rtkit.dev/keyapis_rfidregistry_v1/#таблица-обязательности-передачи-полей-в-request
    message StatusTypeRestricted {}
    // Причины:
    // - Ключ уже существует
    message UidExist {}
    // Причины:
    // - Запись принадлежит другому пользователю
    message OwnedByAnotherUser {}
    // Причины:
    // - Такой ключ уже привязан к другой записи
    message AlreadyLinked {}
    // Причины:
    // - В базе хранится более новая версия записи, значения changed_at отличаются
    message Conflict {}

    // Причина ошибки
    oneof reason {
      // Перевод статуса запрещён
      StatusTypeRestricted status_type_restricted = 1;
      // Запись уже существует
      UidExist uid_exist = 2;
      // Вы не владелец
      OwnedByAnotherUser owned_by_another_user = 3;
      // Ключ уже использован
      AlreadyLinked already_linked = 4;
      // Конфликт версий
      Conflict conflict = 5;
    }
  }
}

// Фильтр записей
message RecordFilter {
  // По типам ключей
  repeated Record.RfidType rfid_types = 1;
  // По типам шифрования
  repeated Record.EncryptionType encryption_types = 2;
  // По номерам ключей
  repeated string uids = 3;
  // По типам статуса
  repeated Record.StatusType status_types = 4;
  // По номерам привязанных ключей
  repeated string in_use_rfid_ids = 5;
  // По владельцам
  repeated string resource_owner_ids = 6;
  // По номерам заказа
  repeated string order_numbers = 7;
  // По владельцам привязанных ключей
  repeated string in_use_resource_owner_ids = 8;
  // По удалённым записям.
  // По умолчанию: false
  google.protobuf.BoolValue is_deleted = 9;
  // По тексту.
  // Если значение не передано то поиск по нему не производится.
  // # Диапазон: 3..64.
  // # Поиск производится по полям:
  // # - Номер заказа;
  // # - Производитель
  google.protobuf.StringValue text = 10;
  // Дата отгрузки от
  google.protobuf.Timestamp start_shipped_at = 11;
  // Дата отгрузки до
  google.protobuf.Timestamp end_shipped_at = 12;
  // По номеру коробки
  repeated int32 package_info_boxes = 13;
  // По номеру места в коробке
  repeated int32 package_info_places = 14;
  // По методу идентификации
  repeated Record.IdentificationMethodType identification_method_types = 15;
}

// Пагинация записей
message RecordPaging {
  // Справочник типов значений сортировки.
  // # Тип: byte
  enum OrderByType {
    // Значение не указано
    ORDER_BY_TYPE_UNKNOWN = 0;
    // По дате перевода в статус new (дате создания)
    NEW_AT = 1;
    // По времени изменения
    CHANGED_AT = 2;
    // По времени заказа
    ORDER_AT = 3;
    // По номеру заказа
    ORDER_NUMBER = 4;
    // По рангу для поиска по тексту.
    // Применяется когда передано поле для поиска по тексту.
    // В случае если текстовое поле не передано, применяется значение по умолчанию
    RANK = 5;
  }
  // Тип значения сортировки.
  // Если значение не передано, то будет взято значение по умолчанию.
  // # По умолчанию: NEW_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 PostRecordRequest {
  // Запись
  Record data = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос сохранения записи
message PostRecordResponse {
  // Ошибка запроса сохранения записи
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка валидации
      ValidationError validation = 1;
      // Ошибка сохранения записи
      Record.SavingError saving = 2;
    }
  }
  // Тип результата
  oneof type {
    // Запись
    Record data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос получения записи
message GetRecordRequest {
  // Идентификатор.
  // # Тип: Guid
  string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос получения записи
message GetRecordResponse {
  // Тип результата
  oneof type {
    // Запись
    Record data = 1;
  }
}

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

// Запрос получения списка
message GetRecordListRequest {
  // Фильтр
  RecordFilter filter = 1;
  // Вариант разбиения на страницы
  oneof pagination {
    // Пагинация
    RecordPaging paging = 2;
  }
}
// Ответ на запрос получения списка записей
message GetRecordListResponse {
  // Ошибка запроса получения списка записей
  message Error {
    // Причина ошибки
    oneof reason {
      // Ошибка фильтрации записей
      ValidationError validation = 1;
    }
  }
  // Тип результата
  oneof type {
    // Запись
    Record data = 1;
    // Ошибка
    Error error = 2;
  }
}

// Запрос удаления записи из реестра.
// Можно удалить только свои записи
message DeleteRecordRequest {
  // Идентификатор записи.
  // # Тип: Guid
  string id = 1 [(google.api.field_behavior) = REQUIRED];
}
// Ответ на запрос удаления записи из реестра
message DeleteRecordResponse {
  // Ошибка удаления записи
  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];
}
