syntax = "proto3";

package nominal.labs.v1;

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

// This file defines version 1 of the Labs platform API. A trusted author
// publishes a TypeScript application that Nominal Core lists and then renders
// full screen inside a sandboxed iframe. The service is implemented by
// apps/labs-service and is served as camelCase JSON over HTTP rather than as
// gRPC. The full wire specification, authorization model, and publish flow are
// documented in the Labs version 1 wire contract.

// GitSource describes where an application was built from. It is modeled on the
// Nix builtins.fetchGit function and uses the same attributes and the same
// default values. The wire contract contains the full table of defaults.
message GitSource {
  // The URL of the repository is required.
  string url = 1 [(buf.validate.field) = {
    required: true
    string: {min_len: 1}
  }];
  // This is the branch or tag to use. When it is not set, the default is HEAD.
  optional string ref = 2 [(buf.validate.field).string.min_len = 1];
  // This is the exact commit to pin. When it is not set, the tip of the branch
  // or tag is used.
  optional string rev = 3 [(buf.validate.field).string.min_len = 1];
  // This controls whether git submodules are checked out. The default is false.
  bool submodules = 4;
  // This is the path to the application within the repository. When it is not
  // set, the default is the repository root.
  optional string dir = 5 [(buf.validate.field).string.min_len = 1];
}

// SourcesManifest describes where an application's source lives. It is a oneof so
// that other version control systems, such as Subversion, can be added later.
message SourcesManifest {
  oneof source {
    option (buf.validate.oneof) = {required: true};

    // The source lives in a git repository.
    GitSource git = 1;
  }
}

// AppManifest is the declarative description that an author publishes with a
// bundle.
message AppManifest {
  // The name of the application is required.
  string name = 1 [(buf.validate.field) = {
    required: true
    string: {min_len: 1}
  }];
  // This records where the application was built from. It is optional.
  optional SourcesManifest sources = 2;
}

// AppDefinition is a published application as stored and returned by the service.
message AppDefinition {
  // This is the resource identifier of the application.
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "com.palantir.ri.ResourceIdentifier"}];
  // This is the workspace that owns the application.
  string workspace_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
  // This is the manifest that the author published.
  AppManifest manifest = 3;
  // This is the same origin path where the host fetches the bundle bytes. It
  // points to the bundle read route on this service.
  string bundle_url = 4;
  // This is the hex encoded SHA-256 digest of the bundle.
  string bundle_sha256 = 5;
  // This is the user who created the application and is its author. The server
  // sets it from the authenticated caller of CreateApp. It is immutable.
  string created_by_user_rid = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // This is the time at which the application was created.
  google.protobuf.Timestamp created_at = 7;
  // This is the time at which the application was last updated.
  google.protobuf.Timestamp updated_at = 8;
  // This indicates whether the application has been archived. Archived
  // applications are hidden from search by default and can be restored with
  // UnarchiveApp.
  bool is_archived = 9;
}

// CreateAppRequest creates a new application from a bundle that was already
// uploaded through the generic Upload Service.
message CreateAppRequest {
  // This is the workspace that will own the application.
  string workspace_rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // This is the manifest to publish.
  AppManifest manifest = 2 [(buf.validate.field) = {required: true}];
  // This is the location of the uploaded bundle object, as returned by the
  // Upload Service when the upload completed.
  string bundle_upload_path = 3 [(buf.validate.field) = {
    required: true
    string: {min_len: 1}
  }];
}

// CreateAppResponse is returned when an application is created.
message CreateAppResponse {
  // This is the application that was created.
  AppDefinition app = 1;
}

// GetAppRequest fetches a single application by its resource identifier.
message GetAppRequest {
  // This is the resource identifier of the application to fetch.
  string rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "com.palantir.ri.ResourceIdentifier"}
  ];
}

// GetAppResponse is returned when an application is fetched.
message GetAppResponse {
  // This is the application that was fetched.
  AppDefinition app = 1;
}

// UpdateAppRequest applies a partial update to an existing application. Any field
// that is not set is left unchanged.
message UpdateAppRequest {
  // This is the resource identifier of the application to update.
  string rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "com.palantir.ri.ResourceIdentifier"}
  ];
  // When this is set, it replaces the stored manifest.
  optional AppManifest manifest = 2;
  // When this is set, it replaces the bundle with a newly uploaded object. When
  // it is not set, the existing bundle is kept. An explicit empty value is
  // rejected so that an invalid bundle path is never stored or dereferenced.
  optional string bundle_upload_path = 3 [(buf.validate.field).string.min_len = 1];
}

