1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.linecorp.centraldogma.server.internal.api;
18  
19  import static com.google.common.collect.ImmutableList.toImmutableList;
20  import static com.linecorp.centraldogma.server.internal.api.HttpApiUtil.checkStatusArgument;
21  import static com.linecorp.centraldogma.server.internal.api.HttpApiUtil.checkUnremoveArgument;
22  import static com.linecorp.centraldogma.server.internal.api.HttpApiUtil.returnOrThrow;
23  import static java.util.Objects.requireNonNull;
24  
25  import java.util.List;
26  import java.util.concurrent.CompletableFuture;
27  
28  import javax.annotation.Nullable;
29  
30  import com.fasterxml.jackson.databind.JsonNode;
31  
32  import com.linecorp.armeria.common.ContextAwareBlockingTaskExecutor;
33  import com.linecorp.armeria.server.ServiceRequestContext;
34  import com.linecorp.armeria.server.annotation.Consumes;
35  import com.linecorp.armeria.server.annotation.Default;
36  import com.linecorp.armeria.server.annotation.Delete;
37  import com.linecorp.armeria.server.annotation.ExceptionHandler;
38  import com.linecorp.armeria.server.annotation.Get;
39  import com.linecorp.armeria.server.annotation.Param;
40  import com.linecorp.armeria.server.annotation.Patch;
41  import com.linecorp.armeria.server.annotation.Post;
42  import com.linecorp.armeria.server.annotation.ProducesJson;
43  import com.linecorp.armeria.server.annotation.ResponseConverter;
44  import com.linecorp.armeria.server.annotation.StatusCode;
45  import com.linecorp.centraldogma.common.Author;
46  import com.linecorp.centraldogma.internal.api.v1.CreateProjectRequest;
47  import com.linecorp.centraldogma.internal.api.v1.ProjectDto;
48  import com.linecorp.centraldogma.server.internal.api.auth.RequiresAdministrator;
49  import com.linecorp.centraldogma.server.internal.api.auth.RequiresRole;
50  import com.linecorp.centraldogma.server.internal.api.converter.CreateApiResponseConverter;
51  import com.linecorp.centraldogma.server.internal.storage.project.ProjectApiManager;
52  import com.linecorp.centraldogma.server.metadata.ProjectMetadata;
53  import com.linecorp.centraldogma.server.metadata.ProjectRole;
54  import com.linecorp.centraldogma.server.storage.project.Project;
55  
56  /**
57   * Annotated service object for managing projects.
58   */
59  @ProducesJson
60  @ExceptionHandler(HttpApiExceptionHandler.class)
61  public class ProjectServiceV1 {
62  
63      private final ProjectApiManager projectApiManager;
64  
65      public ProjectServiceV1(ProjectApiManager projectApiManager) {
66          this.projectApiManager = requireNonNull(projectApiManager, "projectApiManager");
67      }
68  
69      /**
70       * GET /projects?status={status}
71       *
72       * <p>Returns the list of projects or removed projects.
73       */
74      @Get("/projects")
75      public CompletableFuture<List<ProjectDto>> listProjects(@Param @Nullable String status) {
76          final ContextAwareBlockingTaskExecutor executor =
77                  ServiceRequestContext.current().blockingTaskExecutor();
78          if (status != null) {
79              checkStatusArgument(status);
80              return CompletableFuture.supplyAsync(() -> projectApiManager.listRemovedProjects().keySet()
81                                                                          .stream()
82                                                                          .map(ProjectDto::new)
83                                                                          .collect(toImmutableList()), executor);
84          }
85  
86          return CompletableFuture.supplyAsync(() -> projectApiManager.listProjects().values().stream()
87                                                                      .map(DtoConverter::convert)
88                                                                      .collect(toImmutableList()), executor);
89      }
90  
91      /**
92       * POST /projects
93       *
94       * <p>Creates a new project.
95       */
96      @Post("/projects")
97      @StatusCode(201)
98      @ResponseConverter(CreateApiResponseConverter.class)
99      public CompletableFuture<ProjectDto> createProject(CreateProjectRequest request, Author author) {
100         return projectApiManager.createProject(request.name(), author)
101                                 .handle(returnOrThrow(() -> DtoConverter.convert(
102                                         projectApiManager.getProject(request.name()))));
103     }
104 
105     /**
106      * GET /projects/{projectName}
107      *
108      * <p>Gets the {@link ProjectMetadata} of the specified {@code projectName}.
109      * If a {@code checkPermissionOnly} parameter is {@code true}, it is returned whether the user has
110      * permission to read the metadata of the specified {@code projectName}.
111      */
112     @Get("/projects/{projectName}")
113     @RequiresRole(roles = { ProjectRole.OWNER, ProjectRole.MEMBER })
114     public CompletableFuture<ProjectMetadata> getProjectMetadata(
115             @Param String projectName,
116             @Param("checkPermissionOnly") @Default("false") boolean isCheckPermissionOnly) {
117         if (isCheckPermissionOnly) {
118             return CompletableFuture.completedFuture(null);
119         }
120         return projectApiManager.getProjectMetadata(projectName);
121     }
122 
123     /**
124      * DELETE /projects/{projectName}
125      *
126      * <p>Removes a project.
127      */
128     @Delete("/projects/{projectName}")
129     @RequiresRole(roles = ProjectRole.OWNER)
130     public CompletableFuture<Void> removeProject(Project project, Author author) {
131         return projectApiManager.removeProject(project.name(), author);
132     }
133 
134     /**
135      * DELETE /projects/{projectName}/removed
136      *
137      * <p>Purges a project that was removed before.
138      */
139     @Delete("/projects/{projectName}/removed")
140     @RequiresRole(roles = ProjectRole.OWNER)
141     public CompletableFuture<Void> purgeProject(@Param String projectName, Author author) {
142         return projectApiManager.purgeProject(projectName, author);
143     }
144 
145     /**
146      * PATCH /projects/{projectName}
147      *
148      * <p>Patches a project with the JSON_PATCH. Currently, only unremove project operation is supported.
149      */
150     @Consumes("application/json-patch+json")
151     @Patch("/projects/{projectName}")
152     @RequiresAdministrator
153     public CompletableFuture<ProjectDto> patchProject(@Param String projectName, JsonNode node, Author author) {
154         checkUnremoveArgument(node);
155         return projectApiManager.unremoveProject(projectName, author)
156                                 .handle(returnOrThrow(() -> DtoConverter.convert(
157                                         projectApiManager.getProject(projectName))));
158     }
159 }