syntax = "proto3";

package twitch.video.usher;
option go_package = "code.justin.tv/video/usherapi/rpc/usher";

import "google/api/annotations.proto";
import "google/protobuf/descriptor.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/empty.proto";

import "code.justin.tv/video/usherapi/rpc/usher/hls_transcode.proto";
import "code.justin.tv/video/usherapi/rpc/usher/dboption.proto";
import "code.justin.tv/video/usherapi/rpc/usher/job.proto";
import "code.justin.tv/video/usherapi/rpc/usher/stream.proto";
import "code.justin.tv/video/usherapi/rpc/usher/channel_properties.proto";
import "code.justin.tv/video/usherapi/rpc/usher/channel.proto";
import "code.justin.tv/video/usherapi/rpc/usher/node.proto";
import "code.justin.tv/video/usherapi/rpc/usher/cluster.proto";

extend google.protobuf.MethodOptions {
  // singlereply instructs this usher client to unwrap usher API array responses
  // into single messages. If this property is set and Usher's response is an
  // array of length == 1, the first element in the array will be deserialized
  // into the response value, otherwise an error will be returned
  bool singlereply = 909091;

  // extract_request_field instructs the client to extract a given field from the JSON request body
  // during the request's RoundTrip and replace the request body with the
  // extracted value. This is used for requests that expect a JSON array of objects as input,
  // since it allows us to extract the repeated field from the input message and use it
  // as the request body. A similar functionality can be achieved using the
  // (google.api.http).body option, but it does not support repeated fields or maps.
  // Relevant documentation: https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#httprule
  string extract_request_field = 909092;
}