// UpdateAppResponse is returned when an application is updated.
message UpdateAppResponse {
  // This is the application after the update.
  AppDefinition app = 1;
}

// ArchiveAppRequest archives an application so that it is hidden from search by
// default. Archiving is reversible with UnarchiveApp.
message ArchiveAppRequest {
  // This is the resource identifier of the application to archive.
  string rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "com.palantir.ri.ResourceIdentifier"}
  ];
}

// ArchiveAppResponse is returned when an application is archived.
message ArchiveAppResponse {
  // This is the application after it was archived.
  AppDefinition app = 1;
}

// UnarchiveAppRequest restores an application that was previously archived.
message UnarchiveAppRequest {
  // This is the resource identifier of the application to unarchive.
  string rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "com.palantir.ri.ResourceIdentifier"}
  ];
}

// UnarchiveAppResponse is returned when an application is unarchived.
message UnarchiveAppResponse {
  // This is the application after it was unarchived.
  AppDefinition app = 1;
}

// SearchAppsRequest lists the applications in a workspace. Results are paginated
// with a page size and a page token. The default page size is 50 and the
// maximum is 200.
message SearchAppsRequest {
  // This is the workspace to search within.
  string workspace_rid = 1 [
    (buf.validate.field) = {
      required: true
      string: {min_len: 1}
    },
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // This filters by a case insensitive substring of the application name. When
  // it is empty, no name filter is applied.
  optional string name_contains = 2;
  // When this is true, archived applications are included in the results. When
  // it is not set, archived applications are excluded.
  optional bool include_archived = 5;
  // This is the maximum number of results to return in one page. When it is not
  // set, the default is 50. The maximum is 200.
  optional int32 page_size = 3 [
    (buf.validate.field).int32.gte = 1,
    (buf.validate.field).int32.lte = 200
  ];
  // This is the cursor that continues a previous search.
  string page_token = 4;
}

// SearchAppsResponse is returned by a search.
message SearchAppsResponse {
  // These are the applications that matched the search.
  repeated AppDefinition apps = 1;
  // This is the cursor that fetches the next page. It is empty when there are no
  // more results.
  string next_page_token = 2;
}

// LabsService manages the lifecycle of Labs applications.
service LabsService {
  // CreateApp publishes a new application. It returns an error if an application
  // with the same workspace and name already exists.
  rpc CreateApp(CreateAppRequest) returns (CreateAppResponse) {
    option (google.api.http) = {
      post: "/labs/v1/apps"
      body: "*"
    };
  }

  // GetApp returns a single application by its resource identifier.
  rpc GetApp(GetAppRequest) returns (GetAppResponse) {
    option (google.api.http) = {get: "/labs/v1/apps/{rid}"};
  }

  // UpdateApp applies a partial update to an existing application.
  rpc UpdateApp(UpdateAppRequest) returns (UpdateAppResponse) {
    option (google.api.http) = {
      patch: "/labs/v1/apps/{rid}"
      body: "*"
    };
  }

  // ArchiveApp archives an application so that it is hidden from search by
  // default. It is reversible with UnarchiveApp.
  rpc ArchiveApp(ArchiveAppRequest) returns (ArchiveAppResponse) {
    option (google.api.http) = {
      post: "/labs/v1/apps/{rid}/archive"
      body: "*"
    };
  }

  // UnarchiveApp restores an application that was previously archived.
  rpc UnarchiveApp(UnarchiveAppRequest) returns (UnarchiveAppResponse) {
    option (google.api.http) = {
      post: "/labs/v1/apps/{rid}/unarchive"
      body: "*"
    };
  }

  // SearchApps lists the applications in a workspace.
  rpc SearchApps(SearchAppsRequest) returns (SearchAppsResponse) {
    option (google.api.http) = {
      post: "/labs/v1/apps/search"
      body: "*"
    };
  }

  // The bundle bytes are read over a plain HTTP route that is not a remote
  // procedure call. The host issues a GET request to the bundle read path for
  // an application, and the bundle_url field points to that path.
}
