Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/projects_servicer.py b/api/v3/projects_servicer.py
new file mode 100644
index 0000000..17d6f93
--- /dev/null
+++ b/api/v3/projects_servicer.py
@@ -0,0 +1,149 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import api_constants
+from api.v3 import monorail_servicer
+from api.v3 import paginator
+from api.v3.api_proto import projects_pb2
+from api.v3.api_proto import projects_prpc_pb2
+from businesslogic import work_env
+
+
+class ProjectsServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Project objects.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = projects_prpc_pb2.ProjectsServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def ListIssueTemplates(self, mc, request):
+    # type: (MonorailContext, ListIssueTemplatesRequest) ->
+    #   ListIssueTemplatesResponse
+    """pRPC API method that implements ListIssueTemplates.
+
+      Raises:
+        InputException if the request.parent is invalid.
+        NoSuchProjectException if no project exists with the given name.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+      templates = we.ListProjectTemplates(project_id)
+
+    return projects_pb2.ListIssueTemplatesResponse(
+        templates=self.converter.ConvertIssueTemplates(project_id, templates))
+
+  @monorail_servicer.PRPCMethod
+  def ListComponentDefs(self, mc, request):
+    # type: (MonorailContext, ListComponentDefsRequest) ->
+    #   ListComponentDefsResponse
+    """pRPC API method that implements ListComponentDefs.
+
+      Raises:
+        InputException if the request.parent is invalid.
+        NoSuchProjectException if the parent project is not found.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+
+      page_size = paginator.CoercePageSize(
+        request.page_size, api_constants.MAX_COMPONENTS_PER_PAGE)
+      pager = paginator.Paginator(
+          parent=request.parent, page_size=page_size)
+      list_result = we.ListComponentDefs(
+          project_id, page_size, pager.GetStart(request.page_token))
+
+      api_component_defs = self.converter.ConvertComponentDefs(
+          list_result.items, project_id)
+
+    return projects_pb2.ListComponentDefsResponse(
+        component_defs=api_component_defs,
+        next_page_token=pager.GenerateNextPageToken(list_result.next_start))
+
+  @monorail_servicer.PRPCMethod
+  def CreateComponentDef(self, mc, request):
+    # type: (MonorailContext, CreateComponentDefRequest) ->
+    #   ComponentDef
+    """pRPC API method that implements CreateComponentDef.
+
+      Raises:
+        InputException if the request is invalid.
+        NoSuchUserException if any given component admins or ccs do not exist.
+        NoSuchProjectException if the parent project does not exist.
+        PermissionException if the requester is not allowed to create
+          this component.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+    admin_ids = rnc.IngestUserNames(
+        mc.cnxn, request.component_def.admins, self.services)
+    cc_ids = rnc.IngestUserNames(
+        mc.cnxn, request.component_def.ccs, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      component_def = we.CreateComponentDef(
+          project_id, request.component_def.value,
+          request.component_def.docstring, admin_ids, cc_ids,
+          request.component_def.labels)
+
+    return self.converter.ConvertComponentDef(component_def)
+
+  @monorail_servicer.PRPCMethod
+  def DeleteComponentDef(self, mc, request):
+    # type: (MonorailContext, DeleteComponentDefRequest) -> Empty
+    """pRPC API method that implements DeleteComponentDef.
+
+      Raises:
+        InputException if the request in invalid.
+        NoSuchComponentException if the component does not exist.
+        PermissionException if the requester is not allowed to delete
+          this component.
+        NoSuchProjectException if the parent project does not exist.
+    """
+    project_id, component_id = rnc.IngestComponentDefNames(
+        mc.cnxn, [request.name], self.services)[0]
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.DeleteComponentDef(project_id, component_id)
+
+    return empty_pb2.Empty()
+
+  @monorail_servicer.PRPCMethod
+  def ListProjects(self, mc, _):
+    # type: (MonorailContext, ListProjectsRequest) -> ListProjectsResponse
+    """pRPC API method that implements ListProjects.
+
+      Raises:
+        InputException if the request.page_token is invalid or the request does
+          not match the previous request that provided the given page_token.
+    """
+    with work_env.WorkEnv(mc, self.services) as we:
+      # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+      # all servicer methods that are scoped to a single Project need to call
+      # mc.LookupLoggedInUserPerms.
+      #  This method does not because it may be scoped to multiple projects.
+      allowed_project_ids = we.ListProjects()
+      projects_dict = we.GetProjects(allowed_project_ids)
+      projects = [projects_dict[proj_id] for proj_id in allowed_project_ids]
+
+    # TODO(crbug.com/monorail/7505): Add pagination logic.
+    return projects_pb2.ListProjectsResponse(
+        projects=self.converter.ConvertProjects(projects))