syntax = "proto3";

package nominal.authentication.groups.v1;

import "buf/validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "nominal/gen/v1/alias.proto";
import "nominal/gen/v1/error.proto";
import "nominal/scout/elements/v1/elements.proto";

// A group represents a collection of users within an organization
message Group {
  string rid = 1 [
    (nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.GroupRid"},
    (buf.validate.field) = {required: true}
  ];
  string org_rid = 2 [
    (nominal.gen.v1.alias) = {java_name: "io.nominal.authentication.api.OrgRid"},
    (buf.validate.field) = {required: true}
  ];

  // Group ID must be unique per org.
  // If sourced from the customer's IdP, will match IdP group name.
  string group_id = 3 [(buf.validate.field) = {required: true}];

  string display_name = 4 [(buf.validate.field) = {required: true}];
  string description = 5 [(buf.validate.field) = {required: true}];

  // The list of users in this group
  repeated string user_rids = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.authentication.api.UserRid"}];

  google.protobuf.Timestamp created_at = 7 [(buf.validate.field) = {required: true}];
  google.protobuf.Timestamp updated_at = 8 [(buf.validate.field) = {required: true}];

  // Symbol to identify groups
  optional nominal.scout.elements.v1.Symbol symbol = 9;
}

message SearchGroupsQuery {
  oneof query {
    string exact_substring_text = 1;
    // and is implemented to allow for list (use SearchQueryAnd([]))
    SearchQueryAnd and = 2;
    SearchQueryOr or = 3;
  }
}

message SearchQueryAnd {
  repeated SearchGroupsQuery queries = 1;
}

message SearchQueryOr {
  repeated SearchGroupsQuery queries = 1;
}

message SearchGroupsRequest {
  // Page sizes greater than 1_000 will be rejected. Default pageSize is 100.
  optional int32 page_size = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
  SearchGroupsQuery query = 3 [(buf.validate.field) = {required: true}];
  // omitting archivedStatus because currently groups are IDP sync-only
}

message SearchGroupsResponse {
  repeated Group results = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

message GetGroupRequest {
  string group_rid = 1 [
    (nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.GroupRid"},
    (buf.validate.field) = {required: true}
  ];
}

message GetGroupResponse {
  Group group = 1 [(buf.validate.field) = {required: true}];
}

message GetGroupByIdRequest {
  string group_id = 1 [(buf.validate.field) = {required: true}];
}

message GetGroupByIdResponse {
  Group group = 1 [(buf.validate.field) = {required: true}];
}

message GetGroupsRequest {
  repeated string group_rids = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.GroupRid"}];
}

message GetGroupsResponse {
  map<string, Group> groups = 1 [(nominal.gen.v1.alias) = {key_java_name: "io.nominal.scout.rids.api.GroupRid"}];
}

message UpdateGroupMetadataRequest {
  optional string display_name = 1;
  optional string description = 2;
  optional UpdateGroupSymbolWrapper symbol = 3;

  message UpdateGroupSymbolWrapper {
    optional nominal.scout.elements.v1.Symbol value = 1;
  }
}

message UpdateGroupMetadataRequestWrapper {
  string group_rid = 1 [
    (nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.GroupRid"},
    (buf.validate.field) = {required: true}
  ];

  UpdateGroupMetadataRequest request = 2 [(buf.validate.field) = {required: true}];
}

message UpdateGroupMetadataResponse {
  Group group = 1 [(buf.validate.field) = {required: true}];
}

enum GroupErrors {
  // buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX
  GROUP_ERRORS_UNAUTHORIZED = 0 [(nominal.gen.v1.error) = {
    code: "PERMISSION_DENIED"
    message: "Not authorized for group"
  }];
  GROUP_ERRORS_NOT_FOUND = 1 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Group not found"
  }];
  GROUP_ERRORS_REQUESTED_PAGE_SIZE_TOO_LARGE = 2 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Requested page size is too large"
  }];
}

// Service for managing groups within an organization
service GroupService {
  // Get group within the user's organization
  rpc GetGroup(GetGroupRequest) returns (GetGroupResponse) {
    option (google.api.http) = {
      get: "/authentication/groups/v1/{group_rid}"
      response_body: "group"
    };
  }

  // Get batch of groups within the user's organization
  rpc GetGroups(GetGroupsRequest) returns (GetGroupsResponse) {
    option (google.api.http) = {
      post: "/authentication/groups/v1/batch-get"
      body: "group_rids"
      response_body: "groups"
    };
  }

  // Gets a group by its ID within the user's organization
  rpc GetGroupById(GetGroupByIdRequest) returns (GetGroupByIdResponse) {
    option (google.api.http) = {
      get: "/authentication/groups/v1/id/{group_id}"
      response_body: "group"
    };
  }

  // buf:lint:ignore RPC_REQUEST_STANDARD_NAME
  // Updates the metadata of a group
  rpc UpdateGroupMetadata(UpdateGroupMetadataRequestWrapper) returns (UpdateGroupMetadataResponse) {
    option (google.api.http) = {
      put: "/authentication/groups/v1/{group_rid}/metadata"
      body: "request"
      response_body: "group"
    };
  }

  // Searches all groups in the user's organization using given query
  rpc SearchGroups(SearchGroupsRequest) returns (SearchGroupsResponse) {
    option (google.api.http) = {
      post: "/authentication/groups/v1/search"
      response_body: "*"
      body: "*"
    };
  }
}
