chore: update to spring boot 3 and switch to springdoc (#1060)

This commit is contained in:
MiniDigger | Martin 2022-12-28 01:44:06 +01:00 committed by GitHub
parent 5754c54139
commit ba27710bd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 741 additions and 802 deletions

129
.github/renovate.json5 vendored
View File

@ -1,62 +1,71 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"schedule": [
"before 3am on Monday"
],
"ignoreDeps": [
"io.awspring.cloud:spring-cloud-aws-starter-s3", // m3 is spring boot 3 only
],
"packageRules": [
{
"matchManagers": ["docker-compose", "dockerfile", "helmv3", "helm-values"],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "infra non-major dependencies",
"groupSlug": "infra-minor-patch"
},
{
"matchManagers": ["github-actions"],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "gh-actions non-major dependencies",
"groupSlug": "actions-minor-patch"
},
{
"matchManagers": ["maven", "gradle"],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "backend non-major dependencies",
"groupSlug": "backend-minor-patch"
},
{
"matchManagers": ["npm"],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "frontend non-major dependencies",
"groupSlug": "frontend-minor-patch"
}
]
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"schedule": [
"before 3am on Monday"
],
"packageRules": [
{
"matchManagers": [
"docker-compose",
"dockerfile",
"helmv3",
"helm-values"
],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "infra non-major dependencies",
"groupSlug": "infra-minor-patch"
},
{
"matchManagers": [
"github-actions"
],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "gh-actions non-major dependencies",
"groupSlug": "actions-minor-patch"
},
{
"matchManagers": [
"maven",
"gradle"
],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "backend non-major dependencies",
"groupSlug": "backend-minor-patch"
},
{
"matchManagers": [
"npm"
],
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "frontend non-major dependencies",
"groupSlug": "frontend-minor-patch"
}
]
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<version>3.0.1</version>
<relativePath/>
</parent>
@ -28,11 +28,11 @@
<!-- dependency management -->
<jdbi3-bom.version>3.35.0</jdbi3-bom.version>
<configurate.version>4.1.2</configurate.version>
<spring-cloud-aws.version>3.0.0-M2</spring-cloud-aws.version>
<spring-cloud-aws.version>3.0.0-M3</spring-cloud-aws.version>
<!-- dependencies -->
<owasp-html-sanitizer>20220608.1</owasp-html-sanitizer>
<springfox-boot-starter.version>3.0.0</springfox-boot-starter.version>
<springdoc.version>2.0.2</springdoc.version>
<flexmark-all.version>0.64.0</flexmark-all.version>
<jsitemapgenerator.version>4.5</jsitemapgenerator.version>
<org-json.version>20220924</org-json.version>
@ -48,6 +48,18 @@
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
</properties>
<!-- spring-cloud-aws requires this rn -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
@ -68,6 +80,11 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
@ -135,24 +152,17 @@
<artifactId>configurate-yaml</artifactId>
</dependency>
<!--SpringFox dependencies -->
<!-- SpringDoc dependencies -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox-boot-starter.version}</version>
<exclusions>
<exclusion>
<!-- old version -->
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
</exclusion>
</exclusions>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- Bean Validation API support -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<!-- config stuff -->
@ -313,8 +323,8 @@
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>

View File

@ -6,9 +6,9 @@ import io.papermc.hangar.security.authentication.HangarAuthenticationToken;
import io.papermc.hangar.security.authentication.HangarPrincipal;
import io.papermc.hangar.service.PermissionService;
import io.papermc.hangar.service.internal.UserActionLogService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jdbi.v3.core.internal.MemoizingSupplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -1,88 +1,64 @@
package io.papermc.hangar.config;
import io.papermc.hangar.controller.extras.pagination.FilterRegistry;
import io.papermc.hangar.controller.extras.pagination.SorterRegistry;
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableFilters;
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableSorters;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.InitializingBean;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ParameterStyle;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.RequestHandlerProvider;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.spring.web.readers.operation.HandlerMethodResolver;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static java.util.stream.Collectors.toList;
import static springfox.documentation.RequestHandler.byPatternsCondition;
import static springfox.documentation.spring.web.paths.Paths.ROOT;
import org.springframework.web.method.HandlerMethod;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Hangar API")
.description("This page describes the format for the current Hangar REST API, in addition to common questions when using it.<br>" +
"Note that all routes **not** listed here should be considered **internal**, and can change at a moment's notice. **Do not use them**." +
"<h2>Authentication and Authorization</h2>" +
"There are two ways to consume the API: Authenticated or Anonymous." +
"<h3>Anonymous</h3>" +
"When using anonymous authentication you only get access to public information, but don't need to worry about creating and storing an API key or handing JWTs." +
"<h3>Authenticated</h3>" +
"If you need access to non-public actions, or want to do something programmatically, you likely want an API key.<br>" +
"These can be created by going to your user page and clicking on the key icon.<br>" +
"API keys allow you to impersonate yourself, so they should be handled like passwords, **never** share them!" +
"<h4>Authentication</h4>" +
"Once you have an api key, you need to authenticate yourself. For that you `POST` your API Key to `/api/v1/authenticate?apiKey=yourKey`. The response will contain your JWT.<br>" +
"You want to store that JWT and send it with every request. It's valid for a certain amount of time, the token itself contains a field with a timestamp when it will expire. If it expired, you want to reauthenticate yourself." +
"<h4>Authorization</h4>" +
"Now that you have your JWT, you want to set it in the Authorization header for every request like so `Authorization: HangarAuth your.jwt`. The request will be then executed with the permission scope of your api key." +
"<br>" +
"While talking about headers. Please also set a useful User-Agent header. This allows us to better identify loads and need for potentially new endpoints." +
"<h2>FAQ</h2>" +
"<h3>What format do dates have?</h3>" +
"Standard ISO types. Where possible, we use the OpenAPI format modifier. You can view its meanings [here](https://swagger.io/docs/specification/data-models/data-types/#format)." +
"<h3>Are there rate-limits? What about caching?</h3>" +
"There are currently no rate limits. Please don't abuse that fact. If applicable, always cache the responses. The Hangar API itself is cached by CloudFlare and internally.")
.version("1.0")
.build();
static {
io.swagger.v3.core.jackson.ModelResolver.enumsAsRef = true;
}
OpenAPI apiInfo(final OpenAPI openAPI) {
return openAPI
.info(new Info().title("Hangar API")
.description("This page describes the format for the current Hangar REST API, in addition to common questions when using it.<br>" +
"Note that all routes **not** listed here should be considered **internal**, and can change at a moment's notice. **Do not use them**." +
"<h2>Authentication and Authorization</h2>" +
"There are two ways to consume the API: Authenticated or Anonymous." +
"<h3>Anonymous</h3>" +
"When using anonymous authentication you only get access to public information, but don't need to worry about creating and storing an API key or handing JWTs." +
"<h3>Authenticated</h3>" +
"If you need access to non-public actions, or want to do something programmatically, you likely want an API key.<br>" +
"These can be created by going to your user page and clicking on the key icon.<br>" +
"API keys allow you to impersonate yourself, so they should be handled like passwords, **never** share them!" +
"<h4>Authentication</h4>" +
"Once you have an api key, you need to authenticate yourself. For that you `POST` your API Key to `/api/v1/authenticate?apiKey=yourKey`. The response will contain your JWT.<br>" +
"You want to store that JWT and send it with every request. It's valid for a certain amount of time, the token itself contains a field with a timestamp when it will expire. If it expired, you want to reauthenticate yourself." +
"<h4>Authorization</h4>" +
"Now that you have your JWT, you want to set it in the Authorization header for every request like so `Authorization: HangarAuth your.jwt`. The request will be then executed with the permission scope of your api key." +
"<br>" +
"While talking about headers. Please also set a useful User-Agent header. This allows us to better identify loads and need for potentially new endpoints." +
"<h2>FAQ</h2>" +
"<h3>What format do dates have?</h3>" +
"Standard ISO types. Where possible, we use the OpenAPI format modifier. You can view its meanings [here](https://swagger.io/docs/specification/data-models/data-types/#format)." +
"<h3>Are there rate-limits? What about caching?</h3>" +
"There are currently no rate limits. Please don't abuse that fact. If applicable, always cache the responses. The Hangar API itself is cached by CloudFlare and internally.")
.version("1.0"));
}
@Bean
public Docket customImplementation() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("io.papermc.hangar.controller.api.v1"))
.build()
.directModelSubstitute(LocalDate.class, java.sql.Date.class)
.directModelSubstitute(OffsetDateTime.class, Date.class)
.apiInfo(this.apiInfo());
public GroupedOpenApi customImplementation(final CustomScanner customScanner) {
return GroupedOpenApi.builder()
.group("Hangar API")
.packagesToScan("io.papermc.hangar.controller.api.v1")
.addOperationCustomizer(customScanner)
.addOpenApiCustomizer(this::apiInfo)
.build();
// TODO fix
//.directModelSubstitute(LocalDate.class, java.sql.Date.class)
//.directModelSubstitute(OffsetDateTime.class, Date.class)
}
@Bean
@ -90,7 +66,7 @@ public class SwaggerConfig {
return new CustomScanner(filterRegistry);
}
static class CustomScanner implements OperationBuilderPlugin {
static class CustomScanner implements OperationCustomizer {
private final FilterRegistry filterRegistry;
@ -98,62 +74,33 @@ public class SwaggerConfig {
this.filterRegistry = filterRegistry;
}
@Override
public void apply(final OperationContext context) {
final Optional<ApplicableSorters> sorters = context.findAnnotation(ApplicableSorters.class);
if (sorters.isPresent()) {
final Set<RequestParameter> requestParameters = context.operationBuilder().build().getRequestParameters();
requestParameters.add(new RequestParameterBuilder()
.name("sort")
.in(ParameterType.QUERY)
.description("Used to sort the result")
.query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING)).enumerationFacet(e -> e.allowedValues(Arrays.asList(sorters.get().value()).stream().map(SorterRegistry::getName).collect(Collectors.toSet())))).build());
context.operationBuilder().requestParameters(requestParameters);
}
final Optional<ApplicableFilters> filters = context.findAnnotation(ApplicableFilters.class);
if (filters.isPresent()) {
final Set<RequestParameter> requestParameters = context.operationBuilder().build().getRequestParameters();
for (final var clazz : filters.get().value()) {
@Override
public Operation customize(final Operation operation, final HandlerMethod handlerMethod) {
final ApplicableSorters sorters = handlerMethod.getMethodAnnotation(ApplicableSorters.class);
if (sorters != null) {
operation.addParametersItem(new Parameter()
.name("sort")
.in("query")
.description("Used to sort the result"));
// TODO fix
//.query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING)).enumerationFacet(e -> e.allowedValues(Arrays.asList(sorters.value()).stream().map(SorterRegistry::getName).collect(Collectors.toSet())))).build());
}
final ApplicableFilters filters = handlerMethod.getMethodAnnotation(ApplicableFilters.class);
if (filters != null) {
for (final var clazz : filters.value()) {
final var filter = this.filterRegistry.get(clazz);
requestParameters.add(new RequestParameterBuilder()
operation.addParametersItem(new Parameter()
.name(filter.getSingleQueryParam()) // TODO multi-param filters
.in(ParameterType.QUERY)
.description(filter.getDescription())
.query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING))).build());
.in("query")
.description(filter.getDescription()));
// TODO fix
// .query(q -> q.style(ParameterStyle.SIMPLE).model(m -> m.scalarModel(ScalarType.STRING))).build());
}
context.operationBuilder().requestParameters(requestParameters);
}
return operation;
}
@Override
public boolean supports(final DocumentationType documentationType) {
return true;
}
}
// Hack to make this shit work on spring boot 2.6 // TODO migrate to springdoc
@Bean
public InitializingBean removeSpringfoxHandlerProvider(final DocumentationPluginsBootstrapper bootstrapper) {
return () -> bootstrapper.getHandlerProviders().removeIf(WebMvcRequestHandlerProvider.class::isInstance);
}
@Bean
public RequestHandlerProvider customRequestHandlerProvider(final Optional<ServletContext> servletContext, final HandlerMethodResolver methodResolver, final List<RequestMappingInfoHandlerMapping> handlerMappings) {
final String contextPath = servletContext.map(ServletContext::getContextPath).orElse(ROOT);
return () -> handlerMappings.stream()
.filter(mapping -> !mapping.getClass().getSimpleName().equals("IntegrationRequestMappingHandlerMapping"))
.map(mapping -> mapping.getHandlerMethods().entrySet())
.flatMap(Set::stream)
.map(entry -> new WebMvcRequestHandler(contextPath, methodResolver, this.tweakInfo(entry.getKey()), entry.getValue()))
.sorted(byPatternsCondition())
.collect(toList());
}
RequestMappingInfo tweakInfo(final RequestMappingInfo info) {
if (info.getPathPatternsCondition() == null) return info;
final String[] patterns = info.getPathPatternsCondition().getPatternValues().toArray(String[]::new);
return info.mutate().options(new RequestMappingInfo.BuilderConfiguration()).paths(patterns).build();
}
}

