{
{content}
- {authenticated && }
+ {authenticated && }
);
}
@@ -69,13 +71,15 @@ const mapStateToProps = (state: any) => {
const error = getFetchMeFailure(state) || getFetchIndexResourcesFailure(state);
const links = getLinks(state);
const meLink = getMeLink(state);
+ const version = getAppVersion(state);
return {
authenticated,
me,
loading,
error,
links,
- meLink
+ meLink,
+ version
};
};
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 924e176024..aa52c2dc62 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -426,6 +426,13 @@
provided
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.1.1
+ provided
+
+
@@ -471,6 +478,49 @@
+
+ io.openapitools.swagger
+ swagger-maven-plugin
+
+
+ sonia.scm.api.v2.resources
+
+ ${basedir}/target/openapi/META-INF/scm
+ openapi
+ JSON,YAML
+ true
+
+
+
+ http://localhost:8081/scm/api
+ local endpoint url
+
+
+
+ SCM-Manager REST-API
+ ${project.version}
+
+ scmmanager@googlegroups.com
+ SCM-Manager
+ https://scm-manager.org
+
+
+ http://www.opensource.org/licenses/bsd-license.php
+ BSD
+
+
+ src/main/doc/openapi.md
+
+
+
+
+
+ generate
+
+
+
+
+
sonia.scm.maven
smp-maven-plugin
@@ -511,9 +561,15 @@
org.apache.maven.plugins
maven-war-plugin
- 2.2
+ 3.1.0
true
+
+
+ target/openapi
+ WEB-INF/classes
+
+
@@ -860,107 +916,9 @@
-
-
-
-
-
-
-
- doc
-
-
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
-
-
- copy-enunciate-configuration
- compile
-
- copy-resources
-
-
- ${project.build.directory}
-
-
- src/main/doc
- true
-
- **/enunciate.xml
-
-
-
-
-
-
-
-
-
- com.webcohesion.enunciate
- enunciate-maven-plugin
-
-
-
- docs
-
- compile
-
-
-
- ${project.build.directory}/enunciate.xml
- ${project.build.directory}
- restdocs
-
-
-
- com.webcohesion.enunciate
- enunciate-top
- ${enunciate.version}
-
-
- com.webcohesion.enunciate
- enunciate-swagger
-
-
-
-
- com.webcohesion.enunciate
- enunciate-lombok
- ${enunciate.version}
-
-
- org.mapstruct
- mapstruct-processor
- ${org.mapstruct.version}
-
-
-
-
-
- org.apache.maven.plugins
- maven-assembly-plugin
-
-
- src/main/doc/assembly.xml
-
-
-
-
- package
-
- single
-
-
-
-
-
-
diff --git a/scm-webapp/src/main/doc/enunciate.xml b/scm-webapp/src/main/doc/enunciate.xml
deleted file mode 100644
index 225d2e0a2c..0000000000
--- a/scm-webapp/src/main/doc/enunciate.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
- SCM-Manager API
-
-
- SCM-Manager API
- This page describes the RESTful Web Service API of SCM-Manager ${project.version}.
- ]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/scm-webapp/src/main/doc/openapi.md b/scm-webapp/src/main/doc/openapi.md
new file mode 100644
index 0000000000..bf565699d4
--- /dev/null
+++ b/scm-webapp/src/main/doc/openapi.md
@@ -0,0 +1,15 @@
+The following REST documentation describes all public endpoints of your SCM-Manager instance.
+You can try the endpoints with or without authentication right on the swagger surface provided by the OpenAPI-Plugin.
+
+For authenticated requests please use the "Authorize" button and insert your preferred authentication method.
+For basic authentication simply use your SCM-Manager credentials. If you want to use the bearer token authentication, you can generate an
+valid token using the authentication endpoint and copy the response body.
+
+SCM-Manager defines a modern ["Level 3"-REST API](https://martinfowler.com/articles/richardsonMaturityModel.html).
+Using the HATEOAS architecture for REST allows us to provide discoverable and self explanatory endpoint definitions.
+The responses are build using the [HAL JSON format](http://stateless.co/hal_specification.html).
+HAL makes the API human-friendly and simplifies the communication between the frontend and the server using links and embedded resources.
+
+We highly suggest using HAL links when creating new functions for SCM-Manager since they are consistent and can be
+permission checked before being append to the response. The links and embedded resources can also be used by plugins, which can
+define new resources or enrich existing ones.
diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
index 40d5458812..cc2f7792c7 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java
@@ -1,19 +1,19 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
+ * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,13 +24,11 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* http://bitbucket.org/sdorra/scm-manager
- *
*/
-
package sonia.scm.api.rest.resources;
import com.google.common.base.MoreObjects;
@@ -38,10 +36,6 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.inject.Inject;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.FeatureNotSupportedException;
@@ -100,8 +94,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
* @author Sebastian Sdorra
*/
// @Path("import/repositories")
-public class RepositoryImportResource
-{
+public class RepositoryImportResource {
/**
* the logger for RepositoryImportResource
@@ -114,13 +107,12 @@ public class RepositoryImportResource
/**
* Constructs a new repository import resource.
*
- * @param manager repository manager
+ * @param manager repository manager
* @param serviceFactory
*/
@Inject
public RepositoryImportResource(RepositoryManager manager,
- RepositoryServiceFactory serviceFactory)
- {
+ RepositoryServiceFactory serviceFactory) {
this.manager = manager;
this.serviceFactory = serviceFactory;
}
@@ -133,37 +125,23 @@ public class RepositoryImportResource
* bundle file is passed to the {@link UnbundleCommandBuilder}. Note: This method
* requires admin privileges.
*
- * @param uriInfo uri info
- * @param type repository type
- * @param name name of the repository
+ * @param uriInfo uri info
+ * @param type repository type
+ * @param name name of the repository
* @param inputStream input bundle
- * @param compressed true if the bundle is gzip compressed
- *
+ * @param compressed true if the bundle is gzip compressed
* @return empty response with location header which points to the imported repository
* @since 1.43
*/
@POST
@Path("{type}/bundle")
- @StatusCodes({
- @ResponseCode(code = 201, condition = "created", additionalHeaders = {
- @ResponseHeader(name = "Location", description = "uri to the imported repository")
- }),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import bundle feature is not supported by this type of repositories or the parameters are not valid"
- ),
- @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response importFromBundle(@Context UriInfo uriInfo,
- @PathParam("type") String type, @FormParam("name") String name,
- @FormParam("bundle") InputStream inputStream, @QueryParam("compressed")
- @DefaultValue("false") boolean compressed)
- {
+ @PathParam("type") String type, @FormParam("name") String name,
+ @FormParam("bundle") InputStream inputStream, @QueryParam("compressed")
+ @DefaultValue("false") boolean compressed) {
Repository repository = doImportFromBundle(type, name, inputStream,
- compressed);
+ compressed);
return buildResponse(uriInfo, repository);
}
@@ -175,43 +153,28 @@ public class RepositoryImportResource
* workaround of the javascript ui extjs. Note: This method requires admin
* privileges.
*
- * @param type repository type
- * @param name name of the repository
+ * @param type repository type
+ * @param name name of the repository
* @param inputStream input bundle
- * @param compressed true if the bundle is gzip compressed
- *
+ * @param compressed true if the bundle is gzip compressed
* @return empty response with location header which points to the imported
- * repository
+ * repository
* @since 1.43
*/
@POST
@Path("{type}/bundle.html")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import bundle feature is not supported by this type of repositories or the parameters are not valid"
- ),
- @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(RestActionUploadResult.class)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_HTML)
public Response importFromBundleUI(@PathParam("type") String type,
- @FormParam("name") String name,
- @FormParam("bundle") InputStream inputStream, @QueryParam("compressed")
- @DefaultValue("false") boolean compressed)
- {
+ @FormParam("name") String name,
+ @FormParam("bundle") InputStream inputStream, @QueryParam("compressed")
+ @DefaultValue("false") boolean compressed) {
Response response;
- try
- {
+ try {
doImportFromBundle(type, name, inputStream, compressed);
response = Response.ok(new RestActionUploadResult(true)).build();
- }
- catch (WebApplicationException ex)
- {
+ } catch (WebApplicationException ex) {
logger.warn("error durring bundle import", ex);
response = Response.fromResponse(ex.getResponse()).entity(
new RestActionUploadResult(false)).build();
@@ -227,31 +190,17 @@ public class RepositoryImportResource
* repository. Note: This method requires admin privileges.
*
* @param uriInfo uri info
- * @param type repository type
+ * @param type repository type
* @param request request object
- *
* @return empty response with location header which points to the imported
- * repository
+ * repository
* @since 1.43
*/
@POST
@Path("{type}/url")
- @StatusCodes({
- @ResponseCode(code = 201, condition = "created", additionalHeaders = {
- @ResponseHeader(name = "Location", description = "uri to the imported repository")
- }),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import feature is not supported by this type of repositories or the parameters are not valid"
- ),
- @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response importFromUrl(@Context UriInfo uriInfo,
- @PathParam("type") String type, UrlImportRequest request)
- {
+ @PathParam("type") String type, UrlImportRequest request) {
RepositoryPermissions.create().check();
checkNotNull(request, "request is required");
checkArgument(!Strings.isNullOrEmpty(request.getName()),
@@ -268,17 +217,12 @@ public class RepositoryImportResource
Repository repository = create(type, request.getName());
RepositoryService service = null;
- try
- {
+ try {
service = serviceFactory.create(repository);
service.getPullCommand().pull(request.getUrl());
- }
- catch (IOException ex)
- {
+ } catch (IOException ex) {
handleImportFailure(ex, repository);
- }
- finally
- {
+ } finally {
IOUtil.close(service);
}
@@ -290,23 +234,12 @@ public class RepositoryImportResource
* directory. Note: This method requires admin privileges.
*
* @param type repository type
- *
* @return imported repositories
*/
@POST
@Path("{type}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import feature is not supported by this type of repositories"
- ),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(Repository[].class)
- @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
- public Response importRepositories(@PathParam("type") String type)
- {
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public Response importRepositories(@PathParam("type") String type) {
RepositoryPermissions.create().check();
List repositories = new ArrayList();
@@ -315,7 +248,8 @@ public class RepositoryImportResource
//J-
return Response.ok(
- new GenericEntity>(repositories) {}
+ new GenericEntity>(repositories) {
+ }
).build();
//J+
}
@@ -327,32 +261,22 @@ public class RepositoryImportResource
* @return imported repositories
*/
@POST
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import feature is not supported by this type of repositories"
- ),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(Repository[].class)
- @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
- public Response importRepositories()
- {
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public Response importRepositories() {
RepositoryPermissions.create().check();
logger.info("start directory import for all supported repository types");
List repositories = new ArrayList();
- for (Type t : findImportableTypes())
- {
+ for (Type t : findImportableTypes()) {
importFromDirectory(repositories, t.getName());
}
//J-
return Response.ok(
- new GenericEntity>(repositories) {}
+ new GenericEntity>(repositories) {
+ }
).build();
//J+
}
@@ -363,72 +287,50 @@ public class RepositoryImportResource
* of failed directories. Note: This method requires admin privileges.
*
* @param type repository type
- *
* @return imported repositories
* @since 1.43
*/
@POST
@Path("{type}/directory")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import feature is not supported by this type of repositories"
- ),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(ImportResult.class)
- @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response importRepositoriesFromDirectory(
- @PathParam("type") String type)
- {
+ @PathParam("type") String type) {
RepositoryPermissions.create().check();
Response response;
RepositoryHandler handler = manager.getHandler(type);
- if (handler != null)
- {
+ if (handler != null) {
logger.info("start directory import for repository type {}", type);
- try
- {
+ try {
ImportResult result;
ImportHandler importHandler = handler.getImportHandler();
- if (importHandler instanceof AdvancedImportHandler)
- {
+ if (importHandler instanceof AdvancedImportHandler) {
logger.debug("start directory import, using advanced import handler");
result =
((AdvancedImportHandler) importHandler)
.importRepositoriesFromDirectory(manager);
- }
- else
- {
+ } else {
logger.debug("start directory import, using normal import handler");
result = new ImportResult(importHandler.importRepositories(manager),
ImmutableList.of());
}
response = Response.ok(result).build();
- }
- catch (FeatureNotSupportedException ex)
- {
+ } catch (FeatureNotSupportedException ex) {
logger
.warn(
"import feature is not supported by repository handler for type "
.concat(type), ex);
response = Response.status(Response.Status.BAD_REQUEST).build();
- }
- catch (IOException ex)
- {
+ } catch (IOException ex) {
logger.warn("exception occured durring directory import", ex);
response = Response.serverError().build();
}
- }
- else
- {
+ } else {
logger.warn("could not find reposiotry handler for type {}", type);
response = Response.status(Response.Status.BAD_REQUEST).build();
}
@@ -445,25 +347,16 @@ public class RepositoryImportResource
* @return list of repository types
*/
@GET
- @TypeHint(Type[].class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(
- code = 400,
- condition = "bad request, the import feature is not supported by this type of repositories"
- ),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
- public Response getImportableTypes()
- {
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ public Response getImportableTypes() {
RepositoryPermissions.create().check();
List types = findImportableTypes();
//J-
return Response.ok(
- new GenericEntity>(types) {}
+ new GenericEntity>(types) {
+ }
).build();
//J+
}
@@ -473,16 +366,13 @@ public class RepositoryImportResource
/**
* Build rest response for repository.
*
- *
- * @param uriInfo uri info
+ * @param uriInfo uri info
* @param repository imported repository
- *
* @return rest response
*/
- private Response buildResponse(UriInfo uriInfo, Repository repository)
- {
+ private Response buildResponse(UriInfo uriInfo, Repository repository) {
URI location = uriInfo.getBaseUriBuilder().path(
- RepositoryResource.class).path(repository.getId()).build();
+ RepositoryResource.class).path(repository.getId()).build();
return Response.created(location).build();
}
@@ -490,15 +380,12 @@ public class RepositoryImportResource
/**
* Check repository type for support for the given command.
*
- *
- * @param type repository type
- * @param cmd command
+ * @param type repository type
+ * @param cmd command
* @param request request object
*/
- private void checkSupport(Type type, Command cmd, Object request)
- {
- if (!(type instanceof RepositoryType))
- {
+ private void checkSupport(Type type, Command cmd, Object request) {
+ if (!(type instanceof RepositoryType)) {
logger.warn("type {} is not a repository type", type.getName());
throw new WebApplicationException(Response.Status.BAD_REQUEST);
@@ -506,8 +393,7 @@ public class RepositoryImportResource
Set cmds = ((RepositoryType) type).getSupportedCommands();
- if (!cmds.contains(cmd))
- {
+ if (!cmds.contains(cmd)) {
logger.warn("type {} does not support this type of import: {}",
type.getName(), request);
@@ -518,24 +404,18 @@ public class RepositoryImportResource
/**
* Creates a new repository with the given name and type.
*
- *
* @param type repository type
* @param name repository name
- *
* @return newly created repository
*/
- private Repository create(String type, String name)
- {
+ private Repository create(String type, String name) {
Repository repository = null;
- try
- {
+ try {
// TODO #8783
// repository = new Repository(null, type, name);
manager.create(repository);
- }
- catch (InternalRepositoryException ex)
- {
+ } catch (InternalRepositoryException ex) {
handleGenericCreationFailure(ex, type, name);
}
@@ -545,17 +425,14 @@ public class RepositoryImportResource
/**
* Start bundle import.
*
- *
- * @param type repository type
- * @param name name of the repository
+ * @param type repository type
+ * @param name name of the repository
* @param inputStream bundle stream
- * @param compressed true if the bundle is gzip compressed
- *
+ * @param compressed true if the bundle is gzip compressed
* @return imported repository
*/
private Repository doImportFromBundle(String type, String name,
- InputStream inputStream, boolean compressed)
- {
+ InputStream inputStream, boolean compressed) {
RepositoryPermissions.create().check();
checkArgument(!Strings.isNullOrEmpty(name),
@@ -564,8 +441,7 @@ public class RepositoryImportResource
Repository repository;
- try
- {
+ try {
Type t = type(type);
checkSupport(t, Command.UNBUNDLE, "bundle");
@@ -576,26 +452,19 @@ public class RepositoryImportResource
File file = File.createTempFile("scm-import-", ".bundle");
- try
- {
+ try {
long length = Files.asByteSink(file).writeFrom(inputStream);
logger.info("copied {} bytes to temp, start bundle import", length);
service = serviceFactory.create(repository);
service.getUnbundleCommand().setCompressed(compressed).unbundle(file);
- }
- catch (InternalRepositoryException ex)
- {
+ } catch (InternalRepositoryException ex) {
handleImportFailure(ex, repository);
- }
- finally
- {
+ } finally {
IOUtil.close(service);
IOUtil.delete(file);
}
- }
- catch (IOException ex)
- {
+ } catch (IOException ex) {
logger.warn("could not create temporary file", ex);
throw new WebApplicationException(ex);
@@ -607,42 +476,29 @@ public class RepositoryImportResource
/**
* Method description
*
- *
* @return
*/
- private List findImportableTypes()
- {
+ private List findImportableTypes() {
List types = new ArrayList();
Collection handlerTypes = manager.getTypes();
- for (Type t : handlerTypes)
- {
+ for (Type t : handlerTypes) {
RepositoryHandler handler = manager.getHandler(t.getName());
- if (handler != null)
- {
- try
- {
- if (handler.getImportHandler() != null)
- {
+ if (handler != null) {
+ try {
+ if (handler.getImportHandler() != null) {
types.add(t);
}
- }
- catch (FeatureNotSupportedException ex)
- {
- if (logger.isTraceEnabled())
- {
+ } catch (FeatureNotSupportedException ex) {
+ if (logger.isTraceEnabled()) {
logger.trace("import handler is not supported", ex);
- }
- else if (logger.isInfoEnabled())
- {
+ } else if (logger.isInfoEnabled()) {
logger.info("{} handler does not support import of repositories",
t.getName());
}
}
- }
- else if (logger.isWarnEnabled())
- {
+ } else if (logger.isWarnEnabled()) {
logger.warn("could not find handler for type {}", t.getName());
}
}
@@ -653,14 +509,12 @@ public class RepositoryImportResource
/**
* Handle creation failures.
*
- *
- * @param ex exception
+ * @param ex exception
* @param type repository type
* @param name name of the repository
*/
private void handleGenericCreationFailure(Exception ex, String type,
- String name)
- {
+ String name) {
logger.error(String.format("could not create repository %s with type %s",
type, name), ex);
@@ -670,20 +524,15 @@ public class RepositoryImportResource
/**
* Handle import failures.
*
- *
- * @param ex exception
+ * @param ex exception
* @param repository repository
*/
- private void handleImportFailure(Exception ex, Repository repository)
- {
+ private void handleImportFailure(Exception ex, Repository repository) {
logger.error("import for repository failed, delete repository", ex);
- try
- {
+ try {
manager.delete(repository);
- }
- catch (InternalRepositoryException | NotFoundException e)
- {
+ } catch (InternalRepositoryException | NotFoundException e) {
logger.error("can not delete repository after import failure", e);
}
@@ -694,27 +543,21 @@ public class RepositoryImportResource
/**
* Import repositories from a specific type.
*
- *
* @param repositories repository list
- * @param type type of repository
+ * @param type type of repository
*/
- private void importFromDirectory(List repositories, String type)
- {
+ private void importFromDirectory(List repositories, String type) {
RepositoryHandler handler = manager.getHandler(type);
- if (handler != null)
- {
+ if (handler != null) {
logger.info("start directory import for repository type {}", type);
- try
- {
+ try {
List repositoryNames =
handler.getImportHandler().importRepositories(manager);
- if (repositoryNames != null)
- {
- for (String repositoryName : repositoryNames)
- {
+ if (repositoryNames != null) {
+ for (String repositoryName : repositoryNames) {
// TODO #8783
/*Repository repository = null; //manager.get(type, repositoryName);
@@ -729,22 +572,14 @@ public class RepositoryImportResource
}*/
}
}
- }
- catch (FeatureNotSupportedException ex)
- {
+ } catch (FeatureNotSupportedException ex) {
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
- }
- catch (IOException ex)
- {
+ } catch (IOException ex) {
+ throw new WebApplicationException(ex);
+ } catch (InternalRepositoryException ex) {
throw new WebApplicationException(ex);
}
- catch (InternalRepositoryException ex)
- {
- throw new WebApplicationException(ex);
- }
- }
- else
- {
+ } else {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
}
@@ -752,17 +587,13 @@ public class RepositoryImportResource
/**
* Method description
*
- *
* @param type
- *
* @return
*/
- private Type type(String type)
- {
+ private Type type(String type) {
RepositoryHandler handler = manager.getHandler(type);
- if (handler == null)
- {
+ if (handler == null) {
logger.warn("no handler for type {} found", type);
throw new WebApplicationException(Response.Status.NOT_FOUND);
@@ -778,24 +609,21 @@ public class RepositoryImportResource
*/
@XmlRootElement(name = "import")
@XmlAccessorType(XmlAccessType.FIELD)
- public static class UrlImportRequest
- {
+ public static class UrlImportRequest {
/**
* Constructs ...
- *
*/
- public UrlImportRequest() {}
+ public UrlImportRequest() {
+ }
/**
* Constructs a new {@link UrlImportRequest}
*
- *
* @param name name of the repository
- * @param url external url of the repository
+ * @param url external url of the repository
*/
- public UrlImportRequest(String name, String url)
- {
+ public UrlImportRequest(String name, String url) {
this.name = name;
this.url = url;
}
@@ -806,13 +634,12 @@ public class RepositoryImportResource
* {@inheritDoc}
*/
@Override
- public String toString()
- {
+ public String toString() {
//J-
return MoreObjects.toStringHelper(this)
- .add("name", name)
- .add("url", url)
- .toString();
+ .add("name", name)
+ .add("url", url)
+ .toString();
//J+
}
@@ -821,40 +648,44 @@ public class RepositoryImportResource
/**
* Returns name of the repository.
*
- *
* @return name of the repository
*/
- public String getName()
- {
+ public String getName() {
return name;
}
/**
* Returns external url of the repository.
*
- *
* @return external url of the repository
*/
- public String getUrl()
- {
+ public String getUrl() {
return url;
}
//~--- fields -------------------------------------------------------------
- /** name of the repository */
+ /**
+ * name of the repository
+ */
private String name;
- /** external url of the repository */
+ /**
+ * external url of the repository
+ */
private String url;
}
//~--- fields ---------------------------------------------------------------
- /** repository manager */
+ /**
+ * repository manager
+ */
private final RepositoryManager manager;
- /** repository service factory */
+ /**
+ * repository service factory
+ */
private final RepositoryServiceFactory serviceFactory;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java
index deab53a708..8e129570fd 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java
@@ -1,24 +1,63 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.ExampleObject;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+import io.swagger.v3.oas.annotations.security.SecuritySchemes;
+import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.security.*;
+import sonia.scm.security.AccessToken;
+import sonia.scm.security.AccessTokenBuilder;
+import sonia.scm.security.AccessTokenBuilderFactory;
+import sonia.scm.security.AccessTokenCookieIssuer;
+import sonia.scm.security.AllowAnonymousAccess;
+import sonia.scm.security.Scope;
+import sonia.scm.security.Tokens;
+import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.*;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Optional;
+@SecuritySchemes({
+ @SecurityScheme(
+ name = "Basic Authentication",
+ description = "HTTP Basic authentication with username and password",
+ scheme = "basic",
+ type = SecuritySchemeType.HTTP
+ ),
+ @SecurityScheme(
+ name = "Bearer Token Authentication",
+ description = "Authentication with a jwt bearer token",
+ scheme = "bearer",
+ bearerFormat = "JWT",
+ type = SecuritySchemeType.HTTP
+ )
+})
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Authentication", description = "Authentication related endpoints")
+})
@Path(AuthenticationResource.PATH)
@AllowAnonymousAccess
public class AuthenticationResource {
@@ -34,21 +73,33 @@ public class AuthenticationResource {
private LogoutRedirection logoutRedirection;
@Inject
- public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer)
- {
+ public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer) {
this.tokenBuilderFactory = tokenBuilderFactory;
this.cookieIssuer = cookieIssuer;
}
@POST
@Path("access_token")
+ @Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "bad request, required parameter is missing"),
- @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Login via Form",
+ description = "Form-based authentication.",
+ tags = "Authentication",
+ hidden = true
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "204", description = "success without content")
+ @ApiResponse(responseCode = "400", description = "bad request, required parameter is missing")
+ @ApiResponse(responseCode = "401", description = "unauthorized, the specified username or password is wrong")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response authenticateViaForm(
@Context HttpServletRequest request,
@Context HttpServletResponse response,
@@ -59,18 +110,41 @@ public class AuthenticationResource {
@POST
@Path("access_token")
+ @Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_JSON)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "bad request, required parameter is missing"),
- @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Login via JSON",
+ description = "JSON-based authentication.",
+ tags = "Authentication",
+ requestBody = @RequestBody(
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = AuthenticationRequestDto.class),
+ examples = @ExampleObject(
+ name = "Simple login",
+ value = "{\n \"username\":\"scmadmin\",\n \"password\":\"scmadmin\",\n \"cookie\":false,\n \"grant_type\":\"password\"\n}",
+ summary = "Authenticate with username and password"
+ )
+ )
+ )
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "204", description = "success without content")
+ @ApiResponse(responseCode = "400", description = "bad request, required parameter is missing")
+ @ApiResponse(responseCode = "401", description = "unauthorized, the specified username or password is wrong")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response authenticateViaJSONBody(
@Context HttpServletRequest request,
@Context HttpServletResponse response,
AuthenticationRequestDto authentication
- ) {
+ ) {
return authenticate(request, response, authentication);
}
@@ -86,12 +160,11 @@ public class AuthenticationResource {
Response res;
Subject subject = SecurityUtils.getSubject();
- try
- {
+ try {
subject.login(Tokens.createAuthenticationToken(request, authentication.getUsername(), authentication.getPassword()));
AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create();
- if ( authentication.getScope() != null ) {
+ if (authentication.getScope() != null) {
tokenBuilder.scope(Scope.valueOf(authentication.getScope()));
}
@@ -101,17 +174,12 @@ public class AuthenticationResource {
cookieIssuer.authenticate(request, response, token);
res = Response.noContent().build();
} else {
- res = Response.ok( token.compact() ).build();
+ res = Response.ok(token.compact()).build();
}
- }
- catch (AuthenticationException ex)
- {
- if (LOG.isTraceEnabled())
- {
+ } catch (AuthenticationException ex) {
+ if (LOG.isTraceEnabled()) {
LOG.trace("authentication failed for user ".concat(authentication.getUsername()), ex);
- }
- else
- {
+ } else {
LOG.warn("authentication failed for user {}", authentication.getUsername());
}
@@ -126,12 +194,10 @@ public class AuthenticationResource {
@DELETE
@Path("access_token")
@Produces(MediaType.APPLICATION_JSON)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response)
- {
+ @Operation(summary = "Logout", description = "Removes the access token.", tags = "Authentication")
+ @ApiResponse(responseCode = "204", description = "success")
+ @ApiResponse(responseCode = "500", description = "internal server error")
+ public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response) {
Subject subject = SecurityUtils.getSubject();
subject.logout();
@@ -139,7 +205,6 @@ public class AuthenticationResource {
// remove authentication cookie
cookieIssuer.invalidate(request, response);
- // TODO anonymous access ??
if (logoutRedirection == null) {
return Response.noContent().build();
} else {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
index 26868e32f5..bfa3f3619d 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
@@ -1,14 +1,18 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import javax.validation.constraints.NotEmpty;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.ReducedModelObject;
import sonia.scm.group.GroupDisplayManager;
import sonia.scm.user.UserDisplayManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
+import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@@ -18,7 +22,9 @@ import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
-
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Autocomplete", description = "Autocomplete related endpoints")
+})
@Path(AutoCompleteResource.PATH)
public class AutoCompleteResource {
public static final String PATH = "v2/autocomplete/";
@@ -43,13 +49,26 @@ public class AutoCompleteResource {
@GET
@Path("users")
@Produces(VndMediaType.AUTOCOMPLETE)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user:autocomplete\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Search user", description = "Returns matching users.", tags = "Autocomplete")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.AUTOCOMPLETE,
+ schema = @Schema(implementation = ReducedObjectModelDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "if the searched string contains less than 2 characters")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user:autocomplete\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public List searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
return map(userDisplayManager.autocomplete(filter));
}
@@ -57,13 +76,25 @@ public class AutoCompleteResource {
@GET
@Path("groups")
@Produces(VndMediaType.AUTOCOMPLETE)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group:autocomplete\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Search groups", description = "Returns matching groups.", tags = "Autocomplete")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.AUTOCOMPLETE,
+ schema = @Schema(implementation = ReducedObjectModelDto.class)
+ ))
+ @ApiResponse(responseCode = "400", description = "if the searched string contains less than 2 characters")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group:autocomplete\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public List searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
return map(groupDisplayManager.autocomplete(filter));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java
index 5d77470637..7528b43eb2 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java
@@ -1,8 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import de.otto.edison.hal.HalRepresentation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginManager;
@@ -44,11 +46,29 @@ public class AvailablePluginResource {
*/
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(CollectionDto.class)
+ @Operation(
+ summary = "Find all available plugins",
+ description = "Returns a collection of available plugins.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PLUGIN_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
@Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getAvailablePlugins() {
PluginPermissions.read().check();
@@ -68,13 +88,37 @@ public class AvailablePluginResource {
*/
@GET
@Path("/{name}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(PluginDto.class)
@Produces(VndMediaType.PLUGIN)
+ @Operation(
+ summary = "Find single available plugin",
+ description = "Returns an available plugins.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PLUGIN,
+ schema = @Schema(implementation = PluginDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no plugin with the specified name found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getAvailablePlugin(@PathParam("name") String name) {
PluginPermissions.read().check();
Optional plugin = pluginManager.getAvailable(name);
@@ -87,15 +131,28 @@ public class AvailablePluginResource {
/**
* Triggers plugin installation.
+ *
* @param name plugin name
* @return HTTP Status.
*/
@POST
@Path("/{name}/install")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Triggers plugin installation",
+ description = "Put single plugin in installation queue. Plugin will be installed after restart.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:manage\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response installPlugin(@PathParam("name") String name, @QueryParam("restart") boolean restartAfterInstallation) {
PluginPermissions.manage().check();
pluginManager.install(name, restartAfterInstallation);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java
index 9e7353b4b7..33c6fb0908 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java
@@ -1,11 +1,11 @@
package sonia.scm.api.v2.resources;
import com.google.common.base.Strings;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.PageResult;
import sonia.scm.repository.Branch;
import sonia.scm.repository.Branches;
@@ -69,15 +69,33 @@ public class BranchRootResource {
@GET
@Path("{branch}")
@Produces(VndMediaType.BRANCH)
- @TypeHint(BranchDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "branches not supported for given repository"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the branch"),
- @ResponseCode(code = 404, condition = "not found, no branch with the specified name for the repository available or repository not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Get single branch", description = "Returns a branch for a repository.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.BRANCH,
+ schema = @Schema(implementation = BranchDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "branches not supported for given repository")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the branch")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no branch with the specified name for the repository available or repository found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branchName) throws IOException {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
@@ -95,24 +113,42 @@ public class BranchRootResource {
}
}
- @Path("{branch}/changesets/")
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Path("{branch}/changesets/")
@Produces(VndMediaType.CHANGESET_COLLECTION)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "Collection of changesets", description = "Returns a collection of changesets for specific branch.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CHANGESET_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changesets available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response history(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("branch") String branchName,
@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
- if (!branchExists(branchName, repositoryService)){
+ if (!branchExists(branchName, repositoryService)) {
throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
}
Repository repository = repositoryService.getRepository();
@@ -143,15 +179,25 @@ public class BranchRootResource {
@POST
@Path("")
@Consumes(VndMediaType.BRANCH_REQUEST)
- @StatusCodes({
- @ResponseCode(code = 201, condition = "create success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"push\" privilege"),
- @ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created branch"))
+ @Operation(summary = "Create branch", description = "Creates a new branch.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "201",
+ description = "create success",
+ headers = @Header(
+ name = "Location",
+ description = "uri to the created branch"
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"push\" privilege")
+ @ApiResponse(responseCode = "409", description = "conflict, a branch with this name already exists")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response create(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@Valid BranchRequestDto branchRequest) throws IOException {
@@ -195,15 +241,33 @@ public class BranchRootResource {
@GET
@Path("")
@Produces(VndMediaType.BRANCH_COLLECTION)
- @TypeHint(CollectionDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "branches not supported for given repository"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"read repository\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no repository found for the given namespace and name"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "List of branches", description = "Returns all branches for a repository.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.BRANCH_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "branches not supported for given repository")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"read repository\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository found for the given namespace and name",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Branches branches = repositoryService.getBranchesCommand().getBranches();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java
index 0766816d4d..1b1bc7b0a1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import sonia.scm.PageResult;
import sonia.scm.repository.Changeset;
@@ -44,15 +45,32 @@ public class ChangesetRootResource {
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.CHANGESET_COLLECTION)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "Collection of changesets", description = "Returns a collection of changesets.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CHANGESET_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changesets available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
@@ -77,16 +95,33 @@ public class ChangesetRootResource {
}
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changeset with the specified id is available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces(VndMediaType.CHANGESET)
- @TypeHint(ChangesetDto.class)
@Path("{id}")
+ @Produces(VndMediaType.CHANGESET)
+ @Operation(summary = "Specific changeset", description = "Returns a specific changeset.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CHANGESET,
+ schema = @Schema(implementation = ChangesetDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changeset with the specified id is available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Repository repository = repositoryService.getRepository();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java
index e5e754afea..5eb87bc04c 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java
@@ -1,8 +1,12 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator;
@@ -21,6 +25,9 @@ import javax.ws.rs.core.Response;
/**
* RESTful Web Service Resource to manage the configuration.
*/
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Instance configuration", description = "Global SCM-Manager instance configuration")
+})
@Path(ConfigResource.CONFIG_PATH_V2)
public class ConfigResource {
@@ -46,13 +53,25 @@ public class ConfigResource {
@GET
@Path("")
@Produces(VndMediaType.CONFIG)
- @TypeHint(UserDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:global\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Instance configuration", description = "Returns the instance configuration.", tags = "Instance configuration")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CONFIG,
+ schema = @Schema(implementation = ConfigDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:read\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response get() {
// We do this permission check in Resource and not in ScmConfiguration, because it must be available for reading
@@ -70,13 +89,18 @@ public class ConfigResource {
@PUT
@Path("")
@Consumes(VndMediaType.CONFIG)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:global\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update instance configuration", description = "Modifies the instance configuration.", tags = "Instance configuration")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response update(@Valid ConfigDto configDto) {
// This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes.
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java
index 9f08c63c13..0e333c5dd7 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java
@@ -2,8 +2,10 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.spotter.ContentType;
import com.github.sdorra.spotter.ContentTypes;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotFoundException;
@@ -11,6 +13,7 @@ import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.IOUtil;
+import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
@@ -43,20 +46,30 @@ public class ContentResource {
* recognized, this will be given in the header Language.
*
* @param namespace the namespace of the repository
- * @param name the name of the repository
- * @param revision the revision
- * @param path The path of the file
- *
+ * @param name the name of the repository
+ * @param revision the revision
+ * @param path The path of the file
*/
@GET
@Path("{revision}/{path: .*}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository"),
- @ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "File content by revision", description = "Returns the content of a file for the given revision in the repository.", tags = "Repository")
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository with the specified name available in the namespace",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
@@ -85,20 +98,34 @@ public class ContentResource {
* the repository. The programming language will be given in the header Language.
*
* @param namespace the namespace of the repository
- * @param name the name of the repository
- * @param revision the revision
- * @param path The path of the file
- *
+ * @param name the name of the repository
+ * @param revision the revision
+ * @param path The path of the file
*/
@HEAD
@Path("{revision}/{path: .*}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository"),
- @ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "File metadata by revision",
+ description = "Returns the content type and the programming language (if it can be detected) of a file for the given revision in the repository.",
+ tags = "Repository"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository with the specified name available in the namespace",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response metadata(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Response.ResponseBuilder responseBuilder = Response.ok();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java
index 14cc7be63f..a41bdb01a3 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java
@@ -1,7 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.DiffCommandBuilder;
@@ -44,23 +46,35 @@ public class DiffRootResource {
* Get the repository diff of a revision
*
* @param namespace repository namespace
- * @param name repository name
- * @param revision the revision
+ * @param name repository name
+ * @param revision the revision
* @return the dif of the revision
* @throws NotFoundException if the repository is not found
*/
@GET
@Path("{revision}")
@Produces(VndMediaType.DIFF)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "Bad Request"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the diff"),
- @ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision , @Pattern(regexp = DIFF_FORMAT_VALUES_REGEX) @DefaultValue("NATIVE") @QueryParam("format") String format ) throws IOException {
+ @Operation(summary = "Diff by revision", description = "Get the repository diff of a revision.", tags = "Repository")
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "400", description = "bad request")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the diff")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no revision with the specified param for the repository available or repository found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
+ public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @Pattern(regexp = DIFF_FORMAT_VALUES_REGEX) @DefaultValue("NATIVE") @QueryParam("format") String format) throws IOException {
HttpUtil.checkForCRLFInjection(revision);
DiffFormat diffFormat = DiffFormat.valueOf(format);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
@@ -77,14 +91,33 @@ public class DiffRootResource {
@GET
@Path("{revision}/parsed")
@Produces(VndMediaType.DIFF_PARSED)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "Bad Request"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the diff"),
- @ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Parsed diff by revision", description = "Get the parsed repository diff of a revision.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.DIFF_PARSED,
+ schema = @Schema(implementation = DiffResultDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "bad request")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the diff")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no revision with the specified param for the repository available or repository not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public DiffResultDto getParsed(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
HttpUtil.checkForCRLFInjection(revision);
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java
index 5e087dc7ca..3a66f9c7a1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import sonia.scm.PageResult;
import sonia.scm.repository.Changeset;
@@ -54,15 +55,32 @@ public class FileHistoryRootResource {
*/
@GET
@Path("{revision}/{path: .*}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.CHANGESET_COLLECTION)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "Changesets to given file", description = "Get all changesets related to the given file starting with the given revision.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CHANGESET_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changesets available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name,
@PathParam("revision") String revision,
@PathParam("path") String path,
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalPermissionResource.java
index f9cd015f45..79cfbb51b8 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalPermissionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalPermissionResource.java
@@ -1,5 +1,11 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.web.VndMediaType;
@@ -10,6 +16,9 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Permissions", description = "Permission related endpoints")
+})
@Path("v2/permissions")
public class GlobalPermissionResource {
@@ -22,6 +31,25 @@ public class GlobalPermissionResource {
@GET
@Produces(VndMediaType.PERMISSION_COLLECTION)
+ @Operation(summary = "List of permissions", description = "Returns all available permissions.", tags = "Permissions")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PERMISSION_COLLECTION,
+ schema = @Schema(implementation = PermissionListDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the permissions")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
@Path("")
public Response getAll() {
String[] permissions = permissionAssigner.getAvailablePermissions().stream().map(PermissionDescriptor::getValue).toArray(String[]::new);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
index b49ad57297..455a82c4f9 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java
@@ -1,10 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.search.SearchRequest;
@@ -25,9 +25,8 @@ import java.util.function.Predicate;
import static com.google.common.base.Strings.isNullOrEmpty;
-
public class GroupCollectionResource {
-
+
private static final int DEFAULT_PAGE_SIZE = 10;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
@@ -56,46 +55,70 @@ public class GroupCollectionResource {
@GET
@Path("")
@Produces(VndMediaType.GROUP_COLLECTION)
- @TypeHint(CollectionDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "\"sortBy\" field unknown"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "List of groups", description = "Returns all groups for a given page number with a given page size.", tags = "Group")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.GROUP_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "\"sortBy\" field unknown")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
- @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
- @QueryParam("sortBy") String sortBy,
- @DefaultValue("false")
- @QueryParam("desc") boolean desc,
- @DefaultValue("") @QueryParam("q") String search
+ @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
+ @QueryParam("sortBy") String sortBy,
+ @DefaultValue("false")
+ @QueryParam("desc") boolean desc,
+ @DefaultValue("") @QueryParam("q") String search
) {
return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc,
- pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
+ pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
* Creates a new group.
+ *
* @param group The group to be created.
* @return A response with the link to the new group (if created successfully).
*/
@POST
@Path("")
@Consumes(VndMediaType.GROUP)
- @StatusCodes({
- @ResponseCode(code = 201, condition = "create success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
- @ResponseCode(code = 409, condition = "conflict, a group with this name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
+ @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group")
+ @ApiResponse(
+ responseCode = "201",
+ description = "create success",
+ headers = @Header(
+ name = "Location",
+ description = "uri to the created group"
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group\" privilege")
+ @ApiResponse(responseCode = "409", description = "conflict, a group with this name already exists")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response create(@Valid GroupDto group) {
return adapter.create(group,
- () -> dtoToGroupMapper.map(group),
- g -> resourceLinks.group().self(g.getName()));
+ () -> dtoToGroupMapper.map(group),
+ g -> resourceLinks.group().self(g.getName()));
}
private Predicate createSearchPredicate(String search) {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupPermissionResource.java
index dfce12e778..8e42ef43e3 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupPermissionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupPermissionResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.PermissionPermissions;
@@ -39,14 +40,31 @@ public class GroupPermissionResource {
@GET
@Path("")
@Produces(VndMediaType.PERMISSION_COLLECTION)
- @TypeHint(PermissionListDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the group"),
- @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Group permission", description = "Returns permissions for a group.", tags = {"Group", "Permissions"})
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PERMISSION_COLLECTION,
+ schema = @Schema(implementation = PermissionListDto.class)
+ ))
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the group")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no group with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getPermissions(@PathParam("id") String id) {
PermissionPermissions.read().check();
Collection permissions = permissionAssigner.readPermissionsForGroup(id);
@@ -62,15 +80,26 @@ public class GroupPermissionResource {
@PUT
@Path("")
@Consumes(VndMediaType.PERMISSION_COLLECTION)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current group does not have the correct privilege"),
- @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update Group permissions", description = "Sets permissions for a group. Overwrites all existing permissions.", tags = {"Group", "Permissions"})
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current group does not have the correct privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no group with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
Collection permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
.map(PermissionDescriptor::new)
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
index cfc1916fc1..2a209408db 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
@@ -40,20 +41,37 @@ public class GroupResource {
* Note: This method requires "group" privilege.
*
* @param id the id/name of the group
- *
*/
@GET
@Path("")
@Produces(VndMediaType.GROUP)
- @TypeHint(GroupDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the group"),
- @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- public Response get(@PathParam("id") String id){
+ @Operation(summary = "Get single group", description = "Returns a group.", tags = "Group")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.GROUP,
+ schema = @Schema(implementation = GroupDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the group")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no group with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
+ public Response get(@PathParam("id") String id) {
return adapter.get(id, groupToGroupDtoMapper::map);
}
@@ -63,17 +81,21 @@ public class GroupResource {
* Note: This method requires "group" privilege.
*
* @param name the name of the group to delete.
- *
*/
@DELETE
@Path("")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Delete group", description = "Deletes the group with the given id.", tags = "Group")
+ @ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the group")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response delete(@PathParam("id") String name) {
return adapter.delete(name);
}
@@ -83,21 +105,32 @@ public class GroupResource {
*
* Note: This method requires "group" privilege.
*
- * @param name name of the group to be modified
+ * @param name name of the group to be modified
* @param group group object to modify
*/
@PUT
@Path("")
@Consumes(VndMediaType.GROUP)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of id/group name"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update group", description = "Modifies a group.", tags = "Group")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of id/group name")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no group with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response update(@PathParam("id") String name, @Valid GroupDto group) {
return adapter.update(name, existing -> dtoToGroupMapper.map(group));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
index 0d98d6239c..6750dfef33 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupRootResource.java
@@ -1,5 +1,8 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
@@ -7,6 +10,9 @@ import javax.ws.rs.Path;
/**
* RESTful Web Service Resource to manage groups and their members.
*/
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Group", description = "Group related endpoints")
+})
@Path(GroupRootResource.GROUPS_PATH_V2)
public class GroupRootResource {
@@ -17,7 +23,7 @@ public class GroupRootResource {
@Inject
public GroupRootResource(Provider groupCollectionResource,
- Provider groupResource) {
+ Provider groupResource) {
this.groupCollectionResource = groupCollectionResource;
this.groupResource = groupResource;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IncomingRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IncomingRootResource.java
index 1062e60c90..2d1cc1cf99 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IncomingRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IncomingRootResource.java
@@ -1,9 +1,10 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.PageResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
@@ -81,17 +82,34 @@ public class IncomingRootResource {
* @return
* @throws Exception
*/
- @Path("{source}/{target}/changesets")
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Path("{source}/{target}/changesets")
@Produces(VndMediaType.CHANGESET_COLLECTION)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "Incoming changesets", description = "Get the incoming changesets from source to target.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.CHANGESET_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changesets available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response incomingChangesets(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
@@ -117,18 +135,34 @@ public class IncomingRootResource {
}
}
-
- @Path("{source}/{target}/diff")
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the changeset"),
- @ResponseCode(code = 404, condition = "not found, no changesets available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Path("{source}/{target}/diff")
@Produces(VndMediaType.DIFF)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "Incoming diff", description = "Get the incoming diff from source to target.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.DIFF,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the changeset")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changesets available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response incomingDiff(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
@@ -155,14 +189,32 @@ public class IncomingRootResource {
@GET
@Path("{source}/{target}/diff/parsed")
@Produces(VndMediaType.DIFF_PARSED)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "Bad Request"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the diff"),
- @ResponseCode(code = 404, condition = "not found, source or target branch for the repository not available or repository not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Incoming parsed diff", description = "Get the incoming parsed diff from source to target.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.DIFF_PARSED,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "bad request")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the diff")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, source or target branch for the repository not available or repository not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response incomingDiffParsed(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("source") String source,
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java
index 1eec99ea96..8995e7f979 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java
@@ -1,6 +1,12 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.security.AllowAnonymousAccess;
import sonia.scm.web.VndMediaType;
@@ -9,6 +15,15 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+@OpenAPIDefinition(
+ security = {
+ @SecurityRequirement(name = "Basic Authentication"),
+ @SecurityRequirement(name = "Bearer Token Authentication")
+ },
+ tags = {
+ @Tag(name = "Index", description = "SCM-Manager Index")
+ }
+)
@Path(IndexResource.INDEX_PATH_V2)
@AllowAnonymousAccess
public class IndexResource {
@@ -24,7 +39,23 @@ public class IndexResource {
@GET
@Path("")
@Produces(VndMediaType.INDEX)
- @TypeHint(IndexDto.class)
+ @Operation(summary = "Get index", description = "Returns the index for the scm-manager instance.", tags = "Index")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.INDEX,
+ schema = @Schema(implementation = IndexDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public IndexDto getIndex() {
return indexDtoGenerator.generate();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
index eff83aee77..a442c70f7e 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
@@ -1,8 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import de.otto.edison.hal.HalRepresentation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginManager;
@@ -43,12 +45,30 @@ public class InstalledPluginResource {
*/
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(CollectionDto.class)
@Produces(VndMediaType.PLUGIN_COLLECTION)
+ @Operation(
+ summary = "Find all installed plugins",
+ description = "Returns a collection of installed plugins.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PLUGIN_COLLECTION,
+ schema = @Schema(implementation = HalRepresentation.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getInstalledPlugins() {
PluginPermissions.read().check();
List plugins = pluginManager.getInstalled();
@@ -61,11 +81,22 @@ public class InstalledPluginResource {
*/
@POST
@Path("/update")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(CollectionDto.class)
+ @Operation(
+ summary = "Update all installed plugins",
+ description = "Updates all installed plugins to the latest compatible version.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:manage\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response updateAll() {
pluginManager.updateAll();
return Response.ok().build();
@@ -75,18 +106,41 @@ public class InstalledPluginResource {
* Returns the installed plugin with the given id.
*
* @param name name of plugin
- *
* @return installed plugin with specified id
*/
@GET
@Path("/{name}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(PluginDto.class)
@Produces(VndMediaType.PLUGIN)
+ @Operation(
+ summary = "Get installed plugin by name",
+ description = "Returns the installed plugin with the given id",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PLUGIN,
+ schema = @Schema(implementation = PluginDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, plugin by given id could not be found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getInstalledPlugin(@PathParam("name") String name) {
PluginPermissions.read().check();
Optional pluginDto = pluginManager.getInstalled(name);
@@ -100,17 +154,29 @@ public class InstalledPluginResource {
/**
* Triggers plugin uninstall.
+ *
* @param name plugin name
* @return HTTP Status.
*/
@POST
@Path("/{name}/uninstall")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Uninstall plugin",
+ description = "Add plugin uninstall to pending queue. The plugin will be removed on restart.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:manage\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response uninstallPlugin(@PathParam("name") String name, @QueryParam("restart") boolean restartAfterInstallation) {
- PluginPermissions.manage().check();
pluginManager.uninstall(name, restartAfterInstallation);
return Response.ok().build();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java
index 2c2e208893..6bc99cebe7 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java
@@ -1,8 +1,11 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
@@ -23,11 +26,13 @@ import javax.ws.rs.core.UriInfo;
/**
* RESTful Web Service Resource to get currently logged in users.
*/
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Current user", description = "Current user related endpoints")
+})
@Path(MeResource.ME_PATH_V2)
public class MeResource {
static final String ME_PATH_V2 = "v2/me/";
-
private final MeDtoFactory meDtoFactory;
private final UserManager userManager;
private final PasswordService passwordService;
@@ -45,12 +50,23 @@ public class MeResource {
@GET
@Path("")
@Produces(VndMediaType.ME)
- @TypeHint(MeDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Current user", description = "Returns the currently logged in user or a 401 if user is not logged in.", tags = "Current user")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.ME,
+ schema = @Schema(implementation = MeDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@Context Request request, @Context UriInfo uriInfo) {
return Response.ok(meDtoFactory.create()).build();
}
@@ -60,13 +76,17 @@ public class MeResource {
*/
@PUT
@Path("password")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
@Consumes(VndMediaType.PASSWORD_CHANGE)
+ @Operation(summary = "Change password", description = "Change password of the current user.", tags = "Current user")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response changePassword(@Valid PasswordChangeDto passwordChange) {
userManager.changePasswordForLoggedInUser(
passwordService.encryptPassword(passwordChange.getOldPassword()),
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java
index eaf165cda1..50cdc0c617 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.Modifications;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.RepositoryService;
@@ -33,16 +34,27 @@ public class ModificationsRootResource {
* file modifications are for example: Modified, Added or Removed.
*/
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the modifications"),
- @ResponseCode(code = 404, condition = "not found, no changeset with the specified id is available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces(VndMediaType.MODIFICATIONS)
- @TypeHint(ModificationsDto.class)
@Path("{revision}")
+ @Produces(VndMediaType.MODIFICATIONS)
+ @Operation(summary = "File modifications", description = "Get the file modifications related to a revision.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.MODIFICATIONS,
+ schema = @Schema(implementation = ModificationsDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the modifications")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no changeset with the specified id is available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Modifications modifications = repositoryService.getModificationsCommand()
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java
index 7b87c612d7..f1d0a107ca 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java
@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
+import io.swagger.v3.oas.annotations.Operation;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.web.VndMediaType;
@@ -42,6 +43,7 @@ public class NamespaceStrategyResource {
@GET
@Path("")
@Produces(VndMediaType.NAMESPACE_STRATEGIES)
+ @Operation(summary = "List of namespace strategies", description = "Returns all available namespace strategies and the current selected.", tags = "Repository")
public NamespaceStrategiesDto get(@Context UriInfo uriInfo) {
String currentStrategy = strategyAsString(namespaceStrategyProvider.get());
List availableStrategies = collectStrategyNames();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java
index 7d4663829e..be8bed9729 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java
@@ -1,10 +1,12 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginManager;
@@ -40,11 +42,30 @@ public class PendingPluginResource {
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.PLUGIN_COLLECTION)
+ @Operation(
+ summary = "Find all pending plugins",
+ description = "Returns a collection of pending plugins.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PLUGIN_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:read\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getPending() {
List pending = pluginManager
.getAvailable()
@@ -71,7 +92,7 @@ public class PendingPluginResource {
if (
PluginPermissions.manage().isPermitted() &&
- (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty())
+ (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty())
) {
linksBuilder.single(link("execute", resourceLinks.pendingPluginCollection().executePending()));
linksBuilder.single(link("cancel", resourceLinks.pendingPluginCollection().cancelPending()));
@@ -103,10 +124,22 @@ public class PendingPluginResource {
@POST
@Path("/execute")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Execute pending",
+ description = "Executes all pending plugin changes. The server will be restarted on this action.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:manage\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response executePending() {
pluginManager.executePendingAndRestart();
return Response.ok().build();
@@ -114,10 +147,22 @@ public class PendingPluginResource {
@POST
@Path("/cancel")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(
+ summary = "Cancel pending",
+ description = "Cancels all pending plugin changes and clear the pending queue.",
+ tags = "Plugin Management"
+ )
+ @ApiResponse(responseCode = "200", description = "success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"plugin:manage\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response cancelPending() {
pluginManager.cancelPending();
return Response.ok().build();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
index 14abbe73d8..3b1613d1c0 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
@@ -1,9 +1,15 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Plugin Management", description = "Plugin management related endpoints")
+})
@Path("v2/plugins")
public class PluginRootResource {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java
index d27b598646..d7fd6a79a4 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java
@@ -1,10 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.apache.shiro.SecurityUtils;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
@@ -63,13 +63,24 @@ public class RepositoryCollectionResource {
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_COLLECTION)
- @TypeHint(CollectionDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "List of repositories", description = "Returns all repositories for a given page number with a given page size.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@@ -92,15 +103,25 @@ public class RepositoryCollectionResource {
@POST
@Path("")
@Consumes(VndMediaType.REPOSITORY)
- @StatusCodes({
- @ResponseCode(code = 201, condition = "create success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
- @ResponseCode(code = 409, condition = "conflict, a repository with this name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
+ @Operation(summary = "Create repository", description = "Creates a new repository.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "201",
+ description = "create success",
+ headers = @Header(
+ name = "Location",
+ description = "uri to the created repository"
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege")
+ @ApiResponse(responseCode = "409", description = "conflict, a repository with this name already exists")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response create(@Valid RepositoryDto repository, @QueryParam("initialize") boolean initialize) {
AtomicReference reference = new AtomicReference<>();
Response response = adapter.create(repository,
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java
index b87a4911a5..1cebbba3c7 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java
@@ -1,9 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import sonia.scm.AlreadyExistsException;
import sonia.scm.NotFoundException;
@@ -64,17 +65,30 @@ public class RepositoryPermissionRootResource {
* @return a web response with the status code 201 and the url to GET the added permission
*/
@POST
- @StatusCodes({
- @ResponseCode(code = 201, condition = "creates", additionalHeaders = {
- @ResponseHeader(name = "Location", description = "uri of the created permission")
- }),
- @ResponseCode(code = 500, condition = "internal server error"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 409, condition = "conflict")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @Consumes(VndMediaType.REPOSITORY_PERMISSION)
@Path("")
+ @Consumes(VndMediaType.REPOSITORY_PERMISSION)
+ @Operation(summary = "Create repository-specific permission", description = "Adds a new permission to the user or group managed by the repository.", tags = {"Repository", "Permissions"})
+ @ApiResponse(
+ responseCode = "201",
+ description = "creates",
+ headers = @Header(name = "Location", description = "uri of the created permission")
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "409", description = "conflict")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryPermissionDto permission) {
log.info("try to add new permission: {}", permission);
Repository repository = load(namespace, name);
@@ -95,14 +109,32 @@ public class RepositoryPermissionRootResource {
* @throws NotFoundException if the repository does not exists
*/
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "ok"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces(VndMediaType.REPOSITORY_PERMISSION)
- @TypeHint(RepositoryPermissionDto.class)
@Path("{permission-name}")
+ @Produces(VndMediaType.REPOSITORY_PERMISSION)
+ @Operation(summary = "Get single repository-specific permission", description = "Get the searched permission with permission name related to a repository.", tags = {"Repository", "Permissions"})
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_PERMISSION,
+ schema = @Schema(implementation = RepositoryPermissionDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) {
Repository repository = load(namespace, name);
RepositoryPermissions.permissionRead(repository).check();
@@ -125,14 +157,32 @@ public class RepositoryPermissionRootResource {
* @throws NotFoundException if the repository does not exists
*/
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "ok"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces(VndMediaType.REPOSITORY_PERMISSION)
- @TypeHint(RepositoryPermissionDto.class)
@Path("")
+ @Produces(VndMediaType.REPOSITORY_PERMISSION)
+ @Operation(summary = "List of repository-specific permissions", description = "Get all permissions related to a repository.", tags = {"Repository", "Permissions"})
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_PERMISSION,
+ schema = @Schema(implementation = RepositoryPermissionDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) {
Repository repository = load(namespace, name);
RepositoryPermissions.permissionRead(repository).check();
@@ -148,14 +198,19 @@ public class RepositoryPermissionRootResource {
* @return a web response with the status code 204
*/
@PUT
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @Consumes(VndMediaType.REPOSITORY_PERMISSION)
@Path("{permission-name}")
+ @Consumes(VndMediaType.REPOSITORY_PERMISSION)
+ @Operation(summary = "Update repository-specific permission", description = "Update a permission to the user or group managed by the repository.", tags = {"Repository", "Permissions"})
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response update(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName,
@@ -194,14 +249,19 @@ public class RepositoryPermissionRootResource {
* @return a web response with the status code 204
*/
@DELETE
- @StatusCodes({
- @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
@Path("{permission-name}")
+ @Operation(summary = "Delete repository-specific permission", description = "Delete a permission with the given name.", tags = {"Repository", "Permissions"})
+ @ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response delete(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName) {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
index 2294dc600e..ed29ac9b52 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
@@ -87,14 +88,39 @@ public class RepositoryResource {
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY)
- @TypeHint(RepositoryDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository"),
- @ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Get single repository", description = "Returns the repository for the given namespace and name.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY,
+ schema = @Schema(implementation = RepositoryDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "401",
+ description = "not authenticated / invalid credentials"
+ )
+ @ApiResponse(
+ responseCode = "403",
+ description = "not authorized, the current user has no privileges to read the repository"
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository with the specified name available in the namespace",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name){
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
}
@@ -110,13 +136,11 @@ public class RepositoryResource {
*/
@DELETE
@Path("")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Delete repository", description = "Deletes the repository with the given namespace and name.", tags = "Repository")
+ @ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege")
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response delete(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return adapter.delete(loadBy(namespace, name));
}
@@ -133,15 +157,19 @@ public class RepositoryResource {
@PUT
@Path("")
@Consumes(VndMediaType.REPOSITORY)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of namespace or name"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no repository with the specified namespace and name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update repository", description = "Modifies the repository for the given namespace and name.", tags = "Repository")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of namespace or name")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository with the specified namespace and name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repository) {
return adapter.update(
loadBy(namespace, name),
@@ -168,7 +196,7 @@ public class RepositoryResource {
}
@Path("branches/")
- public BranchRootResource branches(@PathParam("namespace") String namespace, @PathParam("name") String name) {
+ public BranchRootResource branches() {
return branchRootResource.get();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java
index 99d8672eec..a039b0cb35 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java
@@ -1,10 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleManager;
import sonia.scm.web.VndMediaType;
@@ -51,21 +51,32 @@ public class RepositoryRoleCollectionResource {
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_ROLE_COLLECTION)
- @TypeHint(CollectionDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "\"sortBy\" field unknown"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current repositoryRole does not have the \"repositoryRole\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "List of repository roles", description = "Returns all repository roles for a given page number with a given page size.", tags = "Repository role")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_ROLE_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "\"sortBy\" field unknown")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current repositoryRole does not have the \"repositoryRole\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
- @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
- @QueryParam("sortBy") String sortBy,
- @DefaultValue("false") @QueryParam("desc") boolean desc
+ @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
+ @QueryParam("sortBy") String sortBy,
+ @DefaultValue("false") @QueryParam("desc") boolean desc
) {
return adapter.getAll(page, pageSize, x -> true, sortBy, desc,
- pageResult -> repositoryRoleCollectionToDtoMapper.map(page, pageSize, pageResult));
+ pageResult -> repositoryRoleCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
@@ -79,15 +90,25 @@ public class RepositoryRoleCollectionResource {
@POST
@Path("")
@Consumes(VndMediaType.REPOSITORY_ROLE)
- @StatusCodes({
- @ResponseCode(code = 201, condition = "create success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"),
- @ResponseCode(code = 409, condition = "conflict, a repository role with this name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repositoryRole"))
+ @Operation(summary = "Create repository role", description = "Creates a new repository role.", tags = "Repository role")
+ @ApiResponse(
+ responseCode = "201",
+ description = "create success",
+ headers = @Header(
+ name = "Location",
+ description = "uri to the created repository role"
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repositoryRole\" privilege")
+ @ApiResponse(responseCode = "409", description = "conflict, a repository role with this name already exists")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response create(@Valid RepositoryRoleDto repositoryRole) {
return adapter.create(repositoryRole, () -> dtoToRepositoryRoleMapper.map(repositoryRole), u -> resourceLinks.repositoryRole().self(u.getName()));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java
index 59adbce264..7f38eaa0c2 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleManager;
import sonia.scm.web.VndMediaType;
@@ -45,14 +46,31 @@ public class RepositoryRoleResource {
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_ROLE)
- @TypeHint(RepositoryRoleDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository role"),
- @ResponseCode(code = 404, condition = "not found, no repository role with the specified name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Get single repository role", description = "Returns the repository role for the given name.", tags = "Repository role")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_ROLE,
+ schema = @Schema(implementation = RepositoryRoleDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository role")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository role with the specified name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@PathParam("name") String name) {
return adapter.get(name, repositoryRoleToDtoMapper::map);
}
@@ -66,13 +84,11 @@ public class RepositoryRoleResource {
*/
@DELETE
@Path("")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Delete repository role", description = "Deletes the repository role with the given name.", tags = "Repository role")
+ @ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repositoryRole\" privilege")
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response delete(@PathParam("name") String name) {
return adapter.delete(name);
}
@@ -88,15 +104,19 @@ public class RepositoryRoleResource {
@PUT
@Path("")
@Consumes(VndMediaType.REPOSITORY_ROLE)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of repository role name"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no repository role with the specified name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update repository role", description = "Modifies the repository role for the given name.", tags = "Repository role")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of repository role name")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repositoryRole\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository role with the specified name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response update(@PathParam("name") String name, @Valid RepositoryRoleDto repositoryRole) {
return adapter.update(name, existing -> dtoToRepositoryRoleMapper.map(repositoryRole));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java
index 44e3a10fba..8d47963778 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java
@@ -1,5 +1,8 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
@@ -7,6 +10,9 @@ import javax.ws.rs.Path;
/**
* RESTful web service resource to manage repository roles.
*/
+@OpenAPIDefinition(tags = {
+ @Tag(name = "Repository role", description = "Repository role related endpoints")
+})
@Path(RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2)
public class RepositoryRoleRootResource {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java
index a7a6365c37..7b8c1120e1 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java
@@ -1,12 +1,20 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
/**
- * RESTful Web Service Resource to manage repositories.
+ * RESTful Web Service Resource to manage repositories.
*/
+@OpenAPIDefinition(
+ tags = {
+ @Tag(name = "Repository", description = "Repository related endpoints")
+ }
+)
@Path(RepositoryRootResource.REPOSITORIES_PATH_V2)
public class RepositoryRootResource {
static final String REPOSITORIES_PATH_V2 = "v2/repositories/";
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java
index 4f9e76a939..8f33381497 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java
@@ -1,8 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import de.otto.edison.hal.HalRepresentation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
@@ -24,11 +26,25 @@ public class RepositoryTypeCollectionResource {
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION)
+ @Operation(summary = "List of repository types", description = "Returns all repository types.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_TYPE_COLLECTION,
+ schema = @Schema(implementation = HalRepresentation.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "\"sortBy\" field unknown")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public HalRepresentation getAll() {
return mapper.map(repositoryManager.getConfiguredTypes());
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java
index 3a47fdf6c6..b04ce46d98 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryType;
import sonia.scm.web.VndMediaType;
@@ -35,12 +36,30 @@ public class RepositoryTypeResource {
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_TYPE)
- @TypeHint(RepositoryTypeDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 404, condition = "not found, no repository type with the specified name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Get single repository type", description = "Returns the specified repository type for the given name.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_TYPE,
+ schema = @Schema(implementation = RepositoryTypeDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository type with the specified name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response get(@PathParam("name") String name) {
for (RepositoryType type : repositoryManager.getConfiguredTypes()) {
if (name.equalsIgnoreCase(type.getName())) {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryVerbResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryVerbResource.java
index 4d4de067e5..0fde9d2911 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryVerbResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryVerbResource.java
@@ -1,8 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import de.otto.edison.hal.Links;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.security.RepositoryPermissionProvider;
import sonia.scm.web.VndMediaType;
@@ -30,11 +32,23 @@ public class RepositoryVerbResource {
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.REPOSITORY_VERB_COLLECTION)
+ @Operation(summary = "List of repository verbs", description = "Returns all repository-specific permissions.", hidden = true)
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.REPOSITORY_VERB_COLLECTION,
+ schema = @Schema(implementation = RepositoryVerbsDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public RepositoryVerbsDto getAll() {
return new RepositoryVerbsDto(
Links.linkingTo().self(resourceLinks.repositoryVerbs().self()).build(),
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
index 758afd7660..dfd7dc8b5b 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java
@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.Operation;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.api.BrowseCommandBuilder;
@@ -32,22 +33,25 @@ public class SourceRootResource {
}
@GET
- @Produces(VndMediaType.SOURCE)
@Path("")
+ @Produces(VndMediaType.SOURCE)
+ @Operation(summary = "List of sources", description = "Returns all sources for repository head.", tags = "Repository")
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
return getSource(namespace, name, "/", null);
}
@GET
- @Produces(VndMediaType.SOURCE)
@Path("{revision}")
+ @Produces(VndMediaType.SOURCE)
+ @Operation(summary = "List of sources by revision", description = "Returns all sources for the given revision.", tags = "Repository")
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
return getSource(namespace, name, "/", revision);
}
@GET
- @Produces(VndMediaType.SOURCE)
@Path("{revision}/{path: .*}")
+ @Produces(VndMediaType.SOURCE)
+ @Operation(summary = "List of sources by revision in path", description = "Returns all sources for the given revision in a specific path.", tags = "Repository")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) throws IOException {
return getSource(namespace, name, path, revision);
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java
index 7acd59e3e1..9294128fee 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
@@ -39,17 +40,27 @@ public class TagRootResource {
this.tagToTagDtoMapper = tagToTagDtoMapper;
}
-
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the tags"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
@Produces(VndMediaType.TAG_COLLECTION)
- @TypeHint(CollectionDto.class)
+ @Operation(summary = "List of tags", description = "Returns the tags for the given namespace and name.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.TAG_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the tags")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Tags tags = getTags(repositoryService);
@@ -65,16 +76,33 @@ public class TagRootResource {
@GET
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the tags"),
- @ResponseCode(code = 404, condition = "not found, no tag available in the repository"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @Produces(VndMediaType.TAG)
- @TypeHint(TagDto.class)
@Path("{tagName}")
+ @Produces(VndMediaType.TAG)
+ @Operation(summary = "Get tag", description = "Returns the tag for the given name.", tags = "Repository")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.TAG,
+ schema = @Schema(implementation = TagDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the tags")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no tag with the specified name available in the repository",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("tagName") String tagName) throws IOException {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginResource.java
index 1c779653a0..4efb0c6446 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.security.AllowAnonymousAccess;
@@ -39,12 +40,23 @@ public class UIPluginResource {
*/
@GET
@Path("")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(CollectionDto.class)
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
+ @Operation(summary = "Collection of ui plugin bundles", description = "Returns a collection of installed plugins and their ui bundles.", hidden = true)
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.UI_PLUGIN_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getInstalledPlugins() {
List plugins = pluginLoader.getInstalledPlugins()
.stream()
@@ -63,13 +75,30 @@ public class UIPluginResource {
*/
@GET
@Path("{id}")
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 404, condition = "not found"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(UIPluginDto.class)
@Produces(VndMediaType.UI_PLUGIN)
+ @Operation(summary = "Get single ui plugin bundle", description = "Returns the installed plugin with the given id.", hidden = true)
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.UI_PLUGIN,
+ schema = @Schema(implementation = UIPluginDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getInstalledPlugin(@PathParam("id") String id) {
Optional uiPluginDto = pluginLoader.getInstalledPlugins()
.stream()
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
index 3e35b8d4a6..ef8b230f8a 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java
@@ -1,10 +1,10 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
-import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.search.SearchRequest;
import sonia.scm.search.SearchUtil;
@@ -59,22 +59,33 @@ public class UserCollectionResource {
@GET
@Path("")
@Produces(VndMediaType.USER_COLLECTION)
- @TypeHint(CollectionDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 400, condition = "\"sortBy\" field unknown"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "List of users", description = "Returns all users for a given page number with a given page size.", tags = "User")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.USER_COLLECTION,
+ schema = @Schema(implementation = CollectionDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "400", description = "\"sortBy\" field unknown")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
- @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
- @QueryParam("sortBy") String sortBy,
- @DefaultValue("false") @QueryParam("desc") boolean desc,
- @DefaultValue("") @QueryParam("q") String search
+ @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
+ @QueryParam("sortBy") String sortBy,
+ @DefaultValue("false") @QueryParam("desc") boolean desc,
+ @DefaultValue("") @QueryParam("q") String search
) {
return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc,
- pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
+ pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
@@ -88,15 +99,25 @@ public class UserCollectionResource {
@POST
@Path("")
@Consumes(VndMediaType.USER)
- @StatusCodes({
- @ResponseCode(code = 201, condition = "create success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
- @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
+ @Operation(summary = "Create user", description = "Creates a new user.", tags = "User")
+ @ApiResponse(
+ responseCode = "201",
+ description = "create success",
+ headers = @Header(
+ name = "Location",
+ description = "uri to the created user"
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
+ @ApiResponse(responseCode = "409", description = "conflict, a user with this name already exists")
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response create(@Valid UserDto user) {
return adapter.create(user, () -> dtoToUserMapper.map(user, passwordService.encryptPassword(user.getPassword())), u -> resourceLinks.user().self(u.getName()));
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserPermissionResource.java
index fd54da503d..5611b32aeb 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserPermissionResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserPermissionResource.java
@@ -1,8 +1,9 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.PermissionPermissions;
@@ -40,14 +41,32 @@ public class UserPermissionResource {
@GET
@Path("")
@Produces(VndMediaType.PERMISSION_COLLECTION)
- @TypeHint(PermissionListDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "User permission", description = "Returns the global git configuration.", tags = {"User", "Permissions"})
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.PERMISSION_COLLECTION,
+ schema = @Schema(implementation = PermissionListDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the user")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no user with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response getPermissions(@PathParam("id") String id) {
PermissionPermissions.read().check();
Collection permissions = permissionAssigner.readPermissionsForUser(id);
@@ -63,15 +82,26 @@ public class UserPermissionResource {
@PUT
@Path("")
@Consumes(VndMediaType.PERMISSION_COLLECTION)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the correct privilege"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update user permissions", description = "Sets permissions for a user. Overwrites all existing permissions.", tags = {"User", "Permissions"})
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the correct privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no user with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ )
+ )
public Response overwritePermissions(@PathParam("id") String id, @Valid PermissionListDto newPermissions) {
Collection permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
.map(PermissionDescriptor::new)
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
index e2a6cc797e..e4dfe6e633 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java
@@ -1,12 +1,12 @@
package sonia.scm.api.v2.resources;
-import com.webcohesion.enunciate.metadata.rs.ResponseCode;
-import com.webcohesion.enunciate.metadata.rs.StatusCodes;
-import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
-import sonia.scm.user.UserPermissions;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
@@ -54,14 +54,31 @@ public class UserResource {
@GET
@Path("")
@Produces(VndMediaType.USER)
- @TypeHint(UserDto.class)
- @StatusCodes({
- @ResponseCode(code = 200, condition = "success"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
+ @Operation(summary = "Get single user", description = "Returns the user for the given id.", tags = "User")
+ @ApiResponse(
+ responseCode = "200",
+ description = "success",
+ content = @Content(
+ mediaType = VndMediaType.USER,
+ schema = @Schema(implementation = UserDto.class)
+ )
+ )
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the user")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no user with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(
+ responseCode = "500",
+ description = "internal server error",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
public Response get(@PathParam("id") String id) {
return adapter.get(id, userToDtoMapper::map);
}
@@ -75,13 +92,11 @@ public class UserResource {
*/
@DELETE
@Path("")
- @StatusCodes({
- @ResponseCode(code = 204, condition = "delete success or nothing to delete"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Delete user", description = "Deletes the user with the given id.", tags = "User")
+ @ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response delete(@PathParam("id") String name) {
return adapter.delete(name);
}
@@ -98,15 +113,19 @@ public class UserResource {
@PUT
@Path("")
@Consumes(VndMediaType.USER)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of id/user name"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Update user", description = "Modifies the user for the given id.", tags = "User")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of id/user name")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no user with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response update(@PathParam("id") String name, @Valid UserDto user) {
return adapter.update(name, existing -> dtoToUserMapper.map(user, existing.getPassword()));
}
@@ -125,15 +144,19 @@ public class UserResource {
@PUT
@Path("password")
@Consumes(VndMediaType.PASSWORD_OVERWRITE)
- @StatusCodes({
- @ResponseCode(code = 204, condition = "update success"),
- @ResponseCode(code = 400, condition = "Invalid body, e.g. the user type is not xml or the given oldPassword do not match the stored one"),
- @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
- @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
- @ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
- @ResponseCode(code = 500, condition = "internal server error")
- })
- @TypeHint(TypeHint.NO_CONTENT.class)
+ @Operation(summary = "Modifies a user password", description = "Lets admins modifies the user password for the given id.", tags = "User")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. the user type is not xml or the given oldPassword do not match the stored one")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no user with the specified id/name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
public Response overwritePassword(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwrite) {
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
return Response.noContent().build();
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
index 9652a97404..a358597238 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserRootResource.java
@@ -1,5 +1,8 @@
package sonia.scm.api.v2.resources;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
@@ -7,6 +10,9 @@ import javax.ws.rs.Path;
/**
* RESTful Web Service Resource to manage users.
*/
+@OpenAPIDefinition(tags = {
+ @Tag(name = "User", description = "User related endpoints")
+})
@Path(UserRootResource.USERS_PATH_V2)
public class UserRootResource {