service Usher {
  // /hls_transcode APIs

  // HLSTranscodeShowChannel returns the primary transcode for a given channel
  rpc HLSTranscodeShowChannel(HLSTranscodeShowChannelRequest) returns (HLSTranscode) {
    option (google.api.http).get = "/hls_transcode/show_channel/{channel}";
    option (singlereply) = true;
  }

  // HLSTranscodeShowChannelAll returns all transcodes for a given channel
  rpc HLSTranscodeShowChannelAll(HLSTranscodeShowChannelRequest) returns (ListOf_HLSTranscode) {
    option (google.api.http).get = "/hls_transcode/show_all_transcodes_channel/{channel}";
  }

  // HLSTranscodeCreateLVSTranscode creates a single LVS transcode job
  rpc HLSTranscodeCreateLVSTranscode(HLSTranscodeCreateLVSTranscodeRequest) returns (HLSTranscodeCreateLVSTranscodeResponse) {
    option (google.api.http) = {
      post: "/hls_transcode/create_lvs_transcode/{channel}"
      body: "*"
    };
    option (singlereply) = true;
  }

  // HLSTranscodeCreateByChannelID creates a transcode job for a specific channel
  rpc HLSTranscodeCreateByChannelID(HLSTranscodeCreateByChannelIDRequest) returns (HLSTranscodeCreateByChannelIDResponse) {
    option (google.api.http) = {
      post: "/hls_transcode/create_by_channel_id/{channel_id}"
      body: "*"
    };
    option (singlereply) = true;
  }

  // HLSTranscodeShow returns one transcode by id
  rpc HLSTranscodeShow(HLSTranscodeShowRequest) returns (HLSTranscode) {
    option (google.api.http).get = "/hls_transcode/show/{transcode_id}";
    option (singlereply) = true;
  }

  // HLSTranscodeReady marks all transcodes for a given stream as ready
  rpc HLSTranscodeReady(HLSTranscodeReadyRequest) returns (Stream) {
    option (google.api.http).post = "/hls_transcode/ready/{stream_id}";
    option (singlereply) = true;
  }

  // HLSTranscodeLvsReady lvs version of above
  rpc HLSTranscodeLVSReady(HLSTranscodeLVSReadyRequest) returns (Stream) {
    option (google.api.http).post = "/hls_transcode/lvs_ready/{stream_id}";
    option (singlereply) = true;
  }

  // HLSTranscodeKill marks a transcode as terminated in Usher
  rpc HLSTranscodeKill(HLSTranscodeKillRequest) returns (HLSTranscode) {
    option (google.api.http).post = "/hls_transcode/kill/{transcode_id}";
    option (singlereply) = true;
  }

  rpc HLSTranscodeUpdate(HLSTranscodeUpdateRequest) returns (HLSTranscode) {
    option (google.api.http) = {
      post: "/hls_transcode/update/{transcode_id}"
      body: "update"
    };
    option (singlereply) = true;
  }

  rpc HLSTranscodeLVSCustomerTranscodes(HLSTranscodeLVSCustomerTranscodesRequest) returns (ListOf_HLSTranscode) {
    option (google.api.http) = {
      get: "/hls_transcode/lvs_customer_transcodes/{customer_id}"
    };
  }

  rpc HLSTranscodeLVSChannel(HLSTranscodeLVSChannelRequest) returns (HLSTranscode) {
    option (google.api.http) = {
      get: "/hls_transcode/lvs_channel/{customer_id}/{content_id}"
    };
    option (singlereply) = true;
  }

  // HLSTranscodePayloadKeys returns a list of encryption keys which can be used to decrypt
  // video playlist and segment payloads.
  rpc HLSTranscodePayloadKeys(HLSTranscodePayloadKeysRequest) returns (ListOf_HLSTranscodePayloadKeys) {
    option (google.api.http) = {
      get: "/hls_transcode/payload_keys"
    };
  }

  // HLSTranscodeStartByChannelID manually assigns a transcode to the given channel.
  rpc HLSTranscodeStartByChannelID(HLSTranscodeStartByChannelIDRequest) returns (HLSTranscodeStartByChannelIDResponse) {
    option (google.api.http) = {
      post: "/hls_transcode/start_by_channel_id/{channel_id}"
      body: "*"
    };
  }

  // HLSTranscodeDestroyByChannelID deletes transcodes for a given channel id.
  rpc HLSTranscodeDestroyByChannelID(HLSTranscodeDestroyByChannelIDRequest) returns (HLSTranscodeDestroyByChannelIDResponse) {
    option (google.api.http) = {
      post: "/hls_transcode/destroy_by_channel_id/{channel_id}"
      body: "*"
    };
  }

  // HLSTranscodeDestroyByChannel deletes transcodes for a given channel.
  rpc HLSTranscodeDestroyByChannel(HLSTranscodeDestroyByChannelRequest) returns (HLSTranscodeDestroyByChannelResponse) {
    option (google.api.http) = {
      post: "/hls_transcode/destroy/{channel}"
      body: "*"
    };
  }

  // HLSTranscodeDeadTranscodes returns a list of channels with dead transcodes.
  rpc HLSTranscodeDeadTranscodes(HLSTranscodeDeadTranscodesRequest) returns (ListOf_DeadTranscode) {
    option (google.api.http) = {
      get: "/hls_transcode/dead_transcodes"
    };
  }

  // HLSTranscodeChannelsToUpgrade returns a list of channels that should be upgraded to have transcodes.
  rpc HLSTranscodeChannelsToUpgrade(HLSTranscodeChannelsToUpgradeRequest) returns (ListOf_ChannelToUpgrade) {
    option (google.api.http) = {
      get: "/hls_transcode/channels_to_upgrade"
    };
  }

  // HLSTranscodeChannelsToRetry returns a list of channels that do not have a transcode and need to be refired.
  rpc HLSTranscodeChannelsToRetry(HLSTranscodeChannelsToRetryRequest) returns (ListOf_ChannelToRetry) {
    option (google.api.http) = {
      get: "/hls_transcode/channels_to_retry"
    };
  }

  // /stream APIs

  // StreamList returns a list of streams available on the site
  rpc StreamList(StreamListRequest) returns (ListOf_Streams) {
    option (google.api.http).get = "/stream/list";
  }

  // StreamShow returns a single stream by stream id
  rpc StreamShow(StreamShowRequest) returns (Stream) {
    option (google.api.http).get = "/stream/show/{stream_id}";
    option (singlereply) = true;
  }

  // StreamMinuteBroadcasts produces minute broadcast data for spade injection
  rpc StreamMinuteBroadcasts(StreamMinuteBroadcastsRequest) returns (ListOf_StreamMinuteBroadcastsResponse) {
    option (google.api.http).get = "/stream/minute_broadcasts";
  }

  // StreamChannelMappings produces a mapping of every stream to the channel and customer it belongs to.
  rpc StreamChannelMappings(StreamChannelMappingsRequest) returns (ListOf_ChannelMappings) {
    option (google.api.http).get = "/stream/channel_mappings";
  }

  // StreamBulkUpdate updates stream metadata for a list of streams.
  rpc StreamBulkUpdate(StreamBulkUpdateRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      post: "/stream/bulkupdate"
      body: "*"
    };
    option (extract_request_field) = "updates";
  }

  rpc StreamForceReplicationByChannelId(StreamForceReplicationByChannelIdRequest) returns (StreamForceReplicationByChannelIdResponse) {
    option (google.api.http) = {
      post: "/stream/force_replication_by_channel_id/{channel_id}"
    };
    option (singlereply) = true;
  }

  // /channel_properties APIs

  // ChannelPropertiesUpdate updates channel properties for a given channel.
  // This api should only be used by the transcoder
  rpc ChannelPropertiesUpdate(ChannelPropertiesUpdateRequest) returns (ChannelProperties) {
    option (google.api.http) = {
      post: "/channel_properties/update/{channel_properties_id}"
      body: "update"
    };
    option (singlereply) = true;
  }

  // ChannelPropertiesShow shows a specific channel's property set
  rpc ChannelPropertiesShow(ChannelPropertiesShowRequest) returns (ChannelProperties) {
    option (google.api.http).get ="/channel_properties/show/{channel}";
    option (singlereply) = true;
  }

  // ChannelPropertiesStreamViewcounts returns viewcount data for spade injection
  rpc ChannelPropertiesStreamViewcounts(ChannelPropertiesStreamViewcountsRequest) returns (google.protobuf.Struct) {
    option (google.api.http).get = "/channel_properties/stream_viewcounts";
    option (singlereply) = true;
  }

  rpc ChannelPropertiesLiveHLSChannels(ChannelPropertiesLiveHLSChannelsRequest) returns (ChannelPropertiesLiveHLSChannelsResponse) {
    option (google.api.http).get = "/channel_properties/live_hls_channels";
    option (singlereply) = true;
  }

  rpc ChannelPropertiesList(ChannelPropertiesListRequest) returns (ListOf_ChannelPropertiesListResponse) {
    option (google.api.http).get = "/channel_properties/list";
  }

  // /job APIs

  // DO NOT USE THIS API
  // JobSend has been included for completeness, but this API is incorrectly
  // named in as it is specifically intended for use only with vods.
  //
  // https://git-aws.internal.justin.tv/video/usher/blob/8625aaf2dfad2462cf7b570a1d6bc9a8fd2e98b8/api/job.py#L19
  rpc JobSend(JobSendRequest) returns (JobSendResponse) {
    option (google.api.http) = {
      post: "/job/send/{rabbit_key}"
      body: "vod_job"
    };
    option deprecated = true;
  }

  // /dboption APIS

  // DBOptionGet returns a single dboption specified by name
  rpc DBOptionGet(DBOptionGetRequest) returns (DBOptionGetResponse) {
    option (google.api.http).get = "/dboption/get/{name}";
    option (singlereply) = true;
  }

  // DBOptionSet sets the value of a single dboption
  rpc DBOptionSet(DBOptionSetRequest) returns (DBOptionSetResponse) {
    option (google.api.http) = {
      post: "/dboption/set"
      body: "options"
    };
    option (singlereply) = true;
  }

  // DBOptionGetAll returns a filtered list of DBOption entries. This signature
  // looks strange, expecially with the singlereply option being set to true for
  // a list API. This is required because of how usher returns results from list
  // operations. Usher's response is a JSON Array wrapped around a single JSON
  // Object. That object contains key/value pairs corresponding to dboption
  // entries.
  //
  // This means that to correctly model this response, we have to use a
  // google.protobuf.Struct type, which has a specific JSON serialization of
  // being converted to a map of string keys to google.protobuf.Value values.
  // Additionally, we have to use singlereply to ensure the outer JSON Array is
  // flattened away so that the struct may be correctly deserialized.
  rpc DBOptionGetAll(DBOptionGetAllRequest) returns (google.protobuf.Struct) {
    option (google.api.http).get = "/dboption/all/{pattern}";
    option (singlereply) = true;
  }

  // /api/channel rooted APIs

  // ChannelLiveCheck returns the live stream status of a channel
  rpc ChannelLiveCheck(ChannelLiveCheckRequest) returns (ChannelLiveCheckResponse) {
    option (google.api.http).get = "/api/channel/livecheck/{channel_id}";
    option (singlereply) = true;
  }

  // /node APIs

  // NodeList returns a list of all nodes in usher. See the documentation for
  // Node in node.proto for more details.
  rpc NodeList(NodeListRequest) returns (ListOf_Node) {
    option (google.api.http).get = "/node/list";
  }

  // NodeListEdge returns a list of all edge nodes in usher.
  rpc NodeListEdge(NodeListEdgeRequest) returns (ListOf_Node) {
    option (google.api.http).get = "/node/list_edges";
  }

  // NodeShow returns a single node in usher.
  rpc NodeShow(NodeShowRequest) returns (Node) {
    option (google.api.http).get = "/node/show/{name}";
    option (singlereply) = true;
  }

  // NodeEnable enables a single node in usher.
  // This enables the node to receive viewer playback and replication traffic.
  rpc NodeEnable(NodeEnableRequest) returns (google.protobuf.Empty) {
    option (google.api.http).post = "/node/enable/{id}";
  }

  // NodeDisable disables a single node in usher.
  // This disables the node so it stops receiving viewer playback and replication traffic.
  rpc NodeDisable(NodeDisableRequest) returns (google.protobuf.Empty) {
    option (google.api.http).post = "/node/disable/{id}";
  }

  // /cluster APIs

  // ClusterList returns a list of all clusters in usher. See the documentation
  // for Cluster in cluster.proto for more details.
  rpc ClusterList(ClusterListRequest) returns (ListOf_Cluster) {
    option (google.api.http).get = "/cluster/list";
  }

 // ClusterThresholds returns the thresholds at which a stream should be replicated to a
 // cluster/POP.
  rpc ClusterThresholds(ClusterThresholdsRequest) returns (ListOf_Threshold) {
    option (google.api.http).get = "/cluster/thresholds/{origin}";
  }
}