View File

@ -6,6 +6,11 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesAnnotationIntrospec
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.config.jackson.HangarAnnotationIntrospector;
import io.papermc.hangar.security.annotations.ratelimit.RateLimitInterceptor;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -14,11 +19,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -169,7 +169,7 @@ public class WebConfig extends WebMvcConfigurationSupport {
}
final ClientHttpResponse response = ex.execute(req, reqBody);
if (interceptorLogger.isDebugEnabled()) {
final int code = response.getRawStatusCode();
final int code = response.getStatusCode().value();
final HttpStatus status = HttpStatus.resolve(code);
final InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);

View File

@ -3,8 +3,8 @@ package io.papermc.hangar.config.hangar;
import io.papermc.hangar.model.common.Color;
import io.papermc.hangar.model.internal.api.responses.Validation;
import io.papermc.hangar.util.PatternWrapper;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

View File

@ -15,24 +15,17 @@ import io.papermc.hangar.service.AuthenticationService;
import io.papermc.hangar.service.TokenService;
import io.papermc.hangar.service.ValidationService;
import io.papermc.hangar.service.internal.auth.SSOService;
import java.util.Optional;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpSession;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Optional;
@Controller
@RateLimit(path = "login")

View File

@ -2,14 +2,16 @@ package io.papermc.hangar.controller.api.v1.interfaces;
import io.papermc.hangar.model.api.ApiKey;
import io.papermc.hangar.model.internal.api.requests.CreateAPIKeyForm;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
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.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import javax.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@ -18,52 +20,50 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "API Keys")
@Tag(name = "API Keys")
@RequestMapping("/api/v1")
public interface IApiKeysController {
@ApiOperation(
value = "Creates an API key",
nickname = "createKey",
notes = "Creates an API key. Requires the `edit_api_keys` permission.",
response = String.class,
authorizations = @Authorization("Session"),
@Operation(
summary = "Creates an API key",
operationId = "createKey",
description = "Creates an API key. Requires the `edit_api_keys` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "API Keys"
)
@ApiResponses({
@ApiResponse(code = 201, message = "Key created", response = String.class),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")})
@ApiResponse(responseCode = "201", description = "Key created", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")})
@PostMapping(path = "/keys", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
String createKey(@ApiParam(value = "Data about the key to create", required = true) @Valid @RequestBody CreateAPIKeyForm apiKeyForm);
String createKey(@Parameter(description = "Data about the key to create", required = true) @Valid @RequestBody CreateAPIKeyForm apiKeyForm);
@ApiOperation(
value = "Fetches a list of API Keys",
nickname = "getKeys",
notes = "Fetches a list of API Keys. Requires the `edit_api_keys` permission.",
response = String.class,
authorizations = @Authorization("Session"),
@Operation(
summary = "Fetches a list of API Keys",
operationId = "getKeys",
description = "Fetches a list of API Keys. Requires the `edit_api_keys` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "API Keys"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Key created", response = ApiKey.class, responseContainer = "List"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")})
@ApiResponse(responseCode = "200", description = "Key created", content = @Content(schema = @Schema(implementation = ApiKey.class))),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")})
@GetMapping(path = "/keys", produces = MediaType.APPLICATION_JSON_VALUE)
List<ApiKey> getKeys();
@ApiOperation(
value = "Deletes an API key",
nickname = "deleteKey",
notes = "Deletes an API key. Requires the `edit_api_keys` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Deletes an API key",
operationId = "deleteKey",
description = "Deletes an API key. Requires the `edit_api_keys` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "API Keys"
)
@ApiResponses({
@ApiResponse(code = 204, message = "Key deleted"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "204", description = "Key deleted"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@DeleteMapping("/keys")
void deleteKey(@ApiParam(value = "The name of the key to delete", required = true) @RequestParam String name);
void deleteKey(@Parameter(description = "The name of the key to delete", required = true) @RequestParam String name);
}

View File

@ -1,34 +1,34 @@
package io.papermc.hangar.controller.api.v1.interfaces;
import io.papermc.hangar.model.api.auth.ApiSession;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "Authentication", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Authentication")
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public interface IAuthenticationController {
@ApiOperation(
value = "Creates an API JWT",
nickname = "authenticate",
notes = "`Log-in` with your API key in order to be able to call other endpoints authenticated. The returned JWT should be specified as a header in all following requests: `Authorization: HangarAuth your.jwt`",
authorizations = @Authorization("Key"),
@Operation(
summary = "Creates an API JWT",
operationId = "authenticate",
description = "`Log-in` with your API key in order to be able to call other endpoints authenticated. The returned JWT should be specified as a header in all following requests: `Authorization: HangarAuth your.jwt`",
security = @SecurityRequirement(name = "Key"),
tags = "Authentication"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api key missing or invalid")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "401", description = "Api key missing or invalid")
})
@PostMapping("/authenticate")
ResponseEntity<ApiSession> authenticate(@ApiParam("JWT") @RequestParam String apiKey);
ResponseEntity<ApiSession> authenticate(@Parameter(description = "JWT") @RequestParam String apiKey);
}

View File

@ -3,12 +3,12 @@ package io.papermc.hangar.controller.api.v1.interfaces;
import io.papermc.hangar.model.api.permissions.PermissionCheck;
import io.papermc.hangar.model.api.permissions.UserPermissions;
import io.papermc.hangar.model.common.NamedPermission;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -17,67 +17,65 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "Permissions", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Permissions")
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public interface IPermissionsController {
@ApiOperation(
value = "Checks whether you have all the provided permissions",
nickname = "hasAll",
notes = "Checks whether you have all the provided permissions in the given context",
response = PermissionCheck.class,
authorizations = @Authorization("Session"),
@Operation(
summary = "Checks whether you have all the provided permissions",
operationId = "hasAll",
description = "Checks whether you have all the provided permissions in the given context",
security = @SecurityRequirement(name = "Session"),
tags = "Permissions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok", response = PermissionCheck.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired")
})
@GetMapping("/permissions/hasAll")
ResponseEntity<PermissionCheck> hasAllPermissions(@ApiParam(value = "The permissions to check", required = true) @RequestParam List<NamedPermission> permissions,
@ApiParam("The owner of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String author,
@ApiParam("The project slug of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@ApiParam("The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
ResponseEntity<PermissionCheck> hasAllPermissions(@Parameter(description = "The permissions to check", required = true) @RequestParam List<NamedPermission> permissions,
@Parameter(description = "The owner of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String author,
@Parameter(description = "The project slug of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@Parameter(description = "The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
);
@ApiOperation(
value = "Checks whether you have at least one of the provided permissions",
nickname = "hasAny",
notes = "Checks whether you have at least one of the provided permissions in the given context",
authorizations = @Authorization("Session"),
@Operation(
summary = "Checks whether you have at least one of the provided permissions",
operationId = "hasAny",
description = "Checks whether you have at least one of the provided permissions in the given context",
security = @SecurityRequirement(name = "Session"),
tags = "Permissions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired")
})
@GetMapping("/permissions/hasAny")
ResponseEntity<PermissionCheck> hasAny(@ApiParam(value = "The permissions to check", required = true) @RequestParam List<NamedPermission> permissions,
@ApiParam("The owner of the project to check permissions in. Must not be used together with `organizationName") @RequestParam(required = false) String author,
@ApiParam("The slug of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@ApiParam("The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
ResponseEntity<PermissionCheck> hasAny(@Parameter(description = "The permissions to check", required = true) @RequestParam List<NamedPermission> permissions,
@Parameter(description = "The owner of the project to check permissions in. Must not be used together with `organizationName") @RequestParam(required = false) String author,
@Parameter(description = "The slug of the project to check permissions in. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@Parameter(description = "The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
);
@ApiOperation(
value = "Returns your permissions",
nickname = "showPermissions",
notes = "Returns a list of permissions you have in the given context",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns your permissions",
operationId = "showPermissions",
description = "Returns a list of permissions you have in the given context",
security = @SecurityRequirement(name = "Session"),
tags = "Permissions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired")
})
@GetMapping(value = "/permissions",
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<UserPermissions> showPermissions(
@ApiParam("The owner of the project to get the permissions for. Must not be used together with `organizationName`") @RequestParam(required = false) String author,
@ApiParam("The slug of the project get the permissions for. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@ApiParam("The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
@Parameter(description = "The owner of the project to get the permissions for. Must not be used together with `organizationName`") @RequestParam(required = false) String author,
@Parameter(description = "The slug of the project get the permissions for. Must not be used together with `organizationName`") @RequestParam(required = false) String slug,
@Parameter(description = "The organization to check permissions in. Must not be used together with `projectOwner` and `projectSlug`") @RequestParam(required = false) String organization
);
}

View File

@ -6,12 +6,12 @@ import io.papermc.hangar.model.api.project.DayProjectStats;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.api.project.ProjectMember;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.OffsetDateTime;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
@ -23,118 +23,118 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "Projects", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Projects")
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public interface IProjectsController {
@ApiOperation(
value = "Returns info on a specific project",
nickname = "getProject",
notes = "Returns info on a specific project. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns info on a specific project",
operationId = "getProject",
description = "Returns info on a specific project. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}")
ResponseEntity<Project> getProject(@ApiParam("The author of the project to return") @PathVariable String author,
@ApiParam("The slug of the project to return") @PathVariable String slug);
ResponseEntity<Project> getProject(@Parameter(description = "The author of the project to return") @PathVariable String author,
@Parameter(description = "The slug of the project to return") @PathVariable String slug);
@ApiOperation(
value = "Returns the members of a project",
nickname = "getProjectMembers",
notes = "Returns the members of a project. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the members of a project",
operationId = "getProjectMembers",
description = "Returns the members of a project. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/members")
ResponseEntity<PaginatedResult<ProjectMember>> getProjectMembers(
@ApiParam("The author of the project to return members for") @PathVariable("author") String author,
@ApiParam("The slug of the project to return members for") @PathVariable("slug") String slug,
@ApiParam("Pagination information") @NotNull RequestPagination pagination
@Parameter(description = "The author of the project to return members for") @PathVariable("author") String author,
@Parameter(description = "The slug of the project to return members for") @PathVariable("slug") String slug,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination
);
@ApiOperation(
value = "Searches the projects on Hangar",
nickname = "getProjects",
notes = "Searches all the projects on Hangar, or for a single user. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Searches the projects on Hangar",
operationId = "getProjects",
description = "Searches all the projects on Hangar, or for a single user. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects")
ResponseEntity<PaginatedResult<Project>> getProjects(
@ApiParam("The query to use when searching") @RequestParam(required = false) String q,
@ApiParam("Whether projects should be sorted by the relevance to the given query") @RequestParam(defaultValue = "true") boolean relevance,
@ApiParam("Pagination information") @NotNull RequestPagination pagination
@Parameter(description = "The query to use when searching") @RequestParam(required = false) String q,
@Parameter(description = "Whether projects should be sorted by the relevance to the given query") @RequestParam(defaultValue = "true") boolean relevance,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination
);
@ApiOperation(
value = "Returns the stats for a project",
nickname = "showProjectStats",
notes = "Returns the stats (downloads and views) for a project per day for a certain date range. Requires the `is_subject_member` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the stats for a project",
operationId = "showProjectStats",
description = "Returns the stats (downloads and views) for a project per day for a certain date range. Requires the `is_subject_member` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/stats")
ResponseEntity<Map<String, DayProjectStats>> getProjectStats(@ApiParam("The author of the project to return stats for") @PathVariable String author,
@ApiParam("The slug of the project to return stats for") @PathVariable String slug,
@NotNull @ApiParam(value = "The first date to include in the result", required = true) @RequestParam OffsetDateTime fromDate,
@NotNull @ApiParam(value = "The last date to include in the result", required = true) @RequestParam OffsetDateTime toDate
ResponseEntity<Map<String, DayProjectStats>> getProjectStats(@Parameter(description = "The author of the project to return stats for") @PathVariable String author,
@Parameter(description = "The slug of the project to return stats for") @PathVariable String slug,
@NotNull @Parameter(description = "The first date to include in the result", required = true) @RequestParam OffsetDateTime fromDate,
@NotNull @Parameter(description = "The last date to include in the result", required = true) @RequestParam OffsetDateTime toDate
);
@ApiOperation(
value = "Returns the stargazers of a project",
nickname = "getProjectStargazers",
notes = "Returns the stargazers of a project. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the stargazers of a project",
operationId = "getProjectStargazers",
description = "Returns the stargazers of a project. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/stargazers")
ResponseEntity<PaginatedResult<User>> getProjectStargazers(
@ApiParam("The author of the project to return stargazers for") @PathVariable("author") String author,
@ApiParam("The slug of the project to return stargazers for") @PathVariable("slug") String slug,
@ApiParam("Pagination information") @NotNull RequestPagination pagination
@Parameter(description = "The author of the project to return stargazers for") @PathVariable("author") String author,
@Parameter(description = "The slug of the project to return stargazers for") @PathVariable("slug") String slug,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination
);
@ApiOperation(
value = "Returns the watchers of a project",
nickname = "getProjectWatchers",
notes = "Returns the watchers of a project. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the watchers of a project",
operationId = "getProjectWatchers",
description = "Returns the watchers of a project. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/watchers")
ResponseEntity<PaginatedResult<User>> getProjectWatchers(
@ApiParam("The author of the project to return watchers for") @PathVariable("author") String author,
@ApiParam("The slug of the project to return watchers for") @PathVariable("slug") String slug,
@ApiParam("Pagination information") @NotNull RequestPagination pagination
@Parameter(description = "The author of the project to return watchers for") @PathVariable("author") String author,
@Parameter(description = "The slug of the project to return watchers for") @PathVariable("slug") String slug,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination
);
}

View File

@ -5,12 +5,12 @@ import io.papermc.hangar.model.api.User;
import io.papermc.hangar.model.api.project.ProjectCompact;
import io.papermc.hangar.model.api.project.ProjectSortingStrategy;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
@ -21,117 +21,117 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "Users", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Users")
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public interface IUsersController {
@ApiOperation(
value = "Returns a specific user",
nickname = "getUser",
notes = "Returns a specific user. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns a specific user",
operationId = "getUser",
description = "Returns a specific user. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/users/{user}")
ResponseEntity<User> getUser(@ApiParam("The name of the user to return") @PathVariable("user") String userName);
ResponseEntity<User> getUser(@Parameter(description = "The name of the user to return") @PathVariable("user") String userName);
@ApiOperation(
value = "Searches for users",
nickname = "showUsers",
notes = "Returns a list of users based on a search query",
authorizations = @Authorization("Session"),
@Operation(
summary = "Searches for users",
operationId = "showUsers",
description = "Returns a list of users based on a search query",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/users")
ResponseEntity<PaginatedResult<User>> getUsers(@ApiParam(value = "The search query", required = true) @RequestParam(value = "query", required = false) String query,
@ApiParam("Pagination information") @NotNull RequestPagination pagination);
ResponseEntity<PaginatedResult<User>> getUsers(@Parameter(description = "The search query", required = true) @RequestParam(defaultValue = "query", required = false) String query,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
@ApiOperation(
value = "Returns the starred projects for a specific user",
nickname = "showStarred",
notes = "Returns the starred projects for a specific user. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the starred projects for a specific user",
operationId = "showStarred",
description = "Returns the starred projects for a specific user. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/users/{user}/starred")
ResponseEntity<PaginatedResult<ProjectCompact>> getUserStarred(@ApiParam("The user to return starred projects for") @PathVariable("user") String userName,
@ApiParam("How to sort the projects") @RequestParam(defaultValue = "updated") ProjectSortingStrategy sort,
@ApiParam("Pagination information") @NotNull RequestPagination pagination);
ResponseEntity<PaginatedResult<ProjectCompact>> getUserStarred(@Parameter(description = "The user to return starred projects for") @PathVariable("user") String userName,
@Parameter(description = "How to sort the projects") @RequestParam(defaultValue = "updated") ProjectSortingStrategy sort,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
@ApiOperation(
value = "Returns the watched projects for a specific user",
nickname = "getUserWatching",
notes = "Returns the watched projects for a specific user. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the watched projects for a specific user",
operationId = "getUserWatching",
description = "Returns the watched projects for a specific user. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/users/{user}/watching")
ResponseEntity<PaginatedResult<ProjectCompact>> getUserWatching(@ApiParam("The user to return watched projects for") @PathVariable("user") String userName,
@ApiParam("How to sort the projects") @RequestParam(defaultValue = "updated") ProjectSortingStrategy sort,
@ApiParam("Pagination information") @NotNull RequestPagination pagination);
ResponseEntity<PaginatedResult<ProjectCompact>> getUserWatching(@Parameter(description = "The user to return watched projects for") @PathVariable("user") String userName,
@Parameter(description = "How to sort the projects") @RequestParam(defaultValue = "updated") ProjectSortingStrategy sort,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
@ApiOperation(
value = "Returns the pinned projects for a specific user",
nickname = "getUserPinnedProjects",
notes = "Returns the pinned projects for a specific user. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the pinned projects for a specific user",
operationId = "getUserPinnedProjects",
description = "Returns the pinned projects for a specific user. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/users/{user}/pinned")
ResponseEntity<List<ProjectCompact>> getUserPinnedProjects(@ApiParam("The user to return pinned projects for") @PathVariable("user") String userName);
ResponseEntity<List<ProjectCompact>> getUserPinnedProjects(@Parameter(description = "The user to return pinned projects for") @PathVariable("user") String userName);
@ApiOperation(
value = "Returns all users with at least one public project",
nickname = "getAuthors",
notes = "Returns all users that have at least one public project. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns all users with at least one public project",
operationId = "getAuthors",
description = "Returns all users that have at least one public project. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/authors")
ResponseEntity<PaginatedResult<User>> getAuthors(@ApiParam("Pagination information") @NotNull RequestPagination pagination);
ResponseEntity<PaginatedResult<User>> getAuthors(@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
@ApiOperation(
value = "Returns Hangar staff",
nickname = "getStaff",
notes = "Returns Hanagr staff. Requires the `view_public_info` permission.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns Hangar staff",
operationId = "getStaff",
description = "Returns Hanagr staff. Requires the `view_public_info` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Users"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/staff")
ResponseEntity<PaginatedResult<User>> getStaff(@ApiParam("Pagination information") @NotNull RequestPagination pagination);
ResponseEntity<PaginatedResult<User>> getStaff(@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
}

View File

@ -5,12 +5,12 @@ import io.papermc.hangar.model.api.project.version.Version;
import io.papermc.hangar.model.api.project.version.VersionStats;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.model.common.Platform;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.OffsetDateTime;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
@ -21,98 +21,97 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Api(tags = "Versions", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Versions")
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public interface IVersionsController {
// TODO implement version creation via API
/*@ApiOperation(
value = "Creates a new version",
nickname = "deployVersion",
/*@Operation(
summary ="Creates a new version",
operationId = "deployVersion",
notes = "Creates a new version for a project. Requires the `create_version` permission in the project or owning organization.",
authorizations = @Authorization("Session"),
security = @SecurityRequirement(name = "Session"),
tags = "Versions"
)
@ApiResponses({
@ApiResponse(code = 201, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = 201, description = "Ok"),
@ApiResponse(responseCode = 401, description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = 403, description = "Not enough permissions to use this endpoint")
})
@PostMapping(path = "/projects/{author}/{slug}/versions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Version> deployVersion()*/
@ApiOperation(
value = "Returns a specific version of a project",
nickname = "showVersion",
notes = "Returns a specific version of a project. Requires the `view_public_info` permission in the project or owning organization.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns a specific version of a project",
operationId = "showVersion",
description = "Returns a specific version of a project. Requires the `view_public_info` permission in the project or owning organization.",
security = @SecurityRequirement(name = "Session"),
tags = "Version"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/versions/{name}")
Version getVersion(@ApiParam("The author of the project to return the version for") @PathVariable String author,
@ApiParam("The slug of the project to return the version for") @PathVariable String slug,
@ApiParam("The name of the version to return") @PathVariable("name") String versionString);
Version getVersion(@Parameter(description = "The author of the project to return the version for") @PathVariable String author,
@Parameter(description = "The slug of the project to return the version for") @PathVariable String slug,
@Parameter(description = "The name of the version to return") @PathVariable("name") String versionString);
@ApiOperation(
value = "Returns all versions of a project",
nickname = "listVersions",
notes = "Returns all versions of a project. Requires the `view_public_info` permission in the project or owning organization.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns all versions of a project",
operationId = "listVersions",
description = "Returns all versions of a project. Requires the `view_public_info` permission in the project or owning organization.",
security = @SecurityRequirement(name = "Session"),
tags = "Versions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/versions")
PaginatedResult<Version> getVersions(@ApiParam("The author of the project to return versions for") @PathVariable String author,
@ApiParam("The slug of the project to return versions for") @PathVariable String slug,
@ApiParam("Pagination information") @NotNull RequestPagination pagination);
PaginatedResult<Version> getVersions(@Parameter(description = "The author of the project to return versions for") @PathVariable String author,
@Parameter(description = "The slug of the project to return versions for") @PathVariable String slug,
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination);
@ApiOperation(
value = "Returns the stats for a version",
nickname = "showVersionStats",
notes = "Returns the stats (downloads) for a version per day for a certain date range. Requires the `is_subject_member` permission.",
responseContainer = "Map",
authorizations = @Authorization("Session"),
@Operation(
summary = "Returns the stats for a version",
operationId = "showVersionStats",
description = "Returns the stats (downloads) for a version per day for a certain date range. Requires the `is_subject_member` permission.",
security = @SecurityRequirement(name = "Session"),
tags = "Versions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/versions/{name}/{platform}/stats")
Map<String, VersionStats> getVersionStats(@ApiParam("The author of the version to return the stats for") @PathVariable String author,
@ApiParam("The slug of the project to return stats for") @PathVariable String slug,
@ApiParam("The version to return the stats for") @PathVariable("name") String versionString,
@ApiParam("The platform of the version to return stats for") @PathVariable Platform platform,
@ApiParam(value = "The first date to include in the result", required = true) @RequestParam @NotNull OffsetDateTime fromDate,
@ApiParam(value = "The last date to include in the result", required = true) @RequestParam @NotNull OffsetDateTime toDate);
Map<String, VersionStats> getVersionStats(@Parameter(description = "The author of the version to return the stats for") @PathVariable String author,
@Parameter(description = "The slug of the project to return stats for") @PathVariable String slug,
@Parameter(description = "The version to return the stats for") @PathVariable("name") String versionString,
@Parameter(description = "The platform of the version to return stats for") @PathVariable Platform platform,
@Parameter(description = "The first date to include in the result", required = true) @RequestParam @NotNull OffsetDateTime fromDate,
@Parameter(description = "The last date to include in the result", required = true) @RequestParam @NotNull OffsetDateTime toDate);
@ApiOperation(
value = "Downloads a version",
nickname = "downloadVersion",
notes = "Downloads the file for a specific platform of a version. Requires visibility of the project and version.",
authorizations = @Authorization("Session"),
@Operation(
summary = "Downloads a version",
operationId = "downloadVersion",
description = "Downloads the file for a specific platform of a version. Requires visibility of the project and version.",
security = @SecurityRequirement(name = "Session"),
tags = "Versions"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 303, message = "Version has an external download url"),
@ApiResponse(code = 400, message = "Version doesn't have a file attached to it"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
@ApiResponse(responseCode = "200", description = "Ok"),
@ApiResponse(responseCode = "303", description = "Version has an external download url"),
@ApiResponse(responseCode = "400", description = "Version doesn't have a file attached to it"),
@ApiResponse(responseCode = "401", description = "Api session missing, invalid or expired"),
@ApiResponse(responseCode = "403", description = "Not enough permissions to use this endpoint")
})
@GetMapping(value = "/projects/{author}/{slug}/versions/{name}/{platform}/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
Resource downloadVersion(@ApiParam("The author of the project to download the version from") @PathVariable String author,
@ApiParam("The slug of the project to download the version from") @PathVariable String slug,
@ApiParam("The name of the version to download") @PathVariable("name") String versionString,
@ApiParam("The platform of the version to download") @PathVariable Platform platform);
Resource downloadVersion(@Parameter(description = "The author of the project to download the version from") @PathVariable String author,
@Parameter(description = "The slug of the project to download the version from") @PathVariable String slug,
@Parameter(description = "The name of the version to download") @PathVariable("name") String versionString,
@Parameter(description = "The platform of the version to download") @PathVariable Platform platform);
}

View File

@ -12,20 +12,13 @@ import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.service.APIKeyService;
import io.papermc.hangar.service.PermissionService;
import java.util.List;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// @el(user: io.papermc.hangar.model.db.UserTable)
@LoggedIn
@ -70,14 +63,14 @@ public class ApiKeyController {
@CurrentUser("#user")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 20)
@PostMapping(path = "/create-key/{user}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String createApiKey(@PathVariable final UserTable user, @RequestBody final @Valid CreateAPIKeyForm apiKeyForm) {
public String createApiKey(@PathVariable final UserTable user, @RequestBody @Valid final CreateAPIKeyForm apiKeyForm) {
return this.apiKeyService.createApiKey(user, apiKeyForm, this.permissionService.getAllPossiblePermissions(user.getId()));
}
@ResponseBody
@CurrentUser("#user")
@PostMapping(path = "/delete-key/{user}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void deleteApiKey(@PathVariable final UserTable user, @RequestBody final @Valid StringContent nameContent) {
public void deleteApiKey(@PathVariable final UserTable user, @RequestBody @Valid final StringContent nameContent) {
this.apiKeyService.deleteApiKey(user, nameContent.getContent());
}
}

View File

@ -16,10 +16,10 @@ import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.service.internal.projects.ChannelService;
import io.papermc.hangar.service.internal.projects.ProjectService;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -75,7 +75,7 @@ public class ChannelController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 15)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
@PostMapping(path = "/{projectId}/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public void createChannel(@PathVariable final long projectId, @RequestBody final @Valid ChannelForm channelForm) {
public void createChannel(@PathVariable final long projectId, @RequestBody @Valid final ChannelForm channelForm) {
final Set<ChannelFlag> flags = channelForm.getFlags();
flags.retainAll(flags.stream().filter(ChannelFlag::isEditable).collect(Collectors.toSet()));
this.channelService.createProjectChannel(channelForm.getName(), channelForm.getColor(), projectId, flags);
@ -86,7 +86,7 @@ public class ChannelController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 15)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
@PostMapping(path = "/{projectId}/edit", consumes = MediaType.APPLICATION_JSON_VALUE)
public void editChannel(@PathVariable final long projectId, @RequestBody final @Valid EditChannelForm channelForm) {
public void editChannel(@PathVariable final long projectId, @RequestBody @Valid final EditChannelForm channelForm) {
final Set<ChannelFlag> flags = channelForm.getFlags();
flags.retainAll(flags.stream().filter(ChannelFlag::isEditable).collect(Collectors.toSet()));
this.channelService.editProjectChannel(channelForm.getId(), channelForm.getName(), channelForm.getColor(), projectId, flags);

View File

@ -37,25 +37,17 @@ import io.papermc.hangar.service.internal.users.UserService;
import io.papermc.hangar.service.internal.users.invites.InviteService;
import io.papermc.hangar.service.internal.users.invites.OrganizationInviteService;
import io.papermc.hangar.service.internal.users.invites.ProjectInviteService;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@LoggedIn
@ -124,7 +116,7 @@ public class HangarUserController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 1, refillSeconds = 20)
@PermissionRequired(NamedPermission.EDIT_OWN_USER_SETTINGS)
@PostMapping(path = "/users/{userName}/settings/tagline", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveTagline(@PathVariable final String userName, @RequestBody final @Valid StringContent content) {
public void saveTagline(@PathVariable final String userName, @RequestBody @Valid final StringContent content) {
final UserTable userTable = this.userService.getUserTable(userName);
if (userTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);

View File

@ -28,24 +28,17 @@ import io.papermc.hangar.service.internal.organizations.OrganizationService;
import io.papermc.hangar.service.internal.perms.members.OrganizationMemberService;
import io.papermc.hangar.service.internal.users.UserService;
import io.papermc.hangar.service.internal.users.invites.OrganizationInviteService;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
// @el(orgName: String)
@Controller
@ -94,7 +87,7 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 2, refillSeconds = 10)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/members/add", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
public void addOrganizationMember(@PathVariable final String orgName, @RequestBody @Valid final EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
if (organizationTable == null) {
throw new HangarApiException("Org " + orgName + " doesn't exist");
@ -107,7 +100,7 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 2, refillSeconds = 10)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/members/edit", consumes = MediaType.APPLICATION_JSON_VALUE)
public void editOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
public void editOrganizationMember(@PathVariable final String orgName, @RequestBody @Valid final EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.memberService.editMember(member, organizationTable);
}
@ -117,7 +110,7 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 2, refillSeconds = 10)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/members/remove", consumes = MediaType.APPLICATION_JSON_VALUE)
public void removeOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
public void removeOrganizationMember(@PathVariable final String orgName, @RequestBody @Valid final EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.memberService.removeMember(member, organizationTable);
}
@ -136,7 +129,7 @@ public class OrganizationController extends HangarComponent {
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.IS_SUBJECT_OWNER, args = "{#orgName}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PostMapping(path = "/org/{orgName}/transfer", consumes = MediaType.APPLICATION_JSON_VALUE)
public void transferOrganization(@PathVariable final String orgName, @RequestBody final @Valid StringContent nameContent) {
public void transferOrganization(@PathVariable final String orgName, @RequestBody @Valid final StringContent nameContent) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.inviteService.sendTransferRequest(nameContent.getContent(), organizationTable);
}
@ -154,7 +147,7 @@ public class OrganizationController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@RateLimit(overdraft = 3, refillTokens = 1, refillSeconds = 60)
@PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public void create(@RequestBody final @Valid CreateOrganizationForm createOrganizationForm) {
public void create(@RequestBody @Valid final CreateOrganizationForm createOrganizationForm) {
this.organizationFactory.createOrganization(createOrganizationForm.getName());
}
@ -163,7 +156,7 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 3, refillTokens = 1, refillSeconds = 60)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.DELETE_ORGANIZATION, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
public void delete(@PathVariable final String orgName, @RequestBody final @Valid StringContent content) {
public void delete(@PathVariable final String orgName, @RequestBody @Valid final StringContent content) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.organizationFactory.deleteOrganization(organizationTable, content.getContent());
}
@ -173,7 +166,7 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 1, refillSeconds = 20)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/settings/tagline", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveTagline(@PathVariable final String orgName, @RequestBody final @Valid StringContent content) {
public void saveTagline(@PathVariable final String orgName, @RequestBody @Valid final StringContent content) {
final UserTable userTable = this.userService.getUserTable(orgName);
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
if (userTable == null || organizationTable == null) {

View File

@ -9,19 +9,14 @@ import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.service.internal.versions.ReviewService;
import java.util.List;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Unlocked
@Controller
@ -45,49 +40,49 @@ public class ReviewController {
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/start", consumes = MediaType.APPLICATION_JSON_VALUE)
public void startVersionReview(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void startVersionReview(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.startReview(versionId, message);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/message", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addVersionReviewMessage(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void addVersionReviewMessage(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.addReviewMessage(versionId, message);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/stop", consumes = MediaType.APPLICATION_JSON_VALUE)
public void stopVersionReview(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void stopVersionReview(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.stopReview(versionId, message);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/reopen", consumes = MediaType.APPLICATION_JSON_VALUE)
public void reopenVersionReview(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void reopenVersionReview(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.reopenReview(versionId, message, ReviewAction.REOPEN);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/approve", consumes = MediaType.APPLICATION_JSON_VALUE)
public void approveVersionReview(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void approveVersionReview(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.approveReview(versionId, message, ReviewState.REVIEWED, ReviewAction.APPROVE);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/approvePartial", consumes = MediaType.APPLICATION_JSON_VALUE)
public void approvePartialVersionReview(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void approvePartialVersionReview(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.approveReview(versionId, message, ReviewState.PARTIALLY_REVIEWED, ReviewAction.PARTIALLY_APPROVE);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/undoApproval", consumes = MediaType.APPLICATION_JSON_VALUE)
public void undoApproval(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
public void undoApproval(@PathVariable final long versionId, @RequestBody @Valid final ReviewMessage message) {
this.reviewService.undoApproval(versionId, message);
}
}

View File

@ -24,10 +24,10 @@ import io.papermc.hangar.service.internal.versions.PinnedVersionService;
import io.papermc.hangar.service.internal.versions.VersionDependencyService;
import io.papermc.hangar.service.internal.versions.VersionFactory;
import io.papermc.hangar.service.internal.versions.VersionService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
@ -79,9 +79,9 @@ public class VersionController extends HangarComponent {
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.CREATE_VERSION, args = "{#projectId}")
@PostMapping(path = "/version/{id}/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PendingVersion> create(@PathVariable("id") final long projectId,
@RequestPart(required = false) final @Size(max = 3, message = "version.new.error.invalidNumOfPlatforms") List<@Valid MultipartFile> files,
@RequestPart final @Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms") List<@Valid MultipartFileOrUrl> data,
@RequestPart final @NotBlank String channel) {
@RequestPart(required = false) @Size(max = 3, message = "version.new.error.invalidNumOfPlatforms") final List<@Valid MultipartFile> files,
@RequestPart @Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms") final List<@Valid MultipartFileOrUrl> data,
@RequestPart @NotBlank final String channel) {
// Use separate lists to hack around multipart form data limitations
return ResponseEntity.ok(this.versionFactory.createPendingVersion(projectId, data, files, channel));
}
@ -91,7 +91,7 @@ public class VersionController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.CREATE_VERSION, args = "{#projectId}")
@PostMapping(path = "/version/{id}/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public void createVersion(@PathVariable("id") final long projectId, @RequestBody final @Valid PendingVersion pendingVersion) {
public void createVersion(@PathVariable("id") final long projectId, @RequestBody @Valid final PendingVersion pendingVersion) {
this.versionFactory.publishPendingVersion(projectId, pendingVersion);
}
@ -100,7 +100,7 @@ public class VersionController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_VERSION, args = "{#projectId}")
@PostMapping(path = "/version/{projectId}/{versionId}/saveDescription", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveDescription(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid StringContent stringContent) {
public void saveDescription(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody @Valid final StringContent stringContent) {
if (stringContent.getContent().length() > this.config.pages.maxLen()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.maxLength");
}
@ -121,7 +121,7 @@ public class VersionController extends HangarComponent {
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_VERSION, args = "{#projectId}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 10)
@PostMapping(path = "/version/{projectId}/{versionId}/savePlatformVersions", consumes = MediaType.APPLICATION_JSON_VALUE)
public void savePlatformVersions(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid UpdatePlatformVersions updatePlatformVersions) {
public void savePlatformVersions(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody @Valid final UpdatePlatformVersions updatePlatformVersions) {
this.versionDependencyService.updateVersionPlatformVersions(projectId, versionId, updatePlatformVersions);
}
@ -130,7 +130,7 @@ public class VersionController extends HangarComponent {
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_VERSION, args = "{#projectId}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 10)
@PostMapping(path = "/version/{projectId}/{versionId}/savePluginDependencies", consumes = MediaType.APPLICATION_JSON_VALUE)
public void savePluginDependencies(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid UpdatePluginDependencies updatePluginDependencies) {
public void savePluginDependencies(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody @Valid final UpdatePluginDependencies updatePluginDependencies) {
this.versionDependencyService.updateVersionPluginDependencies(projectId, versionId, updatePluginDependencies);
}
@ -152,7 +152,7 @@ public class VersionController extends HangarComponent {
@RateLimit(overdraft = 3, refillTokens = 1, refillSeconds = 30)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.DELETE_VERSION, args = "{#projectId}")
@PostMapping(path = "/version/{projectId}/{versionId}/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
public void softDeleteVersion(@PathVariable final long projectId, @PathVariable("versionId") final ProjectVersionTable version, @RequestBody final @Valid StringContent commentContent) {
public void softDeleteVersion(@PathVariable final long projectId, @PathVariable("versionId") final ProjectVersionTable version, @RequestBody @Valid final StringContent commentContent) {
this.versionService.softDeleteVersion(projectId, version, commentContent.getContent());
}
@ -160,7 +160,7 @@ public class VersionController extends HangarComponent {
@ResponseStatus(HttpStatus.NO_CONTENT)
@PermissionRequired(NamedPermission.HARD_DELETE_VERSION)
@PostMapping(path = "/version/{projectId}/{versionId}/hardDelete", consumes = MediaType.APPLICATION_JSON_VALUE)
public void hardDeleteVersion(@PathVariable("projectId") final ProjectTable projectTable, @PathVariable("versionId") final ProjectVersionTable projectVersionTable, @RequestBody final @Valid StringContent commentContent) {
public void hardDeleteVersion(@PathVariable("projectId") final ProjectTable projectTable, @PathVariable("versionId") final ProjectVersionTable projectVersionTable, @RequestBody @Valid final StringContent commentContent) {
this.versionService.hardDeleteVersion(projectTable, projectVersionTable, commentContent.getContent());
}

View File

@ -33,9 +33,9 @@ import io.papermc.hangar.service.internal.admin.HealthService;
import io.papermc.hangar.service.internal.admin.StatService;
import io.papermc.hangar.service.internal.perms.roles.GlobalRoleService;
import io.papermc.hangar.service.internal.users.UserService;
import jakarta.validation.Valid;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
@ -80,7 +80,7 @@ public class AdminController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/platformVersions", consumes = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(NamedPermission.MANUAL_VALUE_CHANGES)
public void changePlatformVersions(@RequestBody final @Valid ChangePlatformVersionsForm form) {
public void changePlatformVersions(@RequestBody @Valid final ChangePlatformVersionsForm form) {
this.platformService.updatePlatformVersions(form);
}
@ -115,7 +115,7 @@ public class AdminController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.IS_STAFF)
@PostMapping(value = "/lock-user/{user}/{locked}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void setUserLock(@PathVariable final UserTable user, @PathVariable final boolean locked, @RequestBody final @Valid StringContent comment) {
public void setUserLock(@PathVariable final UserTable user, @PathVariable final boolean locked, @RequestBody @Valid final StringContent comment) {
this.userService.setLocked(user, locked, comment.getContent());
}

View File

@ -13,20 +13,14 @@ import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.service.internal.admin.FlagService;
import java.util.List;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@LoggedIn
@Controller
@ -44,7 +38,7 @@ public class FlagController extends HangarComponent {
@ResponseStatus(HttpStatus.CREATED)
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 10)
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
public void flag(@RequestBody final @Valid FlagForm form) {
public void flag(@RequestBody @Valid final FlagForm form) {
this.flagService.createFlag(form.getProjectId(), form.getReason(), form.getComment());
}

View File

@ -11,19 +11,14 @@ import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.service.internal.projects.ProjectAdminService;
import io.papermc.hangar.service.internal.projects.ProjectNoteService;
import java.util.List;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// @el(projectId: long)
@Unlocked
@ -50,14 +45,14 @@ public class ProjectAdminController extends HangarComponent {
@ResponseStatus(HttpStatus.CREATED)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
@PostMapping(path = "/notes/{projectId}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addProjectNote(@PathVariable final long projectId, @RequestBody final @Valid StringContent content) {
public void addProjectNote(@PathVariable final long projectId, @RequestBody @Valid final StringContent content) {
this.projectNoteService.addNote(projectId, content.getContent());
}
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.REVIEWER)
@PostMapping(path = "/visibility/{projectId}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void changeProjectVisibility(@PathVariable final long projectId, @RequestBody final @Valid VisibilityChangeForm visibilityChangeForm) {
public void changeProjectVisibility(@PathVariable final long projectId, @RequestBody @Valid final VisibilityChangeForm visibilityChangeForm) {
this.projectAdminService.changeVisibility(projectId, visibilityChangeForm.getVisibility(), visibilityChangeForm.getComment());
}

View File

@ -29,9 +29,9 @@ import io.papermc.hangar.service.internal.projects.ProjectService;
import io.papermc.hangar.service.internal.uploads.ImageService;
import io.papermc.hangar.service.internal.users.UserService;
import io.papermc.hangar.service.internal.users.invites.ProjectInviteService;
import jakarta.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -95,7 +95,7 @@ public class ProjectController extends HangarComponent {
@Unlocked
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createProject(@RequestBody final @Valid NewProjectForm newProject) {
public ResponseEntity<String> createProject(@RequestBody @Valid final NewProjectForm newProject) {
final ProjectTable projectTable = this.projectFactory.createProject(newProject);
// need to do this here, outside the transactional
this.projectService.refreshHomeProjects();
@ -116,7 +116,7 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 10, refillTokens = 1, refillSeconds = 10)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/settings", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid ProjectSettingsForm settingsForm) {
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final ProjectSettingsForm settingsForm) {
this.projectService.saveSettings(author, slug, settingsForm);
}
@ -125,7 +125,7 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 10, refillTokens = 1, refillSeconds = 5)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/sponsors", consumes = MediaType.APPLICATION_JSON_VALUE)
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent content) {
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final StringContent content) {
if (content.getContent().length() > this.config.projects.maxSponsorsLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
@ -155,7 +155,7 @@ public class ProjectController extends HangarComponent {
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_OWNER, args = "{#author, #slug}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PostMapping(path = "/project/{author}/{slug}/rename", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> renameProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent nameContent) {
public ResponseEntity<String> renameProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final StringContent nameContent) {
return ResponseEntity.ok(this.projectFactory.renameProject(author, slug, nameContent.getContent()));
}
@ -164,7 +164,7 @@ public class ProjectController extends HangarComponent {
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_OWNER, args = "{#author, #slug}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PostMapping(path = "/project/{author}/{slug}/transfer", consumes = MediaType.APPLICATION_JSON_VALUE)
public void transferProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent nameContent) {
public void transferProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final StringContent nameContent) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectInviteService.sendTransferRequest(nameContent.getContent(), projectTable);
}
@ -183,7 +183,7 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 2, refillSeconds = 10)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/members/add", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
public void addProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectInviteService.sendInvite(member, projectTable);
}
@ -193,7 +193,7 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 7, refillTokens = 1, refillSeconds = 10)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/members/edit", consumes = MediaType.APPLICATION_JSON_VALUE)
public void editProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
public void editProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectMemberService.editMember(member, projectTable);
}
@ -202,7 +202,7 @@ public class ProjectController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.MANAGE_SUBJECT_MEMBERS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/members/remove", consumes = MediaType.APPLICATION_JSON_VALUE)
public void removeProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
public void removeProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody @Valid final EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectMemberService.removeMember(member, projectTable);
}
@ -252,7 +252,7 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 3, refillTokens = 1, refillSeconds = 45)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.DELETE_PROJECT, args = "{#project}")
@PostMapping(path = "/project/{projectId}/manage/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
public void softDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody final @Valid StringContent commentContent) {
public void softDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody @Valid final StringContent commentContent) {
this.projectFactory.softDelete(project, commentContent.getContent());
}
@ -260,7 +260,7 @@ public class ProjectController extends HangarComponent {
@ResponseStatus(HttpStatus.NO_CONTENT)
@PermissionRequired(NamedPermission.HARD_DELETE_PROJECT)
@PostMapping(path = "/project/{projectId}/manage/hardDelete", consumes = MediaType.APPLICATION_JSON_VALUE)
public void hardDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody final @Valid StringContent commentContent) {
public void hardDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody @Valid final StringContent commentContent) {
this.projectFactory.hardDelete(project, commentContent.getContent());
}

View File

@ -17,8 +17,8 @@ import io.papermc.hangar.service.ValidationService;
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.service.internal.projects.ProjectPageService;
import io.papermc.hangar.util.BBCodeConverter;
import jakarta.validation.Valid;
import java.util.Collection;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -54,7 +54,7 @@ public class ProjectPageController extends HangarComponent {
@Anyone
@RateLimit(overdraft = 20, refillTokens = 3, refillSeconds = 5, greedy = true)
@PostMapping(path = "/render", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> renderMarkdown(@RequestBody final @Valid StringContent content) {
public ResponseEntity<String> renderMarkdown(@RequestBody @Valid final StringContent content) {
if (content.getContent().length() > this.config.projects.contentMaxLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
@ -65,7 +65,7 @@ public class ProjectPageController extends HangarComponent {
@RateLimit(overdraft = 10, refillTokens = 3, refillSeconds = 5)
@ResponseBody
@PostMapping(path = "/convert-bbcode", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public String convertBBCode(@RequestBody final @Valid StringContent bbCodeContent) {
public String convertBBCode(@RequestBody @Valid final StringContent bbCodeContent) {
if (bbCodeContent.getContent().length() > this.config.projects.maxBBCodeLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
@ -100,7 +100,7 @@ public class ProjectPageController extends HangarComponent {
@PermissionRequired(perms = NamedPermission.EDIT_PAGE, type = PermissionType.PROJECT, args = "{#projectId}")
@PostMapping(value = "/create/{projectId}", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<String> createProjectPage(@PathVariable final long projectId, @RequestBody final @Valid NewProjectPage newProjectPage) {
public ResponseEntity<String> createProjectPage(@PathVariable final long projectId, @RequestBody @Valid final NewProjectPage newProjectPage) {
return ResponseEntity.ok(this.projectPageService.createProjectPage(projectId, newProjectPage));
}
@ -109,7 +109,7 @@ public class ProjectPageController extends HangarComponent {
@PermissionRequired(perms = NamedPermission.EDIT_PAGE, type = PermissionType.PROJECT, args = "{#projectId}")
@PostMapping(value = "/save/{projectId}/{pageId}", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public void saveProjectPage(@PathVariable final long projectId, @PathVariable final long pageId, @RequestBody final @Valid StringContent content) {
public void saveProjectPage(@PathVariable final long projectId, @PathVariable final long pageId, @RequestBody @Valid final StringContent content) {
this.projectPageService.saveProjectPage(projectId, pageId, content.getContent());
}

View File

@ -1,15 +1,15 @@
package io.papermc.hangar.controller.validations;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import org.springframework.beans.BeanUtils;
@Target(ElementType.TYPE)

View File

@ -1,21 +1,16 @@
package io.papermc.hangar.controller.validations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import org.intellij.lang.annotations.Language;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = Validate.ValidateConstraintValidator.class)

View File

@ -8,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.server.ResponseStatusException;
public class HangarApiException extends ResponseStatusException {
@ -84,7 +85,7 @@ public class HangarApiException extends ResponseStatusException {
}
@Override
public @NotNull HttpHeaders getResponseHeaders() {
public @NotNull HttpHeaders getHeaders() {
return this.httpHeaders;
}
@ -93,19 +94,12 @@ public class HangarApiException extends ResponseStatusException {
@Override
public void serialize(final HangarApiException exception, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
String message = exception.getReason();
HttpStatus status = null;
final String message = exception.getReason();
HttpStatusCode status = null;
try {
status = exception.getStatus();
status = exception.getStatusCode();
} catch (final IllegalArgumentException ignored) {
}
if (message == null || message.isBlank()) {
if (status != null) {
message = status.getReasonPhrase();
} else {
message = "UNKNOWN";
}
}
gen.writeStartObject();
gen.writeStringField("message", message);
gen.writeArrayFieldStart("messageArgs");
@ -117,10 +111,8 @@ public class HangarApiException extends ResponseStatusException {
gen.writeObjectFieldStart("httpError");
if (status != null) {
gen.writeNumberField("statusCode", status.value());
gen.writeStringField("statusPhrase", status.getReasonPhrase());
} else {
gen.writeNumberField("statusCode", exception.getRawStatusCode());
gen.writeStringField("statusPhrase", "UNKNOWN");
gen.writeNumberField("statusCode", exception.getStatusCode().value());
}
gen.writeEndObject();
}

View File

@ -32,10 +32,7 @@ public class MultiHangarApiException extends ResponseStatusException {
gen.writeBooleanField("isHangarApiException", true);
gen.writeArrayFieldStart("exceptions");
for (final HangarApiException exception : value.exceptions) {
String message = exception.getReason();
if (message == null || message.isBlank()) {
message = exception.getStatus().getReasonPhrase();
}
final String message = exception.getReason();
gen.writeStartObject();
gen.writeStringField("message", message);
gen.writeArrayFieldStart("messageArgs");
@ -45,8 +42,7 @@ public class MultiHangarApiException extends ResponseStatusException {
gen.writeEndArray();
gen.writeBooleanField("isHangarApiException", true);
gen.writeObjectFieldStart("httpError");
gen.writeNumberField("statusCode", exception.getStatus().value());
gen.writeStringField("statusPhrase", exception.getStatus().getReasonPhrase());
gen.writeNumberField("statusCode", exception.getStatusCode().value());
gen.writeEndObject();
gen.writeEndObject();
}

View File

@ -6,7 +6,7 @@ import io.papermc.hangar.exceptions.MultiHangarApiException;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -26,16 +26,16 @@ public class HangarEntityExceptionHandler extends ResponseEntityExceptionHandler
@ExceptionHandler(HangarApiException.class)
public ResponseEntity<HangarApiException> handleException(final HangarApiException exception) {
return new ResponseEntity<>(exception, exception.getResponseHeaders(), exception.getRawStatusCode());
return new ResponseEntity<>(exception, exception.getHeaders(), exception.getStatusCode().value());
}
@ExceptionHandler(MultiHangarApiException.class)
public ResponseEntity<MultiHangarApiException> handleException(final MultiHangarApiException exception) {
return new ResponseEntity<>(exception, exception.getResponseHeaders(), exception.getRawStatusCode());
return new ResponseEntity<>(exception, exception.getHeaders(), exception.getStatusCode().value());
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(final Exception ex, final Object body, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
protected ResponseEntity<Object> handleExceptionInternal(final Exception ex, final Object body, final HttpHeaders headers, final HttpStatusCode status, final WebRequest request) {
if (this.config.isDev()) {
return new ResponseEntity<>(new HangarApiException(ex.getMessage()), status);
} else {
@ -44,7 +44,7 @@ public class HangarEntityExceptionHandler extends ResponseEntityExceptionHandler
}
@Override
protected @NotNull ResponseEntity<Object> handleMethodArgumentNotValid(final @NotNull MethodArgumentNotValidException ex, final @NotNull HttpHeaders headers, final @NotNull HttpStatus status, final @NotNull WebRequest request) {
protected @NotNull ResponseEntity<Object> handleMethodArgumentNotValid(final @NotNull MethodArgumentNotValidException ex, final @NotNull HttpHeaders headers, final @NotNull HttpStatusCode status, final @NotNull WebRequest request) {
return new ResponseEntity<>(ex, headers, status);
}
}

View File

@ -4,11 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import io.papermc.hangar.model.Model;
import io.papermc.hangar.model.Named;
import io.papermc.hangar.model.common.roles.GlobalRole;
import jakarta.annotation.Nullable;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
public class User extends Model implements Named {

View File

@ -2,9 +2,9 @@ package io.papermc.hangar.model.api.project;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.util.Collection;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.jdbi.v3.core.mapper.Nested;
import org.jetbrains.annotations.Nullable;
@ -24,11 +24,14 @@ public class ProjectSettings {
// @el(root: String)
private final @Validate(SpEL = "@validate.regex(#root, @hangarConfig.urlRegex)", message = "validation.invalidUrl") String wiki;
private final @Valid ProjectLicense license;
private final @Valid ProjectDonationSettings donation;
@Valid
private final ProjectLicense license;
@Valid
private final ProjectDonationSettings donation;
// @el(root: Collection<String>)
private final @NotNull @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxKeywords)", message = "project.new.error.tooManyKeywords") Collection<String> keywords;
@NotNull
private final @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxKeywords)", message = "project.new.error.tooManyKeywords") Collection<String> keywords;
private final boolean forumSync;
// @el(root: String)

View File

@ -4,8 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.AtLeastOneNotNull;
import io.papermc.hangar.model.Named;
import io.papermc.hangar.model.api.project.ProjectNamespace;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
import javax.validation.constraints.NotBlank;
import org.jdbi.v3.core.mapper.Nested;
import org.jdbi.v3.core.mapper.reflect.JdbiConstructor;
import org.jetbrains.annotations.Nullable;
@ -13,7 +13,8 @@ import org.jetbrains.annotations.Nullable;
@AtLeastOneNotNull(fieldNames = {"namespace", "externalUrl"}, includeBlankStrings = true, message = "Must specify a projectId or external URL for a dependency")
public class PluginDependency implements Named {
private final @NotBlank(message = "Must have a dependency name") String name;
@NotBlank(message = "Must have a dependency name")
private final String name;
private final boolean required;
private final ProjectNamespace namespace;
private final String externalUrl;

View File

@ -2,13 +2,15 @@ package io.papermc.hangar.model.api.requests;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.model.common.projects.FlagReason;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public class FlagForm {
private final @NotBlank String comment;
@NotBlank
private final String comment;
private final long projectId;
private final @NotNull FlagReason reason;
@NotNull
private final FlagReason reason;
@JsonCreator
public FlagForm(final String comment, final long projectId, final FlagReason reason) {

View File

@ -2,27 +2,30 @@ package io.papermc.hangar.model.api.requests;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
public class RequestPagination {
@ApiModelProperty(value = "The maximum amount of items to return", example = "1", allowEmptyValue = true, allowableValues = "range[1, 25]")
@Schema(description = "The maximum amount of items to return", example = "1", requiredMode = NOT_REQUIRED, allowableValues = "range[1, 25]")
private final long limit;
@ApiModelProperty(value = "Where to start searching", example = "0", allowEmptyValue = true, allowableValues = "range[0, infinity]")
@Schema(description = "Where to start searching", example = "0", requiredMode = NOT_REQUIRED, allowableValues = "range[0, infinity]")
private final long offset;
@JsonIgnore
@ApiModelProperty(hidden = true)
@Schema(accessMode = READ_ONLY)
private final List<Filter.FilterInstance> filters;
@JsonIgnore
@ApiModelProperty(hidden = true)
@Schema(accessMode = READ_ONLY)
private final Map<String, Consumer<StringBuilder>> sorters;
/**

View File

@ -1,17 +1,17 @@
package io.papermc.hangar.model.internal.api.requests;
import io.papermc.hangar.model.common.NamedPermission;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class CreateAPIKeyForm {
@ApiModelProperty(allowableValues = "range[5,256)", required = true)
@Schema(allowableValues = "range[5,256)", requiredMode = Schema.RequiredMode.REQUIRED)
private final @NotBlank @Size(min = 5, max = 255) String name;
@ApiModelProperty(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private final @Size(min = 1) List<NamedPermission> permissions;
public CreateAPIKeyForm(final String name, final List<NamedPermission> permissions) {

View File

@ -5,9 +5,9 @@ import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.common.roles.Role;
import io.papermc.hangar.model.db.roles.ExtendedRoleTable;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import org.springframework.http.HttpStatus;
public class EditMembersForm<R extends Role<? extends ExtendedRoleTable<R, ?>>> {
@ -32,7 +32,8 @@ public class EditMembersForm<R extends Role<? extends ExtendedRoleTable<R, ?>>>
public static class Member<R extends Role<? extends ExtendedRoleTable<R, ?>>> {
private final @NotBlank String name;
@NotBlank
private final String name;
// @el(root: Role)
private final @Validate(SpEL = "#root.assignable") R role;

View File

@ -1,13 +1,14 @@
package io.papermc.hangar.model.internal.api.requests;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
/**
* Request body for simple strings
*/
public class StringContent {
private @NotBlank String content;
@NotBlank
private String content;
public String getContent() {
return this.content;

View File

@ -1,10 +1,10 @@
package io.papermc.hangar.model.internal.api.requests.admin;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.EnumMap;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class ChangePlatformVersionsForm extends EnumMap<Platform, @Size(min = 1) List<@NotBlank String>> {

View File

@ -1,6 +1,6 @@
package io.papermc.hangar.model.internal.api.requests.admin;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.jetbrains.annotations.NotNull;
public record ReportNotificationForm(boolean warning, boolean toReporter, @Valid @NotNull String content) {

View File

@ -4,15 +4,17 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.common.ChannelFlag;
import io.papermc.hangar.model.common.Color;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class ChannelForm {
// @el(root: String)
private final @NotBlank @Validate(SpEL = "@validations.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.modal.error.invalidName") @Validate(SpEL = "@validations.max(#root, @hangarConfig.channels.maxNameLen)", message = "channel.modal.error.tooLongName") String name;
private final @NotNull Color color;
@NotBlank
private final @Validate(SpEL = "@validations.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.modal.error.invalidName") @Validate(SpEL = "@validations.max(#root, @hangarConfig.channels.maxNameLen)", message = "channel.modal.error.tooLongName") String name;
@NotNull
private final Color color;
private final Set<ChannelFlag> flags;
@JsonCreator

View File

@ -3,14 +3,15 @@ package io.papermc.hangar.model.internal.api.requests.projects;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.api.project.ProjectSettings;
import io.papermc.hangar.model.common.projects.Category;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
public class NewProjectForm extends ProjectSettingsForm {
private final long ownerId;
// @el(root: String)
private final @NotNull(message = "project.new.error.invalidName") @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxNameLen)", message = "project.new.error.tooLongName") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.projects.nameRegex)", message = "project.new.error.invalidName") String name;
@NotNull(message = "project.new.error.invalidName")
private final @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxNameLen)", message = "project.new.error.tooLongName") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.projects.nameRegex)", message = "project.new.error.invalidName") String name;
// @el(root: String)
private final @Validate(SpEL = "@validate.max(#root, @hangarConfig.pages.maxLen)", message = "page.new.error.maxLength") String pageContent;

View File

@ -2,16 +2,17 @@ package io.papermc.hangar.model.internal.api.requests.projects;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
public class NewProjectPage {
// @el(root: String)
private final @NotBlank @Validate(SpEL = "@validate.min(#root, @hangarConfig.pages.minNameLen)", message = "page.new.error.name.minLength") @Validate(SpEL = "@validate.max(#root, @hangarConfig.pages.maxNameLen)", message = "page.new.error.name.maxLength") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.pages.nameRegex)", message = "page.new.error.name.invalidChars") String name;
@NotBlank
private final @Validate(SpEL = "@validate.min(#root, @hangarConfig.pages.minNameLen)", message = "page.new.error.name.minLength") @Validate(SpEL = "@validate.max(#root, @hangarConfig.pages.maxNameLen)", message = "page.new.error.name.maxLength") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.pages.nameRegex)", message = "page.new.error.name.invalidChars") String name;
private final Long parentId;
@JsonCreator
public NewProjectPage(final @NotBlank String name, final Long parentId) {
public NewProjectPage(@NotBlank final String name, final Long parentId) {
this.name = name;
this.parentId = parentId;
}

View File

@ -4,16 +4,19 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.api.project.ProjectSettings;
import io.papermc.hangar.model.common.projects.Category;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
public class ProjectSettingsForm {
private final @Valid ProjectSettings settings;
private final @NotNull(message = "project.new.error.noCategory") Category category;
@Valid
private final ProjectSettings settings;
@NotNull(message = "project.new.error.noCategory")
private final Category category;
// @el(root: String)
private final @NotNull(message = "project.new.error.noDescription") @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxDescLen)", message = "project.new.error.tooLongDesc") String description;
@NotNull(message = "project.new.error.noDescription")
private final @Validate(SpEL = "@validate.max(#root, @hangarConfig.projects.maxDescLen)", message = "project.new.error.tooLongDesc") String description;
@JsonCreator
public ProjectSettingsForm(final ProjectSettings settings, final Category category, final String description) {

View File

@ -2,11 +2,12 @@ package io.papermc.hangar.model.internal.api.requests.projects;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.model.common.projects.Visibility;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
public class VisibilityChangeForm {
private final @NotNull Visibility visibility;
@NotNull
private final Visibility visibility;
private final String comment;
@JsonCreator

View File

@ -2,12 +2,14 @@ package io.papermc.hangar.model.internal.api.requests.versions;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ObjectNode;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
public class ReviewMessage {
private final @NotNull String message;
private final @NotNull ObjectNode args;
@NotNull
private final String message;
@NotNull
private final ObjectNode args;
@JsonCreator
public ReviewMessage(final String message, final ObjectNode args) {

View File

@ -2,15 +2,17 @@ package io.papermc.hangar.model.internal.api.requests.versions;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class UpdatePlatformVersions {
private final @NotNull Platform platform;
private final @Size(min = 1, message = "version.edit.error.noPlatformVersions") Set<@NotBlank(message = "version.new.error.invalidPlatformVersion") String> versions;
@NotNull
private final Platform platform;
@Size(min = 1, message = "version.edit.error.noPlatformVersions")
private final Set<@NotBlank(message = "version.new.error.invalidPlatformVersion") String> versions;
@JsonCreator
public UpdatePlatformVersions(final Platform platform, final Set<String> versions) {

View File

@ -3,20 +3,21 @@ package io.papermc.hangar.model.internal.api.requests.versions;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.model.api.project.version.PluginDependency;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class UpdatePluginDependencies {
private final @NotNull Platform platform;
@NotNull
private final Platform platform;
private final Map<String, @Valid PluginDependency> pluginDependencies;
@JsonCreator
public UpdatePluginDependencies(final @NotNull Platform platform, final Set<@Valid PluginDependency> pluginDependencies) {
public UpdatePluginDependencies(@NotNull final Platform platform, final Set<@Valid PluginDependency> pluginDependencies) {
this.platform = platform;
this.pluginDependencies = pluginDependencies.stream().collect(Collectors.toMap(PluginDependency::getName, Function.identity()));
}

View File

@ -2,20 +2,20 @@ package io.papermc.hangar.model.internal.discourse;
import java.time.Duration;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
public class DiscourseError extends RuntimeException {
public static class StatusError extends DiscourseError {
private final HttpStatus status;
private final HttpStatusCode status;
private final String message;
public StatusError(final HttpStatus status, final String message) {
public StatusError(final HttpStatusCode status, final String message) {
this.status = status;
this.message = message;
}
public HttpStatus getStatus() {
public HttpStatusCode getStatus() {
return this.status;
}

View File

@ -5,9 +5,9 @@ import io.papermc.hangar.model.api.User;
import io.papermc.hangar.model.api.UserNameChange;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.common.roles.GlobalRole;
import jakarta.annotation.Nullable;
import java.time.OffsetDateTime;
import java.util.List;
import javax.annotation.Nullable;
public class HangarUser extends User implements Identified {

View File

@ -2,8 +2,8 @@ package io.papermc.hangar.model.internal.versions;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.constraints.Size;
import java.util.List;
import javax.validation.constraints.Size;
import org.jetbrains.annotations.Nullable;
// @el(root: String) // can't figure out how to apply this to just the one record component

View File

@ -6,31 +6,37 @@ import io.papermc.hangar.model.api.project.version.PluginDependency;
import io.papermc.hangar.model.common.ChannelFlag;
import io.papermc.hangar.model.common.Color;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.jetbrains.annotations.Nullable;
public class PendingVersion {
// @el(root: String)
private final @NotBlank(message = "version.new.error.invalidVersionString") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.projects.versionNameRegex)", message = "version.new.error.invalidVersionString") String versionString;
@NotBlank(message = "version.new.error.invalidVersionString")
private final @Validate(SpEL = "@validate.regex(#root, @hangarConfig.projects.versionNameRegex)", message = "version.new.error.invalidVersionString") String versionString;
private final Map<Platform, Set<@Valid PluginDependency>> pluginDependencies;
private final @Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms") Map<Platform, @Size(min = 1, message = "version.edit.error.noPlatformVersions") SortedSet<@NotBlank(message = "version.new.error.invalidPlatformVersion") String>> platformDependencies;
@Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms")
private final Map<Platform, @Size(min = 1, message = "version.edit.error.noPlatformVersions") SortedSet<@NotBlank(message = "version.new.error.invalidPlatformVersion") String>> platformDependencies;
// @el(root: String)
private final @NotBlank(message = "version.new.error.noDescription") @Validate(SpEL = "@validate.max(#root, @hangarConfig.pages.maxLen)", message = "page.new.error.maxLength") String description;
private final @Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms") List<@Valid PendingVersionFile> files;
@NotBlank(message = "version.new.error.noDescription")
private final @Validate(SpEL = "@validate.max(#root, @hangarConfig.pages.maxLen)", message = "page.new.error.maxLength") String description;
@Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms")
private final List<@Valid PendingVersionFile> files;
// @el(root: String)
private final @NotBlank(message = "version.new.error.channel.noName") @Validate(SpEL = "@validate.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.modal.error.invalidName") String channelName;
private final @NotNull(message = "version.new.error.channel.noColor") Color channelColor;
@NotBlank(message = "version.new.error.channel.noName")
private final @Validate(SpEL = "@validate.regex(#root, @hangarConfig.channels.nameRegex)", message = "channel.modal.error.invalidName") String channelName;
@NotNull(message = "version.new.error.channel.noColor")
private final Color channelColor;
private final Set<ChannelFlag> channelFlags;
private final boolean forumSync;

View File

@ -3,8 +3,8 @@ package io.papermc.hangar.model.internal.versions;
import io.papermc.hangar.controller.validations.Validate;
import io.papermc.hangar.model.api.project.version.FileInfo;
import io.papermc.hangar.model.common.Platform;
import jakarta.validation.constraints.Size;
import java.util.List;
import javax.validation.constraints.Size;
import org.jetbrains.annotations.Nullable;
// @el(root: String) // can't figure out how to apply this to just the field

View File

@ -3,9 +3,9 @@ package io.papermc.hangar.security.annotations.ratelimit;
import io.github.bucket4j.Bucket;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.service.internal.BucketService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -2,9 +2,9 @@ package io.papermc.hangar.security.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.papermc.hangar.exceptions.HangarApiException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.pdfbox.util.Charsets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;

View File

@ -4,15 +4,11 @@ import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import io.papermc.hangar.security.configs.SecurityConfig;
import io.papermc.hangar.service.TokenService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
@ -27,6 +23,10 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
public class HangarAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

View File

@ -15,6 +15,7 @@ import io.papermc.hangar.model.internal.job.UpdateDiscourseVersionPostJob;
import io.papermc.hangar.service.internal.discourse.DiscourseService;
import io.papermc.hangar.service.internal.projects.ProjectService;
import io.papermc.hangar.service.internal.versions.VersionService;
import jakarta.annotation.PostConstruct;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.List;
@ -22,7 +23,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.context.SecurityContextHolder;

View File

@ -10,12 +10,12 @@ import io.papermc.hangar.model.identified.ProjectIdentified;
import io.papermc.hangar.model.identified.VersionIdentified;
import io.papermc.hangar.model.internal.admin.DayStats;
import io.papermc.hangar.util.RequestUtil;
import jakarta.servlet.http.Cookie;
import java.net.InetAddress;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.http.Cookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;

View File

@ -16,6 +16,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
@ -96,8 +97,8 @@ public class DiscourseApi {
}
}
private DiscourseError createFromStatus(final HttpStatus status, final String message) {
if (status.equals(HttpStatus.TOO_MANY_REQUESTS)) {
private DiscourseError createFromStatus(final HttpStatusCode status, final String message) {
if (status.value() == HttpStatus.TOO_MANY_REQUESTS.value()) {
if (message != null) {
try {
final JSONObject json = new JSONObject(message);

View File

@ -22,6 +22,7 @@ import io.papermc.hangar.service.internal.admin.StatService;
import io.papermc.hangar.service.internal.file.FileService;
import io.papermc.hangar.service.internal.uploads.ProjectFiles;
import io.papermc.hangar.util.RequestUtil;
import jakarta.servlet.http.Cookie;
import java.net.InetAddress;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
@ -30,7 +31,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.http.Cookie;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;

View File

@ -19,10 +19,10 @@ import io.papermc.hangar.model.internal.versions.HangarReview;
import io.papermc.hangar.model.internal.versions.HangarReviewQueueEntry;
import io.papermc.hangar.service.internal.users.NotificationService;
import io.papermc.hangar.service.internal.visibility.ProjectVersionVisibilityService;
import jakarta.validation.constraints.NotNull;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@ -136,7 +136,8 @@ public class ReviewService extends HangarComponent {
}
}
private @NotNull ProjectVersionReviewTable getLatestUnfinishedReviewAndValidate(final long versionId) {
@NotNull
private ProjectVersionReviewTable getLatestUnfinishedReviewAndValidate(final long versionId) {
final ProjectVersionReviewTable latestUnfinishedReview = this.projectVersionReviewsDAO.getLatestUnfinishedReview(versionId, this.getHangarPrincipal().getUserId());
if (latestUnfinishedReview == null) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "reviews.error.noReviewStarted");

View File

@ -1,11 +1,11 @@
package io.papermc.hangar.util;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class RequestUtil {

View File

@ -1,9 +1,9 @@
package io.papermc.hangar.util;
import jakarta.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

View File

@ -1,11 +1,11 @@
package io.papermc.hangar.util;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
public final class StringUtils {
@ -126,7 +126,7 @@ public final class StringUtils {
return input.equals("None") ? null : input;
}
public static boolean isAnyEqualIgnoreCase(final @NotNull String lhs, final String @NotNull ... rhs) {
public static boolean isAnyEqualIgnoreCase(@NotNull final String lhs, final String @NotNull ... rhs) {
for (final String string : rhs) {
if (lhs.equalsIgnoreCase(string)) {

View File

@ -95,7 +95,7 @@ export default defineNuxtConfig({
"/handle-logout": defineProxyBackend(),
"/refresh": defineProxyBackend(),
"/invalidate": defineProxyBackend(),
"/v2/api-docs/": defineProxyBackend(),
"/v3/api-docs": defineProxyBackend(),
"/robots.txt": defineProxyBackend(),
"/sitemap.xml": defineProxyBackend(),
"/global-sitemap.xml": defineProxyBackend(),

View File

@ -11,7 +11,7 @@ const route = useRoute();
const authStore = useAuthStore();
onMounted(() => {
const swaggerUiVersion = "4.13.0";
const swaggerUiVersion = "4.15.5";
const bundle = document.createElement("script");
bundle.src = `https://unpkg.com/swagger-ui-dist@${swaggerUiVersion}/swagger-ui-bundle.js`;
@ -26,22 +26,22 @@ onMounted(() => {
const script = document.createElement("script");
// language=JavaScript
script.innerHTML = `
window.ui = SwaggerUIBundle({
url: "/v2/api-docs/",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
layout: "BaseLayout",
requestInterceptor: (req) => {
if (!req.loadSpec) {
if (req.url.startsWith("http://localhost:8080")) {
req.url = req.url.replace("http://localhost:8080", "http://localhost:3333");
window.ui = SwaggerUIBundle({
url: "/v3/api-docs.yaml",
dom_id: "#swagger-ui",
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
layout: "BaseLayout",
requestInterceptor: (req) => {
if (!req.loadSpec) {
if (req.url.startsWith("http://localhost:8080")) {
req.url = req.url.replace("http://localhost:8080", "http://localhost:3333");
}
}
return req;
}
return req;
}
});
});
`;
bundle.onload = () => document.body.append(script);
@ -51,7 +51,7 @@ useHead(useSeo(i18n.t("apiDocs.title"), "API Docs for the Hangar REST API", rout
</script>
<template>
<div class="bg-gray-100 dark:(bg-gray-200) rounded-md my-auto mx-2 py-1" lg="w-2/3 min-w-2/3 max-w-2/3">
<div class="bg-gray-100 dark:(bg-gray-200) rounded-md my-auto mx-2 py-1">
<div id="swagger-ui">
<h1 class="text-3xl text-bold">Hangar API</h1>
<h2 class="text-2xl">API Docs for the Hangar REST API</h2>
@ -68,27 +68,33 @@ useHead(useSeo(i18n.t("apiDocs.title"), "API Docs for the Hangar REST API", rout
.info hgroup.main a {
display: none;
}
.wrapper .info {
background-color: unset !important;
border-color: unset !important;
margin: 2rem 0;
.title small pre {
background-color: unset;
border: unset;
}
.description h2 {
padding-top: 1.5rem;
margin: 1.5rem 0 0;
border-top: 3px solid #333333;
}
.scheme-container {
border-top: 1px solid rgba(0, 0, 0, 0.15);
}
.markdown {
min-height: 0;
}
}
}
.model-container,
.responses-inner {
overflow-x: auto;