chore: code cleanup

This commit is contained in:
Jake Potrebic 2022-12-27 10:53:12 -08:00
parent 996014b614
commit f730944592
No known key found for this signature in database
GPG Key ID: 27CC63F7CBC866C7
431 changed files with 6520 additions and 6875 deletions

View File

@ -30,6 +30,7 @@ ij_java_layout_static_imports_separately = true
ij_java_generate_final_locals = true
ij_java_generate_final_parameters = true
ij_java_continuation_indent_size = 4
ij_java_record_components_wrap = on_every_item
[{*.yml, *.yaml}]
indent_size = 2

View File

@ -11,7 +11,6 @@
<option name="IDENTIFIER_CASE" value="1" />
<option name="TYPE_CASE" value="1" />
<option name="CUSTOM_TYPE_CASE" value="1" />
<option name="ALIAS_CASE" value="1" />
<option name="BUILT_IN_CASE" value="4" />
<option name="SUBQUERY_OPENING" value="1" />
<option name="SUBQUERY_CONTENT" value="1" />
@ -19,9 +18,12 @@
<option name="INSERT_INTO_NL" value="2" />
<option name="INSERT_EL_WRAP" value="2" />
<option name="SET_EL_LINE" value="1" />
<option name="SET_EL_WRAP" value="2" />
<option name="WITH_ALIGN_AS" value="true" />
<option name="SELECT_USE_AS_WORD" value="2" />
<option name="SELECT_EL_WRAP" value="2" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_ONLY_JOIN_INDENT" value="1" />
<option name="WHERE_EL_WRAP" value="2" />
<option name="ORDER_EL_WRAP" value="2" />
<option name="CONSTRAINT_WRAP_1" value="false" />
<option name="CONSTRAINT_WRAP_4" value="true" />

View File

@ -37,7 +37,6 @@
<option name="m_ignoreStaticMethodCalls" value="false" />
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnnotations" value="true" />
</inspection_tool>

View File

@ -5,20 +5,20 @@
/**
* Copyright (c) 2015-2016, Atlassian Pty Ltd
* All rights reserved.
*
* <p>
* Copyright (c) 2016-2018, Vladimir Schneider,
* All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* list of conditions and the following disclaimer.
* <p>
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@ -38,76 +38,72 @@ import com.vladsch.flexmark.parser.InlineParserExtension;
import com.vladsch.flexmark.parser.InlineParserExtensionFactory;
import com.vladsch.flexmark.parser.LightInlineParser;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import java.util.Set;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.regex.Pattern;
public class ResizableImageInlineParserExtension implements InlineParserExtension {
final public static Pattern IMAGE_PATTERN = Pattern.compile("!\\[([^\\s\\]]*)]\\(([^\\s\\]]+)\\s*=*(\\d*)x*(\\d*)\\)", // Hangar - fix image in link
Pattern.CASE_INSENSITIVE);
public static final Pattern IMAGE_PATTERN = Pattern.compile("!\\[([^\\s\\]]*)]\\(([^\\s\\]]+)\\s*=*(\\d*)x*(\\d*)\\)", // Hangar - fix image in link
Pattern.CASE_INSENSITIVE);
public ResizableImageInlineParserExtension(LightInlineParser inlineParser) {
public ResizableImageInlineParserExtension(final LightInlineParser inlineParser) {
}
@Override
public void finalizeDocument(@NotNull InlineParser inlineParser) {
public void finalizeDocument(final @NotNull InlineParser inlineParser) {
}
@Override
public void finalizeBlock(@NotNull InlineParser inlineParser) {
public void finalizeBlock(final @NotNull InlineParser inlineParser) {
}
@Override
public boolean parse(@NotNull LightInlineParser inlineParser) {
int index = inlineParser.getIndex();
public boolean parse(final @NotNull LightInlineParser inlineParser) {
final int index = inlineParser.getIndex();
// FIX
BasedSequence input = inlineParser.getInput();
final BasedSequence input = inlineParser.getInput();
if (index + 1 >= input.length()) {
return false;
}
char c = input.charAt(index + 1);
final char c = input.charAt(index + 1);
// END FIX
if (c == '[') {
BasedSequence[] matches = inlineParser.matchWithGroups(IMAGE_PATTERN);
final BasedSequence[] matches = inlineParser.matchWithGroups(IMAGE_PATTERN);
if (matches != null) {
inlineParser.flushTextNode();
BasedSequence text = matches[1];
BasedSequence source = matches[2];
BasedSequence width = matches[3];
BasedSequence height = matches[4];
final BasedSequence text = matches[1];
final BasedSequence source = matches[2];
final BasedSequence width = matches[3];
final BasedSequence height = matches[4];
ResizableImage image = new ResizableImage(text, source, width, height);
final ResizableImage image = new ResizableImage(text, source, width, height);
inlineParser.getBlock().appendChild(image);
return true;
}
}
return false;
}
public static class Factory implements InlineParserExtensionFactory {
@Nullable
@Override
public Set<Class<?>> getAfterDependents() {
public @Nullable Set<Class<?>> getAfterDependents() {
return null;
}
@NotNull
@Override
public CharSequence getCharacters() {
public @NotNull CharSequence getCharacters() {
return "!";
}
@Nullable
@Override
public Set<Class<?>> getBeforeDependents() {
public @Nullable Set<Class<?>> getBeforeDependents() {
return null;
}
@NotNull
@Override
public InlineParserExtension apply(@NotNull LightInlineParser lightInlineParser) {
public @NotNull InlineParserExtension apply(final @NotNull LightInlineParser lightInlineParser) {
return new ResizableImageInlineParserExtension(lightInlineParser);
}

View File

@ -13,7 +13,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@ConfigurationPropertiesScan(value = "io.papermc.hangar.config.hangar", basePackageClasses = PagesConfig.class)
public class HangarApplication {
public static void main(String[] args) {
public static void main(final String[] args) {
SpringApplication.run(HangarApplication.class, args);
}

View File

@ -6,6 +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 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;
@ -17,13 +20,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.server.ResponseStatusException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
public abstract class HangarComponent {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
protected HttpServletRequest request;
@ -39,27 +38,25 @@ public abstract class HangarComponent {
protected UserActionLogService actionLogger;
protected final Optional<HangarPrincipal> getOptionalHangarPrincipal() {
return getHangarPrincipal0().get();
return this.getHangarPrincipal0().get();
}
@NotNull
protected final Permission getGlobalPermissions() {
return getHangarPrincipal0().get().map(HangarPrincipal::getGlobalPermissions).orElse(PermissionService.DEFAULT_SIGNED_OUT_PERMISSIONS);
protected final @NotNull Permission getGlobalPermissions() {
return this.getHangarPrincipal0().get().map(HangarPrincipal::getGlobalPermissions).orElse(PermissionService.DEFAULT_SIGNED_OUT_PERMISSIONS);
}
protected final HangarPrincipal getHangarPrincipal() {
return getHangarPrincipal0().get().orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication principal found"));
return this.getHangarPrincipal0().get().orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication principal found"));
}
@Nullable
protected final Long getHangarUserId() {
return getHangarPrincipal0().get().map(HangarPrincipal::getId).orElse(null);
protected final @Nullable Long getHangarUserId() {
return this.getHangarPrincipal0().get().map(HangarPrincipal::getId).orElse(null);
}
private MemoizingSupplier<Optional<HangarPrincipal>> getHangarPrincipal0() {
return MemoizingSupplier.of(() -> Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.filter(HangarAuthenticationToken.class::isInstance)
.map(HangarAuthenticationToken.class::cast)
.map(HangarAuthenticationToken::getPrincipal));
.filter(HangarAuthenticationToken.class::isInstance)
.map(HangarAuthenticationToken.class::cast)
.map(HangarAuthenticationToken::getPrincipal));
}
}

View File

@ -1,5 +1,8 @@
package io.papermc.hangar;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
@ -22,10 +25,6 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
// https://stackoverflow.com/questions/61526870/spring-boot-custom-bean-loader
public class JdbiBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware, BeanClassLoaderAware, BeanFactoryAware {
@ -35,55 +34,55 @@ public class JdbiBeanFactoryPostProcessor implements BeanDefinitionRegistryPostP
private ClassLoader classLoader;
@Override
public void setResourceLoader(@NotNull ResourceLoader resourceLoader) {
public void setResourceLoader(final @NotNull ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setEnvironment(@NotNull Environment environment) {
public void setEnvironment(final @NotNull Environment environment) {
this.environment = environment;
}
@Override
public void setBeanClassLoader(@NotNull ClassLoader classLoader) {
public void setBeanClassLoader(final @NotNull ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanFactory(@NotNull BeanFactory beanFactory) throws BeansException {
public void setBeanFactory(final @NotNull BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
public void postProcessBeanFactory(final @NotNull ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
// not needed
}
@Override
public void postProcessBeanDefinitionRegistry(@NotNull BeanDefinitionRegistry registry) throws BeansException {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
public void postProcessBeanDefinitionRegistry(final @NotNull BeanDefinitionRegistry registry) throws BeansException {
final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(@NotNull AnnotatedBeanDefinition beanDefinition) {
protected boolean isCandidateComponent(final @NotNull AnnotatedBeanDefinition beanDefinition) {
// By default, scanner does not accept regular interface without @Lookup method, bypass this
return true;
}
};
scanner.setEnvironment(environment);
scanner.setResourceLoader(resourceLoader);
scanner.setEnvironment(this.environment);
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(Repository.class));
List<String> basePackages = AutoConfigurationPackages.get(beanFactory);
final List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
basePackages.stream()
.map(scanner::findCandidateComponents)
.flatMap(Collection::stream)
.forEach(bd -> registerJdbiDaoBeanFactory(registry, bd));
.map(scanner::findCandidateComponents)
.flatMap(Collection::stream)
.forEach(bd -> this.registerJdbiDaoBeanFactory(registry, bd));
}
private void registerJdbiDaoBeanFactory(BeanDefinitionRegistry registry, BeanDefinition bd) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd;
Class<?> jdbiDaoClass;
private void registerJdbiDaoBeanFactory(final BeanDefinitionRegistry registry, final BeanDefinition bd) {
final GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd;
final Class<?> jdbiDaoClass;
try {
jdbiDaoClass = beanDefinition.resolveBeanClass(classLoader);
} catch (ClassNotFoundException e) {
jdbiDaoClass = beanDefinition.resolveBeanClass(this.classLoader);
} catch (final ClassNotFoundException e) {
throw new FatalBeanException(beanDefinition.getBeanClassName() + " not found on classpath", e);
}
beanDefinition.setBeanClass(JdbiDaoBeanFactory.class);

View File

@ -11,23 +11,23 @@ public class JdbiDaoBeanFactory implements FactoryBean<Object>, InitializingBean
private final Class<?> jdbiDaoClass;
private volatile Object jdbiDaoBean;
public JdbiDaoBeanFactory(Jdbi jdbi, Class<?> jdbiDaoClass) {
public JdbiDaoBeanFactory(final Jdbi jdbi, final Class<?> jdbiDaoClass) {
this.jdbi = jdbi;
this.jdbiDaoClass = jdbiDaoClass;
}
@Override
public Object getObject() throws Exception {
return jdbiDaoBean;
return this.jdbiDaoBean;
}
@Override
public Class<?> getObjectType() {
return jdbiDaoClass;
return this.jdbiDaoClass;
}
@Override
public void afterPropertiesSet() throws Exception {
jdbiDaoBean = jdbi.onDemand(jdbiDaoClass);
this.jdbiDaoBean = this.jdbi.onDemand(this.jdbiDaoClass);
}
}

View File

@ -5,6 +5,11 @@ import io.papermc.hangar.db.customtypes.JSONB;
import io.papermc.hangar.db.customtypes.JobState;
import io.papermc.hangar.db.customtypes.PGLoggedAction;
import io.papermc.hangar.db.customtypes.RoleCategory;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.mapper.ColumnMapper;
import org.jdbi.v3.core.mapper.RowMapper;
@ -21,12 +26,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
@Configuration
public class JDBIConfig {
@ -41,28 +40,29 @@ public class JDBIConfig {
}
@Bean
DataSource dataSource(@Value("${spring.datasource.url}") String url, @Value("${spring.datasource.username}") String username, @Value("${spring.datasource.password}") String password) {
HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class).url(url).username(username).password(password).build();
DataSource dataSource(@Value("${spring.datasource.url}") final String url, @Value("${spring.datasource.username}") final String username, @Value("${spring.datasource.password}") final String password) {
final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class).url(url).username(username).password(password).build();
dataSource.setPoolName(UUID.randomUUID().toString());
return dataSource;
}
@Bean
public Jdbi jdbi(DataSource dataSource, List<JdbiPlugin> jdbiPlugins, List<RowMapper<?>> rowMappers, List<RowMapperFactory> rowMapperFactories, List<ColumnMapper<?>> columnMappers) {
SqlLogger myLogger = new SqlLogger() {
public Jdbi jdbi(final DataSource dataSource, final List<JdbiPlugin> jdbiPlugins, final List<RowMapper<?>> rowMappers, final List<RowMapperFactory> rowMapperFactories, final List<ColumnMapper<?>> columnMappers) {
final SqlLogger myLogger = new SqlLogger() {
@Override
public void logException(StatementContext context, SQLException ex) {
public void logException(final StatementContext context, final SQLException ex) {
Logger.getLogger("sql").info("sql: " + context.getRenderedSql());
}
@Override
public void logAfterExecution(StatementContext context) {
public void logAfterExecution(final StatementContext context) {
Logger.getLogger("sql").info("sql ae: " + context.getRenderedSql());
}
};
TransactionAwareDataSourceProxy dataSourceProxy = new TransactionAwareDataSourceProxy(dataSource);
Jdbi jdbi = Jdbi.create(dataSourceProxy);
//jdbi.setSqlLogger(myLogger); // for debugging sql statements
PostgresTypes config = jdbi.getConfig(PostgresTypes.class);
final TransactionAwareDataSourceProxy dataSourceProxy = new TransactionAwareDataSourceProxy(dataSource);
final Jdbi jdbi = Jdbi.create(dataSourceProxy);
// jdbi.setSqlLogger(myLogger); // for debugging sql statements
final PostgresTypes config = jdbi.getConfig(PostgresTypes.class);
jdbiPlugins.forEach(jdbi::installPlugin);
rowMappers.forEach(jdbi::registerRowMapper);

View File

@ -1,11 +1,9 @@
package io.papermc.hangar.config;
import org.springframework.beans.factory.InitializingBean;
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 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;
@ -14,13 +12,12 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
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 org.springframework.beans.factory.InitializingBean;
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;
@ -50,46 +47,46 @@ 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();
.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();
}
@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(apiInfo());
.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());
}
@Bean
public CustomScanner customScanner(FilterRegistry filterRegistry) {
public CustomScanner customScanner(final FilterRegistry filterRegistry) {
return new CustomScanner(filterRegistry);
}
@ -97,66 +94,66 @@ public class SwaggerConfig {
private final FilterRegistry filterRegistry;
public CustomScanner(FilterRegistry filterRegistry) {
public CustomScanner(final FilterRegistry filterRegistry) {
this.filterRegistry = filterRegistry;
}
@Override
public void apply(OperationContext context) {
Optional<ApplicableSorters> sorters = context.findAnnotation(ApplicableSorters.class);
public void apply(final OperationContext context) {
final Optional<ApplicableSorters> sorters = context.findAnnotation(ApplicableSorters.class);
if (sorters.isPresent()) {
Set<RequestParameter> requestParameters = context.operationBuilder().build().getRequestParameters();
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());
.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);
}
Optional<ApplicableFilters> filters = context.findAnnotation(ApplicableFilters.class);
final Optional<ApplicableFilters> filters = context.findAnnotation(ApplicableFilters.class);
if (filters.isPresent()) {
Set<RequestParameter> requestParameters = context.operationBuilder().build().getRequestParameters();
final Set<RequestParameter> requestParameters = context.operationBuilder().build().getRequestParameters();
for (var clazz : filters.get().value()) {
var filter = filterRegistry.get(clazz);
for (final var clazz : filters.get().value()) {
final var filter = this.filterRegistry.get(clazz);
requestParameters.add(new RequestParameterBuilder()
.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());
.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());
}
context.operationBuilder().requestParameters(requestParameters);
}
}
@Override
public boolean supports(DocumentationType documentationType) {
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(DocumentationPluginsBootstrapper bootstrapper) {
public InitializingBean removeSpringfoxHandlerProvider(final DocumentationPluginsBootstrapper bootstrapper) {
return () -> bootstrapper.getHandlerProviders().removeIf(WebMvcRequestHandlerProvider.class::isInstance);
}
@Bean
public RequestHandlerProvider customRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver, List<RequestMappingInfoHandlerMapping> handlerMappings) {
String contextPath = servletContext.map(ServletContext::getContextPath).orElse(ROOT);
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, tweakInfo(entry.getKey()), entry.getValue()))
.map(entry -> new WebMvcRequestHandler(contextPath, methodResolver, this.tweakInfo(entry.getKey()), entry.getValue()))
.sorted(byPatternsCondition())
.collect(toList());
}
RequestMappingInfo tweakInfo(RequestMappingInfo info) {
RequestMappingInfo tweakInfo(final RequestMappingInfo info) {
if (info.getPathPatternsCondition() == null) return info;
String[] patterns = info.getPathPatternsCondition().getPatternValues().toArray(String[]::new);
final String[] patterns = info.getPathPatternsCondition().getPatternValues().toArray(String[]::new);
return info.mutate().options(new RequestMappingInfo.BuilderConfiguration()).paths(patterns).build();
}
}

View File

@ -3,19 +3,30 @@ package io.papermc.hangar.config;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesAnnotationIntrospector;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.config.jackson.HangarAnnotationIntrospector;
import io.papermc.hangar.security.annotations.ratelimit.RateLimitInterceptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
@ -38,40 +49,21 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupp
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
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.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.config.jackson.HangarAnnotationIntrospector;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
private static Logger interceptorLogger = LoggerFactory.getLogger(LoggingInterceptor.class); // NO-SONAR
private static final Logger interceptorLogger = LoggerFactory.getLogger(LoggingInterceptor.class); // NO-SONAR
private final HangarConfig hangarConfig;
private final ObjectMapper mapper;
private final RateLimitInterceptor rateLimitInterceptor;
private final List<Converter<?,?>> converters;
private final List<ConverterFactory<?,?>> converterFactories;
private final List<Converter<?, ?>> converters;
private final List<ConverterFactory<?, ?>> converterFactories;
private final List<HandlerMethodArgumentResolver> resolvers;
@Autowired
public WebConfig(HangarConfig hangarConfig, ObjectMapper mapper, RateLimitInterceptor rateLimitInterceptor, List<Converter<?, ?>> converters, List<ConverterFactory<?, ?>> converterFactories, List<HandlerMethodArgumentResolver> resolvers) {
public WebConfig(final HangarConfig hangarConfig, final ObjectMapper mapper, final RateLimitInterceptor rateLimitInterceptor, final List<Converter<?, ?>> converters, final List<ConverterFactory<?, ?>> converterFactories, final List<HandlerMethodArgumentResolver> resolvers) {
this.hangarConfig = hangarConfig;
this.mapper = mapper;
this.rateLimitInterceptor = rateLimitInterceptor;
@ -81,17 +73,17 @@ public class WebConfig extends WebMvcConfigurationSupport {
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(this.rateLimitInterceptor).addPathPatterns("/**");
}
@Override
protected void addCorsMappings(CorsRegistry registry) {
CorsRegistration corsRegistration = registry.addMapping("/api/internal/**");
if (hangarConfig.isDev()) {
protected void addCorsMappings(final CorsRegistry registry) {
final CorsRegistration corsRegistration = registry.addMapping("/api/internal/**");
if (this.hangarConfig.isDev()) {
corsRegistration.allowedOrigins("http://localhost:3333");
} else {
corsRegistration.allowedOrigins(hangarConfig.getBaseUrl());
corsRegistration.allowedOrigins(this.hangarConfig.getBaseUrl());
}
corsRegistration.allowedMethods("GET", "HEAD", "POST", "DELETE");
}
@ -110,7 +102,7 @@ public class WebConfig extends WebMvcConfigurationSupport {
public Filter identifyFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Server", "Hangar");
filterChain.doFilter(request, response);
}
@ -118,53 +110,52 @@ public class WebConfig extends WebMvcConfigurationSupport {
}
@Override
protected void addFormatters(FormatterRegistry registry) {
converters.forEach(registry::addConverter);
converterFactories.forEach(registry::addConverterFactory);
protected void addFormatters(final FormatterRegistry registry) {
this.converters.forEach(registry::addConverter);
this.converterFactories.forEach(registry::addConverterFactory);
}
@Override
public void configureMessageConverters(@NotNull List<HttpMessageConverter<?>> converters) {
public void configureMessageConverters(final @NotNull List<HttpMessageConverter<?>> converters) {
// TODO kinda wack, but idk a better way rn
ParameterNamesAnnotationIntrospector sAnnotationIntrospector = (ParameterNamesAnnotationIntrospector) mapper.getSerializationConfig().getAnnotationIntrospector().allIntrospectors().stream().filter(ParameterNamesAnnotationIntrospector.class::isInstance).findFirst().orElseThrow();
mapper.setAnnotationIntrospectors(
AnnotationIntrospector.pair(sAnnotationIntrospector, new HangarAnnotationIntrospector()),
mapper.getDeserializationConfig().getAnnotationIntrospector()
final ParameterNamesAnnotationIntrospector sAnnotationIntrospector = (ParameterNamesAnnotationIntrospector) this.mapper.getSerializationConfig().getAnnotationIntrospector().allIntrospectors().stream().filter(ParameterNamesAnnotationIntrospector.class::isInstance).findFirst().orElseThrow();
this.mapper.setAnnotationIntrospectors(
AnnotationIntrospector.pair(sAnnotationIntrospector, new HangarAnnotationIntrospector()),
this.mapper.getDeserializationConfig().getAnnotationIntrospector()
);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
super.addDefaultHttpMessageConverters(converters);
converters.add(new MappingJackson2HttpMessageConverter(this.mapper));
this.addDefaultHttpMessageConverters(converters);
}
@NotNull
@Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
protected @NotNull RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new RequestMappingHandlerAdapter() {
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
List<HandlerMethodArgumentResolver> existingResolvers = new ArrayList<>(Objects.requireNonNull(getArgumentResolvers()));
existingResolvers.addAll(0, resolvers);
final List<HandlerMethodArgumentResolver> existingResolvers = new ArrayList<>(Objects.requireNonNull(this.getArgumentResolvers()));
existingResolvers.addAll(0, WebConfig.this.resolvers);
this.setArgumentResolvers(existingResolvers);
}
};
}
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper mapper) {
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(final ObjectMapper mapper) {
return new MappingJackson2HttpMessageConverter(mapper);
}
@Bean
public RestTemplate restTemplate(List<HttpMessageConverter<?>> messageConverters) {
RestTemplate restTemplate;
public RestTemplate restTemplate(final List<HttpMessageConverter<?>> messageConverters) {
final RestTemplate restTemplate;
if (interceptorLogger.isDebugEnabled()) {
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
final ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
restTemplate = new RestTemplate(factory);
restTemplate.setInterceptors(List.of(new LoggingInterceptor()));
} else {
restTemplate = new RestTemplate();
}
super.addDefaultHttpMessageConverters(messageConverters);
this.addDefaultHttpMessageConverters(messageConverters);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
@ -172,17 +163,17 @@ public class WebConfig extends WebMvcConfigurationSupport {
static class LoggingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
public ClientHttpResponse intercept(final HttpRequest req, final byte[] reqBody, final ClientHttpRequestExecution ex) throws IOException {
if (interceptorLogger.isDebugEnabled()) {
interceptorLogger.debug("Request {}, body {}, headers {}", req.getMethod() + " " + req.getURI(), new String(reqBody, StandardCharsets.UTF_8), req.getHeaders());
}
ClientHttpResponse response = ex.execute(req, reqBody);
final ClientHttpResponse response = ex.execute(req, reqBody);
if (interceptorLogger.isDebugEnabled()) {
int code = response.getRawStatusCode();
HttpStatus status = HttpStatus.resolve(code);
final int code = response.getRawStatusCode();
final HttpStatus status = HttpStatus.resolve(code);
InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr).lines().collect(Collectors.joining("\n"));
final InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
final String body = new BufferedReader(isr).lines().collect(Collectors.joining("\n"));
interceptorLogger.debug("Response {}, body {}, headers {}", (status != null ? status : code), body, response.getHeaders());
}

View File

@ -10,8 +10,9 @@ import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties(prefix = "hangar.api")
public record ApiConfig(@NestedConfigurationProperty Session session) { // TODO is this used anywhere now?
// TODO is this used anywhere now?
@ConfigurationProperties(prefix = "hangar.api.session")
public record Session( // TODO is this used anywhere now?
public record Session(
@DurationUnit(ChronoUnit.HOURS) @DefaultValue("3") Duration publicExpiration,
@DurationUnit(ChronoUnit.DAYS) @DefaultValue("14") Duration expiration
) {

View File

@ -59,32 +59,32 @@ public class HangarConfig {
private String link;
public String getName() {
return name;
return this.name;
}
public void setName(String name) {
public void setName(final String name) {
this.name = name;
}
public String getImage() {
return image;
return this.image;
}
public void setImage(String image) {
public void setImage(final String image) {
this.image = image;
}
public String getLink() {
return link;
return this.link;
}
public void setLink(String link) {
public void setLink(final String link) {
this.link = link;
}
}
@Autowired
public HangarConfig(FakeUserConfig fakeUser, HomepageConfig homepage, ChannelsConfig channels, PagesConfig pages, ProjectsConfig projects, UserConfig user, OrganizationsConfig org, ApiConfig api, SSOConfig sso, HangarSecurityConfig security, QueueConfig queue, DiscourseConfig discourse, JobsConfig jobs, StorageConfig storage) {
public HangarConfig(final FakeUserConfig fakeUser, final HomepageConfig homepage, final ChannelsConfig channels, final PagesConfig pages, final ProjectsConfig projects, final UserConfig user, final OrganizationsConfig org, final ApiConfig api, final SSOConfig sso, final HangarSecurityConfig security, final QueueConfig queue, final DiscourseConfig discourse, final JobsConfig jobs, final StorageConfig storage) {
this.fakeUser = fakeUser;
this.homepage = homepage;
this.channels = channels;
@ -108,66 +108,66 @@ public class HangarConfig {
}
public String getLogo() {
return logo;
return this.logo;
}
public void setLogo(String logo) {
public void setLogo(final String logo) {
this.logo = logo;
}
public List<Sponsor> getSponsors() {
return sponsors;
return this.sponsors;
}
public void setSponsors(List<Sponsor> sponsors) {
public void setSponsors(final List<Sponsor> sponsors) {
this.sponsors = sponsors;
}
public List<Announcement> getAnnouncements() {
return announcements;
return this.announcements;
}
public void setAnnouncements(List<Announcement> announcements) {
public void setAnnouncements(final List<Announcement> announcements) {
this.announcements = announcements;
}
public boolean isDev() {
return dev;
return this.dev;
}
public void setDev(boolean dev) {
public void setDev(final boolean dev) {
this.dev = dev;
}
public String getBaseUrl() {
return baseUrl;
return this.baseUrl;
}
public void setBaseUrl(String baseUrl) {
public void setBaseUrl(final String baseUrl) {
this.baseUrl = baseUrl;
}
public String getGaCode() {
return gaCode;
return this.gaCode;
}
public void setGaCode(String gaCode) {
public void setGaCode(final String gaCode) {
this.gaCode = gaCode;
}
public String getUrlRegex() {
return urlRegex;
return this.urlRegex;
}
public void setUrlRegex(String urlRegex) {
public void setUrlRegex(final String urlRegex) {
this.urlRegex = urlRegex;
}
public List<String> getLicenses() {
return licenses;
return this.licenses;
}
public void setLicenses(List<String> licenses) {
public void setLicenses(final List<String> licenses) {
this.licenses = licenses;
}

View File

@ -7,12 +7,10 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "hangar.security")
public record HangarSecurityConfig(

View File

@ -10,27 +10,27 @@ import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties(prefix = "hangar.projects")
public record ProjectsConfig( // TODO split into ProjectsConfig and VersionsConfig
@DefaultValue("^[a-zA-Z0-9-_]{3,}$") PatternWrapper nameRegex,
@DefaultValue("^[a-zA-Z0-9-_.+]+$") PatternWrapper versionNameRegex,
@DefaultValue("25") int maxNameLen,
@DefaultValue("30") int maxVersionNameLen,
@DefaultValue("100") int maxDependencies,
@DefaultValue("50") int maxPages,
@DefaultValue("5") int maxChannels,
@DefaultValue("30000") int maxBBCodeLen,
@DefaultValue("25") int initLoad,
@DefaultValue("10") int initVersionLoad,
@DefaultValue("120") int maxDescLen,
@DefaultValue("500") int maxSponsorsLen,
@DefaultValue("5") int maxKeywords,
@DefaultValue("1000000") int contentMaxLen,
@DefaultValue("true") boolean fileValidate, // TODO implement or remove
@DefaultValue("28") @DurationUnit(ChronoUnit.DAYS) Duration staleAge,
@DefaultValue("1h") String checkInterval, // TODO implement or remove
@DefaultValue("1d") String draftExpire, // TODO implement or remove
@DefaultValue("30") int userGridPageSize, // TODO implement or remove
@DefaultValue("10") @DurationUnit(ChronoUnit.MINUTES) Duration unsafeDownloadMaxAge,
@DefaultValue("false") boolean showUnreviewedDownloadWarning
@DefaultValue("^[a-zA-Z0-9-_]{3,}$") PatternWrapper nameRegex,
@DefaultValue("^[a-zA-Z0-9-_.+]+$") PatternWrapper versionNameRegex,
@DefaultValue("25") int maxNameLen,
@DefaultValue("30") int maxVersionNameLen,
@DefaultValue("100") int maxDependencies,
@DefaultValue("50") int maxPages,
@DefaultValue("5") int maxChannels,
@DefaultValue("30000") int maxBBCodeLen,
@DefaultValue("25") int initLoad,
@DefaultValue("10") int initVersionLoad,
@DefaultValue("120") int maxDescLen,
@DefaultValue("500") int maxSponsorsLen,
@DefaultValue("5") int maxKeywords,
@DefaultValue("1000000") int contentMaxLen,
@DefaultValue("true") boolean fileValidate, // TODO implement or remove
@DefaultValue("28") @DurationUnit(ChronoUnit.DAYS) Duration staleAge,
@DefaultValue("1h") String checkInterval, // TODO implement or remove
@DefaultValue("1d") String draftExpire, // TODO implement or remove
@DefaultValue("30") int userGridPageSize, // TODO implement or remove
@DefaultValue("10") @DurationUnit(ChronoUnit.MINUTES) Duration unsafeDownloadMaxAge,
@DefaultValue("false") boolean showUnreviewedDownloadWarning
) {
public Validation projectName() {

View File

@ -1,19 +1,16 @@
package io.papermc.hangar.config.hangar;
import io.awspring.cloud.autoconfigure.core.AwsProperties;
import io.papermc.hangar.HangarApplication;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.URISyntaxException;
@ConfigurationProperties(prefix = "hangar.storage")
public record StorageConfig(

View File

@ -5,27 +5,26 @@ import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.security.authentication.HangarAuthenticationToken;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Arrays;
public class HangarAnnotationIntrospector extends JacksonAnnotationIntrospector {
private static final Logger logger = LoggerFactory.getLogger(HangarAnnotationIntrospector.class);
@Override
public final boolean _isIgnorable(Annotated a) {
public final boolean _isIgnorable(final Annotated a) {
if (a.hasAnnotation(RequiresPermission.class)) {
logger.debug("Found {} annotation on {}", RequiresPermission.class, a.getName());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!authentication.isAuthenticated() || !(authentication instanceof HangarAuthenticationToken)) {
return true;
}
Permission perms = ((HangarAuthenticationToken) authentication).getPrincipal().getGlobalPermissions();
NamedPermission[] requiredPerms = a.getAnnotation(RequiresPermission.class).value();
final Permission perms = ((HangarAuthenticationToken) authentication).getPrincipal().getGlobalPermissions();
final NamedPermission[] requiredPerms = a.getAnnotation(RequiresPermission.class).value();
if (!perms.hasAll(requiredPerms)) {
return true;
}

View File

@ -1,7 +1,6 @@
package io.papermc.hangar.config.jackson;
import io.papermc.hangar.model.common.NamedPermission;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -4,17 +4,16 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class StringSanitizerModule extends SimpleModule {
public StringSanitizerModule() {
addDeserializer(String.class, new StdScalarDeserializer<>(String.class) {
this.addDeserializer(String.class, new StdScalarDeserializer<>(String.class) {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
public String deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
return StringUtils.trimToNull(p.getValueAsString());
}
});

View File

@ -1,13 +1,12 @@
package io.papermc.hangar.controller;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.controller.extras.RobotsBuilder;
import io.papermc.hangar.security.annotations.Anyone;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Anyone
@Controller
@ -17,38 +16,38 @@ public class ApplicationController extends HangarComponent {
@ResponseBody
public String robots() {
return new RobotsBuilder()
.group("*")
.disallow("/invalidate")
.disallow("/login")
.disallow("/logout")
.disallow("/handle-logout")
.disallow("/logged-out")
.disallow("/refresh")
.disallow("/signup")
.disallow("/verify")
.disallow("/linkout")
.disallow("/admin")
.disallow("/actuator")
.disallow("/error")
.disallow("/version-info")
.disallow("/api")
.allow("/api$")
.disallow("/notifications")
.disallow("/*/settings")
.disallow("/*/*/channels")
.disallow("/*/*/discuss")
.disallow("/*/*/flags")
.disallow("/*/*/notes")
.disallow("/*/*/settings")
.disallow("/*/*/stars")
.disallow("/*/*/watchers")
.disallow("/*/*/versions/new$")
.disallow("/*/*/versions/*/download")
.disallow("/*/*/versions/*/jar")
.disallow("/*/*/versions/*/confirm")
.disallow("/*/*/versions/*/*/reviews")
.endGroup()
.sitemap(config.getBaseUrl() + "/sitemap.xml")
.build();
.group("*")
.disallow("/invalidate")
.disallow("/login")
.disallow("/logout")
.disallow("/handle-logout")
.disallow("/logged-out")
.disallow("/refresh")
.disallow("/signup")
.disallow("/verify")
.disallow("/linkout")
.disallow("/admin")
.disallow("/actuator")
.disallow("/error")
.disallow("/version-info")
.disallow("/api")
.allow("/api$")
.disallow("/notifications")
.disallow("/*/settings")
.disallow("/*/*/channels")
.disallow("/*/*/discuss")
.disallow("/*/*/flags")
.disallow("/*/*/notes")
.disallow("/*/*/settings")
.disallow("/*/*/stars")
.disallow("/*/*/watchers")
.disallow("/*/*/versions/new$")
.disallow("/*/*/versions/*/download")
.disallow("/*/*/versions/*/jar")
.disallow("/*/*/versions/*/confirm")
.disallow("/*/*/versions/*/*/reviews")
.endGroup()
.sitemap(this.config.getBaseUrl() + "/sitemap.xml")
.build();
}
}

View File

@ -44,7 +44,7 @@ public class LoginController extends HangarComponent {
private final ValidationService validationService;
@Autowired
public LoginController(AuthenticationService authenticationService, SSOService ssoService, TokenService tokenService, ValidationService validationService) {
public LoginController(final AuthenticationService authenticationService, final SSOService ssoService, final TokenService tokenService, final ValidationService validationService) {
this.authenticationService = authenticationService;
this.ssoService = ssoService;
this.tokenService = tokenService;
@ -52,45 +52,45 @@ public class LoginController extends HangarComponent {
}
@GetMapping(path = "/login", params = "returnUrl")
public RedirectView loginFromFrontend(@RequestParam(defaultValue = "/") String returnUrl) {
if (config.fakeUser.enabled()) {
config.checkDev();
public RedirectView loginFromFrontend(@RequestParam(defaultValue = "/") final String returnUrl) {
if (this.config.fakeUser.enabled()) {
this.config.checkDev();
UserTable fakeUser = authenticationService.loginAsFakeUser();
tokenService.issueRefreshToken(fakeUser);
return addBaseAndRedirect(returnUrl);
final UserTable fakeUser = this.authenticationService.loginAsFakeUser();
this.tokenService.issueRefreshToken(fakeUser);
return this.addBaseAndRedirect(returnUrl);
} else {
response.addCookie(new Cookie("url", returnUrl));
return redirectToSso(ssoService.getLoginUrl(config.getBaseUrl() + "/login"));
this.response.addCookie(new Cookie("url", returnUrl));
return this.redirectToSso(this.ssoService.getLoginUrl(this.config.getBaseUrl() + "/login"));
}
}
@RateLimit(overdraft = 5, refillTokens = 5, refillSeconds = 10)
@GetMapping(path = "/login", params = {"code", "state"})
public RedirectView loginFromAuth(@RequestParam String code, @RequestParam String state, @CookieValue String url) {
UserTable user = ssoService.authenticate(code, state, config.getBaseUrl() + "/login");
public RedirectView loginFromAuth(@RequestParam final String code, @RequestParam final String state, @CookieValue final String url) {
final UserTable user = this.ssoService.authenticate(code, state, this.config.getBaseUrl() + "/login");
if (user == null) {
throw new HangarApiException("nav.user.error.loginFailed");
}
if (!validationService.isValidUsername(user.getName())) {
if (!this.validationService.isValidUsername(user.getName())) {
throw new HangarApiException("nav.user.error.invalidUsername");
}
tokenService.issueRefreshToken(user);
return addBaseAndRedirect(url);
this.tokenService.issueRefreshToken(user);
return this.addBaseAndRedirect(url);
}
@ResponseBody
@GetMapping("/refresh")
public String refreshAccessToken(@CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) String refreshToken) {
return tokenService.refreshAccessToken(refreshToken).accessToken();
public String refreshAccessToken(@CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) final String refreshToken) {
return this.tokenService.refreshAccessToken(refreshToken).accessToken();
}
@GetMapping("/invalidate")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void invalidateRefreshToken(@CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) String refreshToken) {
tokenService.invalidateToken(refreshToken);
HttpSession session = request.getSession(false);
public void invalidateRefreshToken(@CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) final String refreshToken) {
this.tokenService.invalidateToken(refreshToken);
final HttpSession session = this.request.getSession(false);
if (session != null) {
session.invalidate();
}
@ -98,109 +98,109 @@ public class LoginController extends HangarComponent {
@ResponseBody
@GetMapping(path = "/logout", params = "returnUrl")
public String logout(@RequestParam(defaultValue = "/?loggedOut") String returnUrl) {
if (config.fakeUser.enabled()) {
response.addCookie(new Cookie("url", returnUrl));
public String logout(@RequestParam(defaultValue = "/?loggedOut") final String returnUrl) {
if (this.config.fakeUser.enabled()) {
this.response.addCookie(new Cookie("url", returnUrl));
return "/fake-logout";
} else {
response.addCookie(new Cookie("url", returnUrl));
Optional<HangarPrincipal> principal = getOptionalHangarPrincipal();
this.response.addCookie(new Cookie("url", returnUrl));
final Optional<HangarPrincipal> principal = this.getOptionalHangarPrincipal();
if (principal.isPresent()) {
return ssoService.getLogoutUrl(config.getBaseUrl() + "/handle-logout", principal.get()).getUrl();
return this.ssoService.getLogoutUrl(this.config.getBaseUrl() + "/handle-logout", principal.get()).getUrl();
} else {
tokenService.invalidateToken(null);
return addBase(returnUrl);
this.tokenService.invalidateToken(null);
return this.addBase(returnUrl);
}
}
}
@GetMapping(path = "/fake-logout")
public RedirectView fakeLogout(@CookieValue(value = "url", defaultValue = "/logged-out") String returnUrl, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) String refreshToken) {
public RedirectView fakeLogout(@CookieValue(value = "url", defaultValue = "/logged-out") final String returnUrl, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) final String refreshToken) {
// invalidate refresh token
if (refreshToken != null) {
tokenService.invalidateToken(refreshToken);
this.tokenService.invalidateToken(refreshToken);
}
// invalidate session
HttpSession session = request.getSession(false);
final HttpSession session = this.request.getSession(false);
if (session != null) {
session.invalidate();
}
return addBaseAndRedirect(returnUrl);
return this.addBaseAndRedirect(returnUrl);
}
@GetMapping(path = "/handle-logout", params = "state")
public RedirectView loggedOut(@RequestParam String state, @CookieValue(value = "url", defaultValue = "/logged-out") String returnUrl, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) String refreshToken) {
String username;
public RedirectView loggedOut(@RequestParam final String state, @CookieValue(value = "url", defaultValue = "/logged-out") final String returnUrl, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) final String refreshToken) {
final String username;
try {
// get username
DecodedJWT decodedJWT = tokenService.verify(state);
final DecodedJWT decodedJWT = this.tokenService.verify(state);
username = decodedJWT.getSubject();
} catch (JWTVerificationException e) {
} catch (final JWTVerificationException e) {
throw new HangarApiException("nav.user.error.logoutFailed", e.getMessage());
}
// invalidate id token
ssoService.logout(username);
this.ssoService.logout(username);
// invalidate refresh token
if (refreshToken != null) {
tokenService.invalidateToken(refreshToken);
this.tokenService.invalidateToken(refreshToken);
}
// invalidate session
HttpSession session = request.getSession(false);
final HttpSession session = this.request.getSession(false);
if (session != null) {
session.invalidate();
}
return addBaseAndRedirect(returnUrl);
return this.addBaseAndRedirect(returnUrl);
}
@GetMapping("/signup")
public RedirectView signUp(@RequestParam(defaultValue = "") String returnUrl) {
if (config.fakeUser.enabled()) {
public RedirectView signUp(@RequestParam(defaultValue = "") final String returnUrl) {
if (this.config.fakeUser.enabled()) {
throw new HangarApiException("nav.user.error.fakeUserEnabled", "Signup");
}
return new RedirectView(ssoService.getSignupUrl(returnUrl));
return new RedirectView(this.ssoService.getSignupUrl(returnUrl));
}
@PostMapping("/sync")
@RateLimit(overdraft = 5, refillTokens = 2, refillSeconds = 10)
public ResponseEntity sync(@NotNull @RequestBody SsoSyncData body, @RequestHeader("X-Kratos-Hook-Api-Key") String apiKey) {
if (!apiKey.equals(config.sso.kratosApiKey())) {
public ResponseEntity sync(@RequestBody final @NotNull SsoSyncData body, @RequestHeader("X-Kratos-Hook-Api-Key") final String apiKey) {
if (!apiKey.equals(this.config.sso.kratosApiKey())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
if (body.state().equals("active")) {
logger.debug("Syncing {}'s new traits: {}", body.id(), body.traits());
this.logger.debug("Syncing {}'s new traits: {}", body.id(), body.traits());
try {
ssoService.sync(body.id(), body.traits());
} catch (WebHookException ex) {
this.ssoService.sync(body.id(), body.traits());
} catch (final WebHookException ex) {
return ResponseEntity.badRequest().body(ex.getError());
}
} else {
logger.debug("Not syncing since its not active! {}", body);
this.logger.debug("Not syncing since its not active! {}", body);
}
return ResponseEntity.accepted().build();
}
private RedirectView addBaseAndRedirect(String url) {
return new RedirectView(addBase(url));
private RedirectView addBaseAndRedirect(final String url) {
return new RedirectView(this.addBase(url));
}
private String addBase(String url) {
if (!url.startsWith("http")) {
if (url.startsWith("/")) {
url = config.getBaseUrl() + url;
url = this.config.getBaseUrl() + url;
} else {
url = config.getBaseUrl() + "/" + url;
url = this.config.getBaseUrl() + "/" + url;
}
}
return url;
}
private RedirectView redirectToSso(URLWithNonce urlWithNonce) {
if (!config.sso.enabled()) {
private RedirectView redirectToSso(final URLWithNonce urlWithNonce) {
if (!this.config.sso.enabled()) {
throw new HangarApiException("nav.user.error.loginDisabled");
}
ssoService.insert(urlWithNonce.getNonce());
this.ssoService.insert(urlWithNonce.getNonce());
return new RedirectView(urlWithNonce.getUrl());
}
}

View File

@ -16,7 +16,7 @@ public class SitemapController extends HangarComponent {
private final SitemapService sitemapService;
@Autowired
public SitemapController(SitemapService sitemapService) {
public SitemapController(final SitemapService sitemapService) {
this.sitemapService = sitemapService;
}
@ -24,20 +24,20 @@ public class SitemapController extends HangarComponent {
@GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String sitemapIndex() {
return sitemapService.getSitemap();
return this.sitemapService.getSitemap();
}
@Anyone
@GetMapping(value = "/global-sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String globalSitemap() {
return sitemapService.getGlobalSitemap();
return this.sitemapService.getGlobalSitemap();
}
@Anyone
@GetMapping(value = "/{user}/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String userSitemap(@PathVariable String user) {
return sitemapService.getUserSitemap(user);
public String userSitemap(@PathVariable final String user) {
return this.sitemapService.getUserSitemap(user);
}
}

View File

@ -9,15 +9,13 @@ import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.service.APIKeyService;
import io.papermc.hangar.service.PermissionService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List;
@Controller
@RateLimit(path = "apikeys", greedy = true)
@PermissionRequired(NamedPermission.EDIT_API_KEYS)
@ -27,7 +25,7 @@ public class ApiKeysController extends HangarComponent implements IApiKeysContro
private final PermissionService permissionService;
@Autowired
public ApiKeysController(APIKeyService apiKeyService, PermissionService permissionService) {
public ApiKeysController(final APIKeyService apiKeyService, final PermissionService permissionService) {
this.apiKeyService = apiKeyService;
this.permissionService = permissionService;
}
@ -36,20 +34,20 @@ public class ApiKeysController extends HangarComponent implements IApiKeysContro
@ResponseBody
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 15)
@ResponseStatus(HttpStatus.CREATED)
public String createKey(CreateAPIKeyForm apiKeyForm) {
return apiKeyService.createApiKey(getHangarPrincipal(), apiKeyForm, permissionService.getAllPossiblePermissions(getHangarPrincipal().getUserId()));
public String createKey(final CreateAPIKeyForm apiKeyForm) {
return this.apiKeyService.createApiKey(this.getHangarPrincipal(), apiKeyForm, this.permissionService.getAllPossiblePermissions(this.getHangarPrincipal().getUserId()));
}
@Override
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public List<ApiKey> getKeys() {
return apiKeyService.getApiKeys(getHangarPrincipal().getUserId());
return this.apiKeyService.getApiKeys(this.getHangarPrincipal().getUserId());
}
@Override
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteKey(String name) {
apiKeyService.deleteApiKey(getHangarPrincipal(), name);
public void deleteKey(final String name) {
this.apiKeyService.deleteApiKey(this.getHangarPrincipal(), name);
}
}

View File

@ -17,13 +17,13 @@ public class AuthenticationController extends HangarComponent implements IAuthen
private final APIAuthenticationService apiAuthenticationService;
@Autowired
public AuthenticationController(APIAuthenticationService apiAuthenticationService) {
public AuthenticationController(final APIAuthenticationService apiAuthenticationService) {
this.apiAuthenticationService = apiAuthenticationService;
}
@Anyone
@Override
public ResponseEntity<ApiSession> authenticate(String apiKey) {
return ResponseEntity.ok(apiAuthenticationService.createJWTForApiKey(apiKey));
public ResponseEntity<ApiSession> authenticate(final String apiKey) {
return ResponseEntity.ok(this.apiAuthenticationService.createJWTForApiKey(apiKey));
}
}

View File

@ -11,6 +11,8 @@ import io.papermc.hangar.model.common.PermissionType;
import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.service.PermissionService;
import java.util.List;
import java.util.function.BiPredicate;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
@ -18,9 +20,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import java.util.List;
import java.util.function.BiPredicate;
@Anyone
@Controller
@RateLimit(path = "apipermissions", refillTokens = 100, greedy = true)
@ -29,46 +28,46 @@ public class PermissionsController extends HangarComponent implements IPermissio
private final PermissionService permissionService;
@Autowired
public PermissionsController(PermissionService permissionService) {
public PermissionsController(final PermissionService permissionService) {
this.permissionService = permissionService;
}
@Override
public ResponseEntity<PermissionCheck> hasAllPermissions(List<NamedPermission> permissions, String author, String slug, String organization) {
return has(permissions, author, slug, organization, (namedPerms, perm) -> namedPerms.stream().allMatch(p -> perm.has(p.getPermission())));
public ResponseEntity<PermissionCheck> hasAllPermissions(final List<NamedPermission> permissions, final String author, final String slug, final String organization) {
return this.has(permissions, author, slug, organization, (namedPerms, perm) -> namedPerms.stream().allMatch(p -> perm.has(p.getPermission())));
}
@Override
public ResponseEntity<PermissionCheck> hasAny(List<NamedPermission> permissions, String author, String slug, String organization) {
return has(permissions, author, slug, organization, (namedPerms, perm) -> namedPerms.stream().anyMatch(p -> perm.has(p.getPermission())));
public ResponseEntity<PermissionCheck> hasAny(final List<NamedPermission> permissions, final String author, final String slug, final String organization) {
return this.has(permissions, author, slug, organization, (namedPerms, perm) -> namedPerms.stream().anyMatch(p -> perm.has(p.getPermission())));
}
@Override
public ResponseEntity<UserPermissions> showPermissions(String author, String slug, String organization) {
Pair<PermissionType, Permission> scopedPerms = getPermissionsInScope(author, slug, organization);
public ResponseEntity<UserPermissions> showPermissions(final String author, final String slug, final String organization) {
final Pair<PermissionType, Permission> scopedPerms = this.getPermissionsInScope(author, slug, organization);
return ResponseEntity.ok(new UserPermissions(scopedPerms.getLeft(), scopedPerms.getRight().toBinString(), scopedPerms.getRight().toNamed()));
}
private Pair<PermissionType, Permission> getPermissionsInScope(String author, String slug, String organization) {
private Pair<PermissionType, Permission> getPermissionsInScope(final String author, final String slug, final String organization) {
if (author != null && slug != null && organization == null) { // author & slug
Permission perms = permissionService.getProjectPermissions(getHangarUserId(), author, slug);
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
Permission perms = this.permissionService.getProjectPermissions(this.getHangarUserId(), author, slug);
perms = this.getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.PROJECT, perms);
} else if (author == null && slug == null && organization == null) { // current user (I don't think there's a need to see other user's global permissions)
Permission perms = permissionService.getGlobalPermissions(getHangarUserId());
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
Permission perms = this.permissionService.getGlobalPermissions(this.getHangarUserId());
perms = this.getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.GLOBAL, perms);
} else if (author == null && slug == null) { // just org
Permission perms = permissionService.getOrganizationPermissions(getHangarUserId(), organization);
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
Permission perms = this.permissionService.getOrganizationPermissions(this.getHangarUserId(), organization);
perms = this.getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.ORGANIZATION, perms);
} else {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "Incorrect request parameters");
}
}
private ResponseEntity<PermissionCheck> has(List<NamedPermission> perms, String author, String slug, String organization, BiPredicate<List<NamedPermission>, Permission> check) {
Pair<PermissionType, Permission> scopedPerms = getPermissionsInScope(author, slug, organization);
private ResponseEntity<PermissionCheck> has(final List<NamedPermission> perms, final String author, final String slug, final String organization, final BiPredicate<List<NamedPermission>, Permission> check) {
final Pair<PermissionType, Permission> scopedPerms = this.getPermissionsInScope(author, slug, organization);
return ResponseEntity.ok(new PermissionCheck(scopedPerms.getLeft(), check.test(perms, scopedPerms.getRight())));
}
}

View File

@ -5,13 +5,17 @@ import io.papermc.hangar.controller.api.v1.interfaces.IProjectsController;
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 io.papermc.hangar.controller.extras.pagination.filters.projects.*;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectAuthorFilter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectCategoryFilter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectLicenseFilter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectMCVersionFilter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectPlatformFilter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectQueryFilter;
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.User;
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.project.ProjectSortingStrategy;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.common.PermissionType;
@ -19,16 +23,14 @@ import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.api.ProjectsApiService;
import java.time.OffsetDateTime;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import java.time.OffsetDateTime;
import java.util.Map;
@Anyone
@Controller
@RateLimit(path = "apiprojects", overdraft = 200, refillTokens = 50, greedy = true)
@ -37,44 +39,44 @@ public class ProjectsController extends HangarComponent implements IProjectsCont
private final ProjectsApiService projectsApiService;
@Autowired
public ProjectsController(ProjectsApiService projectsApiService) {
public ProjectsController(final ProjectsApiService projectsApiService) {
this.projectsApiService = projectsApiService;
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<Project> getProject(String author, String slug) {
return ResponseEntity.ok(projectsApiService.getProject(author, slug));
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<Project> getProject(final String author, final String slug) {
return ResponseEntity.ok(this.projectsApiService.getProject(author, slug));
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<ProjectMember>> getProjectMembers(String author, String slug, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(projectsApiService.getProjectMembers(author, slug, pagination));
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<ProjectMember>> getProjectMembers(final String author, final String slug, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.projectsApiService.getProjectMembers(author, slug, pagination));
}
@Override
@ApplicableFilters({ProjectCategoryFilter.class, ProjectPlatformFilter.class, ProjectAuthorFilter.class, ProjectQueryFilter.class, ProjectLicenseFilter.class, ProjectMCVersionFilter.class})
@ApplicableSorters({SorterRegistry.VIEWS, SorterRegistry.DOWNLOADS, SorterRegistry.NEWEST, SorterRegistry.STARS, SorterRegistry.UPDATED, SorterRegistry.RECENT_DOWNLOADS, SorterRegistry.RECENT_VIEWS})
public ResponseEntity<PaginatedResult<Project>> getProjects(String q, boolean orderWithRelevance, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(projectsApiService.getProjects(q, orderWithRelevance, pagination));
public ResponseEntity<PaginatedResult<Project>> getProjects(final String q, final boolean orderWithRelevance, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.projectsApiService.getProjects(q, orderWithRelevance, pagination));
}
@Override
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_MEMBER, args = "{#author, #slug}")
public ResponseEntity<Map<String, DayProjectStats>> getProjectStats(String author, String slug, @NotNull OffsetDateTime fromDate, @NotNull OffsetDateTime toDate) {
return ResponseEntity.ok(projectsApiService.getProjectStats(author, slug, fromDate, toDate));
public ResponseEntity<Map<String, DayProjectStats>> getProjectStats(final String author, final String slug, final @NotNull OffsetDateTime fromDate, final @NotNull OffsetDateTime toDate) {
return ResponseEntity.ok(this.projectsApiService.getProjectStats(author, slug, fromDate, toDate));
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<User>> getProjectStargazers(String author, String slug, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(projectsApiService.getProjectStargazers(author, slug, pagination));
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<User>> getProjectStargazers(final String author, final String slug, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.projectsApiService.getProjectStargazers(author, slug, pagination));
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<User>> getProjectWatchers(String author, String slug, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(projectsApiService.getProjectWatchers(author, slug, pagination));
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
public ResponseEntity<PaginatedResult<User>> getProjectWatchers(final String author, final String slug, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.projectsApiService.getProjectWatchers(author, slug, pagination));
}
}

View File

@ -12,13 +12,12 @@ import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.service.api.UsersApiService;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import java.util.List;
@Anyone
@Controller
@RateLimit(path = "apiusers", overdraft = 200, refillTokens = 50, greedy = true)
@ -27,45 +26,45 @@ public class UsersController extends HangarComponent implements IUsersController
private final UsersApiService usersApiService;
@Autowired
public UsersController(UsersApiService usersApiService) {
public UsersController(final UsersApiService usersApiService) {
this.usersApiService = usersApiService;
}
@Override
public ResponseEntity<User> getUser(String userName) {
return ResponseEntity.ok(usersApiService.getUser(userName, User.class));
public ResponseEntity<User> getUser(final String userName) {
return ResponseEntity.ok(this.usersApiService.getUser(userName, User.class));
}
@Override
@ApplicableSorters({SorterRegistry.USER_NAME, SorterRegistry.USER_JOIN_DATE, SorterRegistry.USER_PROJECT_COUNT, SorterRegistry.USER_LOCKED, SorterRegistry.USER_ORG, SorterRegistry.USER_ROLES})
public ResponseEntity<PaginatedResult<User>> getUsers(String query, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(usersApiService.getUsers(query, pagination, User.class));
public ResponseEntity<PaginatedResult<User>> getUsers(final String query, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.usersApiService.getUsers(query, pagination, User.class));
}
@Override
public ResponseEntity<PaginatedResult<ProjectCompact>> getUserStarred(String userName, ProjectSortingStrategy sort, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(usersApiService.getUserStarred(userName, sort, pagination));
public ResponseEntity<PaginatedResult<ProjectCompact>> getUserStarred(final String userName, final ProjectSortingStrategy sort, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.usersApiService.getUserStarred(userName, sort, pagination));
}
@Override
public ResponseEntity<PaginatedResult<ProjectCompact>> getUserWatching(String userName, ProjectSortingStrategy sort, @NotNull RequestPagination pagination) {
return ResponseEntity.ok(usersApiService.getUserWatching(userName, sort, pagination));
public ResponseEntity<PaginatedResult<ProjectCompact>> getUserWatching(final String userName, final ProjectSortingStrategy sort, final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.usersApiService.getUserWatching(userName, sort, pagination));
}
@Override
public ResponseEntity<List<ProjectCompact>> getUserPinnedProjects(final String userName) {
return ResponseEntity.ok(usersApiService.getUserPinned(userName));
return ResponseEntity.ok(this.usersApiService.getUserPinned(userName));
}
@Override
@ApplicableSorters({SorterRegistry.USER_NAME, SorterRegistry.USER_JOIN_DATE, SorterRegistry.USER_PROJECT_COUNT})
public ResponseEntity<PaginatedResult<User>> getAuthors(@NotNull RequestPagination pagination) {
return ResponseEntity.ok(usersApiService.getAuthors(pagination));
public ResponseEntity<PaginatedResult<User>> getAuthors(final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.usersApiService.getAuthors(pagination));
}
@Override
@ApplicableSorters({SorterRegistry.USER_NAME, SorterRegistry.USER_JOIN_DATE, SorterRegistry.USER_ROLES})
public ResponseEntity<PaginatedResult<User>> getStaff(@NotNull RequestPagination pagination) {
return ResponseEntity.ok(usersApiService.getStaff(pagination));
public ResponseEntity<PaginatedResult<User>> getStaff(final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.usersApiService.getStaff(pagination));
}
}

View File

@ -17,7 +17,6 @@ import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.api.VersionsApiService;
import io.papermc.hangar.service.internal.versions.DownloadService;
import java.time.OffsetDateTime;
@ -38,34 +37,34 @@ public class VersionsController implements IVersionsController {
private final VersionsApiService versionsApiService;
@Autowired
public VersionsController(DownloadService downloadService, VersionsApiService versionsApiService) {
public VersionsController(final DownloadService downloadService, final VersionsApiService versionsApiService) {
this.downloadService = downloadService;
this.versionsApiService = versionsApiService;
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
public Version getVersion(String author, String slug, String name) {
return versionsApiService.getVersion(author, slug, name);
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
public Version getVersion(final String author, final String slug, final String name) {
return this.versionsApiService.getVersion(author, slug, name);
}
@Override
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@ApplicableFilters({VersionChannelFilter.class, VersionPlatformFilter.class, VersionTagFilter.class})
public PaginatedResult<Version> getVersions(String author, String slug, @NotNull @ConfigurePagination(defaultLimitString = "@hangarConfig.projects.initVersionLoad", maxLimit = 25) RequestPagination pagination) {
return versionsApiService.getVersions(author, slug, pagination);
public PaginatedResult<Version> getVersions(final String author, final String slug, @ConfigurePagination(defaultLimitString = "@hangarConfig.projects.initVersionLoad", maxLimit = 25) final @NotNull RequestPagination pagination) {
return this.versionsApiService.getVersions(author, slug, pagination);
}
@Override
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_MEMBER, args = "{#author, #slug}")
public Map<String, VersionStats> getVersionStats(String author, String slug, String versionString, Platform platform, @NotNull OffsetDateTime fromDate, @NotNull OffsetDateTime toDate) {
return versionsApiService.getVersionStats(author, slug, versionString, platform, fromDate, toDate);
public Map<String, VersionStats> getVersionStats(final String author, final String slug, final String versionString, final Platform platform, final @NotNull OffsetDateTime fromDate, final @NotNull OffsetDateTime toDate) {
return this.versionsApiService.getVersionStats(author, slug, versionString, platform, fromDate, toDate);
}
@Override
@RateLimit(overdraft = 10, refillTokens = 2)
@VisibilityRequired(type = Type.VERSION, args = "{#author, #slug, #versionString, #platform}")
public Resource downloadVersion(String author, String slug, String versionString, Platform platform) {
return downloadService.getVersionFile(author, slug, versionString, platform, false, null);
@VisibilityRequired(type = VisibilityRequired.Type.VERSION, args = "{#author, #slug, #versionString, #platform}")
public Resource downloadVersion(final String author, final String slug, final String versionString, final Platform platform) {
return this.downloadService.getVersionFile(author, slug, versionString, platform, false, null);
}
}

View File

@ -8,6 +8,8 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
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;
@ -16,55 +18,52 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import javax.validation.Valid;
@Api(tags = "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"),
tags = "API Keys"
value = "Creates an API key",
nickname = "createKey",
notes = "Creates an API key. Requires the `edit_api_keys` permission.",
response = String.class,
authorizations = @Authorization("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(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")})
@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);
@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"),
tags = "API Keys"
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"),
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(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")})
@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"),
tags = "API Keys"
value = "Deletes an API key",
nickname = "deleteKey",
notes = "Deletes an API key. Requires the `edit_api_keys` permission.",
authorizations = @Authorization("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(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")
})
@DeleteMapping(value = "/keys")
@DeleteMapping("/keys")
void deleteKey(@ApiParam(value = "The name of the key to delete", required = true) @RequestParam String name);
}

View File

@ -18,16 +18,16 @@ import org.springframework.web.bind.annotation.RequestParam;
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"),
tags = "Authentication"
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"),
tags = "Authentication"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api key missing or invalid")
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api key missing or invalid")
})
@PostMapping("/authenticate")
ResponseEntity<ApiSession> authenticate(@ApiParam("JWT") @RequestParam String apiKey);

View File

@ -9,6 +9,7 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@ -16,27 +17,25 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Api(tags = "Permissions", produces = MediaType.APPLICATION_JSON_VALUE)
@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"),
tags = "Permissions"
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"),
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(code = 200, message = "Ok", response = PermissionCheck.class),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
})
@GetMapping(value = "/permissions/hasAll")
@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,
@ -44,16 +43,16 @@ public interface IPermissionsController {
);
@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"),
tags = "Permissions"
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"),
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(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
})
@GetMapping("/permissions/hasAny")
ResponseEntity<PermissionCheck> hasAny(@ApiParam(value = "The permissions to check", required = true) @RequestParam List<NamedPermission> permissions,
@ -63,22 +62,22 @@ public interface IPermissionsController {
);
@ApiOperation(
value = "Returns your permissions",
nickname = "showPermissions",
notes = "Returns a list of permissions you have in the given context",
authorizations = @Authorization("Session"),
tags = "Permissions"
value = "Returns your permissions",
nickname = "showPermissions",
notes = "Returns a list of permissions you have in the given context",
authorizations = @Authorization("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(code = 200, message = "Ok"),
@ApiResponse(code = 400, message = "Bad Request"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired")
})
@GetMapping(value = "/permissions",
produces = MediaType.APPLICATION_JSON_VALUE)
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
@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
);
}

View File

@ -5,7 +5,6 @@ import io.papermc.hangar.model.api.User;
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.project.ProjectSortingStrategy;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -13,6 +12,8 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import java.time.OffsetDateTime;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -22,78 +23,75 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.OffsetDateTime;
import java.util.Map;
@Api(tags = "Projects", produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(path ="/api/v1", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
@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"),
tags = "Projects"
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"),
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(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")
})
@GetMapping( "/projects/{author}/{slug}")
@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);
@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"),
tags = "Projects"
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"),
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(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")
})
@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
@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
);
@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"),
tags = "Projects"
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"),
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(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")
})
@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
@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
);
@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"),
tags = "Projects"
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"),
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(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")
})
@GetMapping("/projects/{author}/{slug}/stats")
ResponseEntity<Map<String, DayProjectStats>> getProjectStats(@ApiParam("The author of the project to return stats for") @PathVariable String author,
@ -103,40 +101,40 @@ public interface IProjectsController {
);
@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"),
tags = "Projects"
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"),
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(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")
})
@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
@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
);
@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"),
tags = "Projects"
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"),
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(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")
})
@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
@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
);
}

View File

@ -11,6 +11,7 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -19,54 +20,53 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Api(tags = "Users", produces = MediaType.APPLICATION_JSON_VALUE)
@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"),
tags = "Users"
value = "Returns a specific user",
nickname = "getUser",
notes = "Returns a specific user. Requires the `view_public_info` permission.",
authorizations = @Authorization("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(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")
})
@GetMapping("/users/{user}")
ResponseEntity<User> getUser(@ApiParam("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"),
tags = "Users"
value = "Searches for users",
nickname = "showUsers",
notes = "Returns a list of users based on a search query",
authorizations = @Authorization("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(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")
})
@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);
@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"),
tags = "Users"
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"),
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(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")
})
@GetMapping("/users/{user}/starred")
ResponseEntity<PaginatedResult<ProjectCompact>> getUserStarred(@ApiParam("The user to return starred projects for") @PathVariable("user") String userName,
@ -74,16 +74,16 @@ public interface IUsersController {
@ApiParam("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"),
tags = "Users"
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"),
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(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")
})
@GetMapping("/users/{user}/watching")
ResponseEntity<PaginatedResult<ProjectCompact>> getUserWatching(@ApiParam("The user to return watched projects for") @PathVariable("user") String userName,
@ -106,31 +106,31 @@ public interface IUsersController {
ResponseEntity<List<ProjectCompact>> getUserPinnedProjects(@ApiParam("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"),
tags = "Users"
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"),
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(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")
})
@GetMapping("/authors")
ResponseEntity<PaginatedResult<User>> getAuthors(@ApiParam("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"),
tags = "Users"
value = "Returns Hangar staff",
nickname = "getStaff",
notes = "Returns Hanagr staff. Requires the `view_public_info` permission.",
authorizations = @Authorization("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(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")
})
@GetMapping("/staff")
ResponseEntity<PaginatedResult<User>> getStaff(@ApiParam("Pagination information") @NotNull RequestPagination pagination);

View File

@ -11,6 +11,8 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import java.time.OffsetDateTime;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
@ -19,9 +21,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.OffsetDateTime;
import java.util.Map;
@Api(tags = "Versions", produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(path = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public interface IVersionsController {
@ -43,16 +42,16 @@ public interface IVersionsController {
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"),
tags = "Version"
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"),
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(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")
})
@GetMapping("/projects/{author}/{slug}/versions/{name}")
Version getVersion(@ApiParam("The author of the project to return the version for") @PathVariable String author,
@ -60,16 +59,16 @@ public interface IVersionsController {
@ApiParam("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"),
tags = "Versions"
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"),
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(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")
})
@GetMapping("/projects/{author}/{slug}/versions")
PaginatedResult<Version> getVersions(@ApiParam("The author of the project to return versions for") @PathVariable String author,
@ -77,17 +76,17 @@ public interface IVersionsController {
@ApiParam("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"),
tags = "Versions"
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"),
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(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")
})
@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,
@ -98,22 +97,22 @@ public interface IVersionsController {
@ApiParam(value = "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"),
tags = "Versions"
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"),
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(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")
})
@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);
@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);
}

View File

@ -23,11 +23,11 @@ public final class ApiUtils {
* @param limit requested limit
* @return actual limit
*/
public static long limitOrDefault(@Nullable final Long limit) {
public static long limitOrDefault(final @Nullable Long limit) {
return limitOrDefault(limit, DEFAULT_LIMIT);
}
public static long limitOrDefault(@Nullable final Long limit, final long maxLimit) {
public static long limitOrDefault(final @Nullable Long limit, final long maxLimit) {
if (limit != null && limit < 1)
throw new HangarApiException(HttpStatus.BAD_REQUEST, "Limit should be greater than 0");
return Math.min(limit == null ? maxLimit : limit, maxLimit);
@ -43,7 +43,7 @@ public final class ApiUtils {
return Math.max(offset == null ? 0 : offset, 0);
}
public static <T> @Nullable T mapParameter(final NativeWebRequest webRequest, final String param, Function<String, T> map) {
public static <T> @Nullable T mapParameter(final NativeWebRequest webRequest, final String param, final Function<String, T> map) {
final @Nullable String value = webRequest.getParameter(param);
if (value != null) {
return map.apply(value);

View File

@ -13,25 +13,25 @@ public class RobotsBuilder {
this.sitemaps = new ArrayList<>();
}
public Group group(String userAgent) {
public Group group(final String userAgent) {
return new Group(userAgent, this);
}
public RobotsBuilder sitemap(String sitemap) {
public RobotsBuilder sitemap(final String sitemap) {
this.sitemaps.add(sitemap);
return this;
}
public String build() {
StringBuilder sb = new StringBuilder();
for (Group group : groups) {
final StringBuilder sb = new StringBuilder();
for (final Group group : this.groups) {
sb.append("user-agent: ").append(group.userAgent).append("\n");
for (String directive : group.directives) {
for (final String directive : group.directives) {
sb.append(directive).append("\n");
}
}
sb.append("\n\n");
for (String sitemap : sitemaps) {
for (final String sitemap : this.sitemaps) {
sb.append("Sitemap: ").append(sitemap).append("\n");
}
return sb.toString();
@ -43,18 +43,18 @@ public class RobotsBuilder {
private final RobotsBuilder builder;
private final List<String> directives;
private Group(String userAgent, RobotsBuilder builder) {
private Group(final String userAgent, final RobotsBuilder builder) {
this.userAgent = userAgent;
this.builder = builder;
this.directives = new ArrayList<>();
}
public Group allow(String path) {
public Group allow(final String path) {
this.directives.add("Allow: " + path);
return this;
}
public Group disallow(String path) {
public Group disallow(final String path) {
this.directives.add("Disallow: " + path);
return this;
}

View File

@ -1,16 +1,15 @@
package io.papermc.hangar.controller.extras.converters;
import java.time.OffsetDateTime;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.time.OffsetDateTime;
@Component
public class OffsetDateTimeConverter implements Converter<String, OffsetDateTime> {
@Override
public OffsetDateTime convert(@NotNull String s) {
public OffsetDateTime convert(final @NotNull String s) {
return OffsetDateTime.parse(s);
}
}

View File

@ -2,47 +2,46 @@ package io.papermc.hangar.controller.extras.converters;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.exceptions.HangarApiException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings("rawtypes")
@Component
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
@SuppressWarnings("unchecked")
@Override
public <T extends Enum> @NotNull Converter<String, T> getConverter(@NotNull Class<T> targetType) {
public <T extends Enum> @NotNull Converter<String, T> getConverter(final @NotNull Class<T> targetType) {
return s -> {
// search for json creator first
try {
for (Method declaredMethod : targetType.getDeclaredMethods()) {
for (final Method declaredMethod : targetType.getDeclaredMethods()) {
if (declaredMethod.isAnnotationPresent(JsonCreator.class)) {
return (T) declaredMethod.invoke(null, s);
}
}
} catch (IllegalAccessException | InvocationTargetException ignored) {
} catch (final IllegalAccessException | InvocationTargetException ignored) {
// ignored
}
// try ordinal
int ordinal;
final int ordinal;
try {
ordinal = Integer.parseInt(s);
return targetType.getEnumConstants()[ordinal];
} catch (NumberFormatException ignored) {
} catch (final NumberFormatException ignored) {
// ignored
}
// fall back to value of
try {
return (T) Enum.valueOf(targetType, s.trim().toUpperCase());
} catch (IllegalArgumentException e) {
} catch (final IllegalArgumentException e) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, s + " did not match a valid " + targetType);
}

View File

@ -1,25 +1,23 @@
package io.papermc.hangar.controller.extras.pagination;
import io.papermc.hangar.controller.extras.pagination.Filter.FilterInstance;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
public interface Filter<F extends FilterInstance> {
Set<String> getQueryParamNames();
@NotNull
default String getSingleQueryParam() {
return getQueryParamNames().stream().findFirst().orElseThrow();
default @NotNull String getSingleQueryParam() {
return this.getQueryParamNames().stream().findFirst().orElseThrow();
}
String getDescription();
default boolean supports(NativeWebRequest webRequest) {
return webRequest.getParameterMap().containsKey(getSingleQueryParam());
default boolean supports(final NativeWebRequest webRequest) {
return webRequest.getParameterMap().containsKey(this.getSingleQueryParam());
}
@NotNull

View File

@ -24,8 +24,7 @@ public class FilterRegistry {
filters.forEach(f -> this.filters.put((Class<? extends Filter<?>>) f.getClass(), f));
}
@NotNull
public <T extends Filter<? extends Filter.FilterInstance>> T get(final Class<T> filterClass) {
public @NotNull <T extends Filter<? extends Filter.FilterInstance>> T get(final Class<T> filterClass) {
if (this.filters.containsKey(filterClass)) {
return (T) this.filters.get(filterClass);
}

View File

@ -1,19 +1,17 @@
package io.papermc.hangar.controller.extras.pagination;
import io.papermc.hangar.controller.extras.pagination.SorterRegistry.SortDirection;
import java.util.function.Consumer;
@FunctionalInterface
public interface Sorter {
void applySorting(StringBuilder sb, SortDirection dir);
void applySorting(StringBuilder sb, SorterRegistry.SortDirection dir);
default Consumer<StringBuilder> ascending() {
return sb -> applySorting(sb, SortDirection.ASCENDING);
return sb -> this.applySorting(sb, SorterRegistry.SortDirection.ASCENDING);
}
default Consumer<StringBuilder> descending() {
return sb -> applySorting(sb, SortDirection.DESCENDING);
return sb -> this.applySorting(sb, SorterRegistry.SortDirection.DESCENDING);
}
}

View File

@ -1,9 +1,8 @@
package io.papermc.hangar.controller.extras.pagination;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public enum SorterRegistry implements Sorter {
@ -28,28 +27,27 @@ public enum SorterRegistry implements Sorter {
private final String name;
private final Sorter sorter;
SorterRegistry(String name, Sorter sorter) {
SorterRegistry(final String name, final Sorter sorter) {
this.name = name;
this.sorter = sorter;
}
public String getName() {
return name;
return this.name;
}
@Override
public void applySorting(StringBuilder sb, SortDirection dir) {
public void applySorting(final StringBuilder sb, final SortDirection dir) {
this.sorter.applySorting(sb, dir);
}
private static Sorter simpleSorter(@NotNull String columnName) {
private static Sorter simpleSorter(final @NotNull String columnName) {
return (sb, dir) -> sb.append(columnName).append(dir);
}
@NotNull
public static SorterRegistry getSorter(@NotNull String name) {
public static @NotNull SorterRegistry getSorter(final @NotNull String name) {
if (SORTERS.isEmpty()) {
for (SorterRegistry value : SorterRegistry.values()) {
for (final SorterRegistry value : values()) {
SORTERS.put(value.name, value);
}
}
@ -65,13 +63,13 @@ public enum SorterRegistry implements Sorter {
private final String sql;
SortDirection(String sql) {
SortDirection(final String sql) {
this.sql = sql;
}
@Override
public String toString() {
return sql;
return this.sql;
}
}
}

View File

@ -1,8 +1,6 @@
package io.papermc.hangar.controller.extras.pagination.annotations;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.Filter.FilterInstance;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -17,5 +15,5 @@ public @interface ApplicableFilters {
/**
* Set of applicable filters for this request
*/
Class<? extends Filter<? extends FilterInstance>>[] value();
Class<? extends Filter<? extends Filter.FilterInstance>>[] value();
}

View File

@ -1,7 +1,6 @@
package io.papermc.hangar.controller.extras.pagination.annotations;
import io.papermc.hangar.controller.extras.pagination.SorterRegistry;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -3,13 +3,12 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogActionFilter.LogActionFilterInstance;
import io.papermc.hangar.model.internal.logs.LogAction;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogActionFilter implements Filter<LogActionFilterInstance> {
@ -24,28 +23,27 @@ public class LogActionFilter implements Filter<LogActionFilterInstance> {
}
@Override
public boolean supports(NativeWebRequest webRequest) {
return Filter.super.supports(webRequest) && LogAction.LOG_REGISTRY.containsKey(webRequest.getParameter(getSingleQueryParam()));
public boolean supports(final NativeWebRequest webRequest) {
return Filter.super.supports(webRequest) && LogAction.LOG_REGISTRY.containsKey(webRequest.getParameter(this.getSingleQueryParam()));
}
@NotNull
@Override
public LogActionFilterInstance create(NativeWebRequest webRequest) {
return new LogActionFilterInstance(LogAction.LOG_REGISTRY.get(webRequest.getParameter(getSingleQueryParam())));
public @NotNull LogActionFilterInstance create(final NativeWebRequest webRequest) {
return new LogActionFilterInstance(LogAction.LOG_REGISTRY.get(webRequest.getParameter(this.getSingleQueryParam())));
}
static class LogActionFilterInstance implements FilterInstance {
static class LogActionFilterInstance implements Filter.FilterInstance {
private final LogAction<?> logAction;
LogActionFilterInstance(LogAction<?> logAction) {
LogActionFilterInstance(final LogAction<?> logAction) {
this.logAction = logAction;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND la.action = :actionFilter::LOGGED_ACTION_TYPE");
q.bind("actionFilter", logAction.getPgLoggedAction().getValue());
q.bind("actionFilter", this.logAction.getPgLoggedAction().getValue());
}
}
}

View File

@ -2,14 +2,13 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogPageFilter.LogPageFilterInstance;
import java.util.Set;
import org.apache.commons.lang3.math.NumberUtils;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogPageFilter implements Filter<LogPageFilterInstance> {
@ -24,26 +23,25 @@ public class LogPageFilter implements Filter<LogPageFilterInstance> {
}
@Override
public boolean supports(NativeWebRequest webRequest) {
return Filter.super.supports(webRequest) && NumberUtils.isDigits(webRequest.getParameter(getSingleQueryParam()));
public boolean supports(final NativeWebRequest webRequest) {
return Filter.super.supports(webRequest) && NumberUtils.isDigits(webRequest.getParameter(this.getSingleQueryParam()));
}
@NotNull
@Override
public LogPageFilterInstance create(NativeWebRequest webRequest) {
return new LogPageFilterInstance(Long.parseLong(webRequest.getParameter(getSingleQueryParam())));
public @NotNull LogPageFilterInstance create(final NativeWebRequest webRequest) {
return new LogPageFilterInstance(Long.parseLong(webRequest.getParameter(this.getSingleQueryParam())));
}
static class LogPageFilterInstance implements FilterInstance {
static class LogPageFilterInstance implements Filter.FilterInstance {
private final long pageId;
LogPageFilterInstance(long pageId) {
LogPageFilterInstance(final long pageId) {
this.pageId = pageId;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND la.pp_id = :pageId");
q.bind("pageId", this.pageId);
}

View File

@ -2,14 +2,13 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogProjectFilter.LogProjectFilterInstance;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogProjectFilter implements Filter<LogProjectFilterInstance> {
@ -19,8 +18,8 @@ public class LogProjectFilter implements Filter<LogProjectFilterInstance> {
}
@Override
public boolean supports(NativeWebRequest webRequest) {
return getQueryParamNames().stream().anyMatch(webRequest.getParameterMap()::containsKey);
public boolean supports(final NativeWebRequest webRequest) {
return this.getQueryParamNames().stream().anyMatch(webRequest.getParameterMap()::containsKey);
}
@Override
@ -28,24 +27,23 @@ public class LogProjectFilter implements Filter<LogProjectFilterInstance> {
return "Filters logs by a project namespace";
}
@NotNull
@Override
public LogProjectFilterInstance create(NativeWebRequest webRequest) {
public @NotNull LogProjectFilterInstance create(final NativeWebRequest webRequest) {
return new LogProjectFilterInstance(webRequest.getParameter("authorName"), webRequest.getParameter("projectSlug"));
}
static class LogProjectFilterInstance implements FilterInstance {
static class LogProjectFilterInstance implements Filter.FilterInstance {
private final String authorName;
private final String projectSlug;
LogProjectFilterInstance(String authorName, String projectSlug) {
LogProjectFilterInstance(final String authorName, final String projectSlug) {
this.authorName = authorName;
this.projectSlug = projectSlug;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
if (StringUtils.isNotBlank(this.authorName)) {
sb.append(" AND la.p_owner_name = :authorName");
q.bind("authorName", this.authorName);

View File

@ -2,14 +2,13 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogSubjectFilter.LogSubjectFilterInstance;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogSubjectFilter implements Filter<LogSubjectFilterInstance> {
@ -23,22 +22,21 @@ public class LogSubjectFilter implements Filter<LogSubjectFilterInstance> {
return "Filters by subject name, usually a user action where the subject name is the user the action is about, not the user that performed the action";
}
@NotNull
@Override
public LogSubjectFilterInstance create(NativeWebRequest webRequest) {
return new LogSubjectFilterInstance(webRequest.getParameter(getSingleQueryParam()));
public @NotNull LogSubjectFilterInstance create(final NativeWebRequest webRequest) {
return new LogSubjectFilterInstance(webRequest.getParameter(this.getSingleQueryParam()));
}
static class LogSubjectFilterInstance implements FilterInstance {
static class LogSubjectFilterInstance implements Filter.FilterInstance {
private final String subjectName;
LogSubjectFilterInstance(String subjectName) {
LogSubjectFilterInstance(final String subjectName) {
this.subjectName = subjectName;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
if (StringUtils.isNotBlank(this.subjectName)) {
sb.append(" AND la.s_name = :subjectName");
q.bind("subjectName", this.subjectName);

View File

@ -2,14 +2,13 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogUserFilter.LogUserFilterInstance;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogUserFilter implements Filter<LogUserFilterInstance> {
@ -24,31 +23,31 @@ public class LogUserFilter implements Filter<LogUserFilterInstance> {
}
@Override
public @NotNull LogUserFilterInstance create(NativeWebRequest webRequest) {
return new LogUserFilterInstance(webRequest.getParameter(getSingleQueryParam()));
public @NotNull LogUserFilterInstance create(final NativeWebRequest webRequest) {
return new LogUserFilterInstance(webRequest.getParameter(this.getSingleQueryParam()));
}
static class LogUserFilterInstance implements FilterInstance {
static class LogUserFilterInstance implements Filter.FilterInstance {
private final String userName;
LogUserFilterInstance(String userName) {
LogUserFilterInstance(final String userName) {
this.userName = userName;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
if (StringUtils.isNotBlank(userName)) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
if (StringUtils.isNotBlank(this.userName)) {
sb.append(" AND la.user_name = :userName");
q.bind("userName", userName);
q.bind("userName", this.userName);
}
}
@Override
public String toString() {
return "LogUserFilterInstance{" +
"userName='" + userName + '\'' +
'}';
"userName='" + this.userName + '\'' +
'}';
}
}
}

View File

@ -3,6 +3,7 @@ package io.papermc.hangar.controller.extras.pagination.filters.log;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.log.LogVersionFilter.LogVersionFilterInstance;
import io.papermc.hangar.model.common.Platform;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -10,15 +11,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class LogVersionFilter implements Filter<LogVersionFilterInstance> {
private final ConversionService conversionService;
@Autowired
public LogVersionFilter(ConversionService conversionService) {
public LogVersionFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -33,28 +32,27 @@ public class LogVersionFilter implements Filter<LogVersionFilterInstance> {
}
@Override
public boolean supports(NativeWebRequest webRequest) {
return getQueryParamNames().stream().allMatch(webRequest.getParameterMap()::containsKey);
public boolean supports(final NativeWebRequest webRequest) {
return this.getQueryParamNames().stream().allMatch(webRequest.getParameterMap()::containsKey);
}
@NotNull
@Override
public LogVersionFilterInstance create(NativeWebRequest webRequest) {
return new LogVersionFilterInstance(webRequest.getParameter("versionString"), conversionService.convert(webRequest.getParameter("platform"), Platform.class));
public @NotNull LogVersionFilterInstance create(final NativeWebRequest webRequest) {
return new LogVersionFilterInstance(webRequest.getParameter("versionString"), this.conversionService.convert(webRequest.getParameter("platform"), Platform.class));
}
static class LogVersionFilterInstance implements FilterInstance {
static class LogVersionFilterInstance implements Filter.FilterInstance {
private final String versionString;
private final Platform platform;
LogVersionFilterInstance(String versionString, Platform platform) {
LogVersionFilterInstance(final String versionString, final Platform platform) {
this.versionString = versionString;
this.platform = platform;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND la.pv_version_string = :versionString");
q.bind("versionString", this.versionString);
sb.append(" AND :platform = ANY(la.pv_platforms)");

View File

@ -2,13 +2,12 @@ package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectAuthorFilter.ProjectAuthorFilterInstance;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class ProjectAuthorFilter implements Filter<ProjectAuthorFilterInstance> {
@ -22,31 +21,30 @@ public class ProjectAuthorFilter implements Filter<ProjectAuthorFilterInstance>
return "The author of the project";
}
@NotNull
@Override
public ProjectAuthorFilterInstance create(NativeWebRequest webRequest) {
return new ProjectAuthorFilterInstance(webRequest.getParameter(getSingleQueryParam()));
public @NotNull ProjectAuthorFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectAuthorFilterInstance(webRequest.getParameter(this.getSingleQueryParam()));
}
static class ProjectAuthorFilterInstance implements FilterInstance {
static class ProjectAuthorFilterInstance implements Filter.FilterInstance {
private final String ownerName;
ProjectAuthorFilterInstance(String ownerName) {
ProjectAuthorFilterInstance(final String ownerName) {
this.ownerName = ownerName;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND ").append("p.owner_name").append(" = ").append(":ownerName");
q.bind("ownerName", ownerName);
q.bind("ownerName", this.ownerName);
}
@Override
public String toString() {
return "ProjectAuthorFilterInstance{" +
"ownerName='" + ownerName + '\'' +
'}';
"ownerName='" + this.ownerName + '\'' +
'}';
}
}
}

View File

@ -3,6 +3,8 @@ package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectCategoryFilter.ProjectCategoryFilterInstance;
import io.papermc.hangar.model.common.projects.Category;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -10,16 +12,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
@Component
public class ProjectCategoryFilter implements Filter<ProjectCategoryFilterInstance> {
private final ConversionService conversionService;
@Autowired
public ProjectCategoryFilter(ConversionService conversionService) {
public ProjectCategoryFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -33,29 +32,28 @@ public class ProjectCategoryFilter implements Filter<ProjectCategoryFilterInstan
return "A category to filter for";
}
@NotNull
@Override
public ProjectCategoryFilterInstance create(NativeWebRequest webRequest) {
return new ProjectCategoryFilterInstance(conversionService.convert(webRequest.getParameterValues(getSingleQueryParam()), Category[].class));
public @NotNull ProjectCategoryFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectCategoryFilterInstance(this.conversionService.convert(webRequest.getParameterValues(this.getSingleQueryParam()), Category[].class));
}
static class ProjectCategoryFilterInstance implements FilterInstance {
static class ProjectCategoryFilterInstance implements Filter.FilterInstance {
private final Category[] categories;
public ProjectCategoryFilterInstance(Category[] categories) {
public ProjectCategoryFilterInstance(final Category[] categories) {
this.categories = categories;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND ").append("p.category").append(" IN (");
for (int i = 0; i < categories.length; i++) {
for (int i = 0; i < this.categories.length; i++) {
sb.append(":__category__").append(i);
if (i + 1 != categories.length) {
if (i + 1 != this.categories.length) {
sb.append(",");
}
q.bind("__category__" + i, categories[i]);
q.bind("__category__" + i, this.categories[i]);
}
sb.append(")");
}
@ -63,8 +61,8 @@ public class ProjectCategoryFilter implements Filter<ProjectCategoryFilterInstan
@Override
public String toString() {
return "ProjectCategoryFilterInstance{" +
"categories=" + Arrays.toString(categories) +
'}';
"categories=" + Arrays.toString(this.categories) +
'}';
}
}
}

View File

@ -1,5 +1,8 @@
package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -7,18 +10,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
import io.papermc.hangar.controller.extras.pagination.Filter;
@Component
public class ProjectLicenseFilter implements Filter<ProjectLicenseFilter.ProjectLicenseFilterInstance> {
private final ConversionService conversionService;
@Autowired
public ProjectLicenseFilter(ConversionService conversionService) {
public ProjectLicenseFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -32,29 +30,28 @@ public class ProjectLicenseFilter implements Filter<ProjectLicenseFilter.Project
return "A license to filter for";
}
@NotNull
@Override
public ProjectLicenseFilterInstance create(NativeWebRequest webRequest) {
return new ProjectLicenseFilterInstance(conversionService.convert(webRequest.getParameterValues(getSingleQueryParam()), String[].class));
public @NotNull ProjectLicenseFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectLicenseFilterInstance(this.conversionService.convert(webRequest.getParameterValues(this.getSingleQueryParam()), String[].class));
}
static class ProjectLicenseFilterInstance implements FilterInstance {
static class ProjectLicenseFilterInstance implements Filter.FilterInstance {
private final String[] licenses;
public ProjectLicenseFilterInstance(String[] licenses) {
public ProjectLicenseFilterInstance(final String[] licenses) {
this.licenses = licenses;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND p.license_type").append(" IN (");
for (int i = 0; i < licenses.length; i++) {
for (int i = 0; i < this.licenses.length; i++) {
sb.append(":__licenses__").append(i);
if (i + 1 != licenses.length) {
if (i + 1 != this.licenses.length) {
sb.append(",");
}
q.bind("__licenses__" + i, licenses[i]);
q.bind("__licenses__" + i, this.licenses[i]);
}
sb.append(")");
}
@ -62,8 +59,8 @@ public class ProjectLicenseFilter implements Filter<ProjectLicenseFilter.Project
@Override
public String toString() {
return "ProjectLicenseFilterInstance{" +
"licenses=" + Arrays.toString(licenses) +
'}';
"licenses=" + Arrays.toString(this.licenses) +
'}';
}
}
}

View File

@ -1,6 +1,8 @@
package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -8,16 +10,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
@Component
public class ProjectMCVersionFilter implements Filter<ProjectMCVersionFilter.ProjectMCVersionFilterInstance> {
private final ConversionService conversionService;
@Autowired
public ProjectMCVersionFilter(ConversionService conversionService) {
public ProjectMCVersionFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -31,13 +30,12 @@ public class ProjectMCVersionFilter implements Filter<ProjectMCVersionFilter.Pro
return "A Minecraft version to filter for";
}
@NotNull
@Override
public ProjectMCVersionFilterInstance create(NativeWebRequest webRequest) {
return new ProjectMCVersionFilterInstance(conversionService.convert(webRequest.getParameterValues(getSingleQueryParam()), String[].class));
public @NotNull ProjectMCVersionFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectMCVersionFilterInstance(this.conversionService.convert(webRequest.getParameterValues(this.getSingleQueryParam()), String[].class));
}
static class ProjectMCVersionFilterInstance implements FilterInstance {
static class ProjectMCVersionFilterInstance implements Filter.FilterInstance {
private final String[] versions;
@ -46,14 +44,14 @@ public class ProjectMCVersionFilter implements Filter<ProjectMCVersionFilter.Pro
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND v.version").append(" IN (");
for (int i = 0; i < versions.length; i++) {
for (int i = 0; i < this.versions.length; i++) {
sb.append(":__version__").append(i);
if (i + 1 != versions.length) {
if (i + 1 != this.versions.length) {
sb.append(",");
}
q.bind("__version__" + i, versions[i]);
q.bind("__version__" + i, this.versions[i]);
}
sb.append(")");
}
@ -61,7 +59,7 @@ public class ProjectMCVersionFilter implements Filter<ProjectMCVersionFilter.Pro
@Override
public String toString() {
return "ProjectMCVersionFilterInstance{" +
"versions=" + Arrays.toString(versions) +
"versions=" + Arrays.toString(this.versions) +
'}';
}
}

View File

@ -2,6 +2,8 @@ package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.model.common.Platform;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -9,16 +11,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
@Component
public class ProjectPlatformFilter implements Filter<ProjectPlatformFilter.ProjectPlatformFilterInstance> {
private final ConversionService conversionService;
@Autowired
public ProjectPlatformFilter(ConversionService conversionService) {
public ProjectPlatformFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -32,29 +31,28 @@ public class ProjectPlatformFilter implements Filter<ProjectPlatformFilter.Proje
return "A platform to filter for";
}
@NotNull
@Override
public ProjectPlatformFilterInstance create(NativeWebRequest webRequest) {
return new ProjectPlatformFilterInstance(conversionService.convert(webRequest.getParameterValues(getSingleQueryParam()), Platform[].class));
public @NotNull ProjectPlatformFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectPlatformFilterInstance(this.conversionService.convert(webRequest.getParameterValues(this.getSingleQueryParam()), Platform[].class));
}
static class ProjectPlatformFilterInstance implements FilterInstance {
static class ProjectPlatformFilterInstance implements Filter.FilterInstance {
private final Platform[] platforms;
public ProjectPlatformFilterInstance(Platform[] platforms) {
public ProjectPlatformFilterInstance(final Platform[] platforms) {
this.platforms = platforms;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND v.platform").append(" IN (");
for (int i = 0; i < platforms.length; i++) {
for (int i = 0; i < this.platforms.length; i++) {
sb.append(":__platform__").append(i);
if (i + 1 != platforms.length) {
if (i + 1 != this.platforms.length) {
sb.append(",");
}
q.bind("__platform__" + i, platforms[i]);
q.bind("__platform__" + i, this.platforms[i]);
}
sb.append(")");
}
@ -62,8 +60,8 @@ public class ProjectPlatformFilter implements Filter<ProjectPlatformFilter.Proje
@Override
public String toString() {
return "ProjectCategoryFilterInstance{" +
"platforms=" + Arrays.toString(platforms) +
'}';
"platforms=" + Arrays.toString(this.platforms) +
'}';
}
}
}

View File

@ -2,13 +2,12 @@ package io.papermc.hangar.controller.extras.pagination.filters.projects;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.projects.ProjectQueryFilter.ProjectQueryFilterInstance;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class ProjectQueryFilter implements Filter<ProjectQueryFilterInstance> {
@ -22,35 +21,34 @@ public class ProjectQueryFilter implements Filter<ProjectQueryFilterInstance> {
return "The query to use when searching";
}
@NotNull
@Override
public ProjectQueryFilterInstance create(NativeWebRequest webRequest) {
return new ProjectQueryFilterInstance(webRequest.getParameter(getSingleQueryParam()));
public @NotNull ProjectQueryFilterInstance create(final NativeWebRequest webRequest) {
return new ProjectQueryFilterInstance(webRequest.getParameter(this.getSingleQueryParam()));
}
static class ProjectQueryFilterInstance implements FilterInstance {
static class ProjectQueryFilterInstance implements Filter.FilterInstance {
private final String query;
ProjectQueryFilterInstance(String query) {
ProjectQueryFilterInstance(final String query) {
this.query = query;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND (hp.search_words @@ websearch_to_tsquery");
if (!query.endsWith(" ")) {
if (!this.query.endsWith(" ")) {
sb.append("_postfix");
}
sb.append("('english', :query)").append(")");
q.bind("query", query.trim());
q.bind("query", this.query.trim());
}
@Override
public String toString() {
return "ProjectQueryFilterInstance{" +
"query='" + query + '\'' +
'}';
"query='" + this.query + '\'' +
'}';
}
}
}

View File

@ -2,14 +2,13 @@ package io.papermc.hangar.controller.extras.pagination.filters.versions;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.versions.VersionChannelFilter.VersionChannelFilterInstance;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
@Component
public class VersionChannelFilter implements Filter<VersionChannelFilterInstance> {
@ -23,27 +22,26 @@ public class VersionChannelFilter implements Filter<VersionChannelFilterInstance
return "A name of a version channel to filter for";
}
@NotNull
@Override
public VersionChannelFilterInstance create(NativeWebRequest webRequest) {
return new VersionChannelFilterInstance(webRequest.getParameterValues(getSingleQueryParam()));
public @NotNull VersionChannelFilterInstance create(final NativeWebRequest webRequest) {
return new VersionChannelFilterInstance(webRequest.getParameterValues(this.getSingleQueryParam()));
}
public static class VersionChannelFilterInstance implements FilterInstance {
public static class VersionChannelFilterInstance implements Filter.FilterInstance {
private final String[] channels;
public VersionChannelFilterInstance(String[] channels) {
public VersionChannelFilterInstance(final String[] channels) {
this.channels = channels;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND pc.name IN (");
for (int i = 0; i < channels.length; i++) {
for (int i = 0; i < this.channels.length; i++) {
sb.append(":__channelName_").append(i);
q.bind("__channelName_" + i, channels[i]);
if (i + 1 != channels.length) {
q.bind("__channelName_" + i, this.channels[i]);
if (i + 1 != this.channels.length) {
sb.append(",");
}
}
@ -53,8 +51,8 @@ public class VersionChannelFilter implements Filter<VersionChannelFilterInstance
@Override
public String toString() {
return "VersionChannelFilterInstance{" +
"channels=" + Arrays.toString(channels) +
'}';
"channels=" + Arrays.toString(this.channels) +
'}';
}
}
}

View File

@ -3,6 +3,8 @@ package io.papermc.hangar.controller.extras.pagination.filters.versions;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.versions.VersionPlatformFilter.VersionPlatformFilterInstance;
import io.papermc.hangar.model.common.Platform;
import java.util.Arrays;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -10,16 +12,13 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Arrays;
import java.util.Set;
@Component
public class VersionPlatformFilter implements Filter<VersionPlatformFilterInstance> {
private final ConversionService conversionService;
@Autowired
public VersionPlatformFilter(ConversionService conversionService) {
public VersionPlatformFilter(final ConversionService conversionService) {
this.conversionService = conversionService;
}
@ -33,27 +32,26 @@ public class VersionPlatformFilter implements Filter<VersionPlatformFilterInstan
return "A platform name to filter for";
}
@NotNull
@Override
public VersionPlatformFilterInstance create(NativeWebRequest webRequest) {
return new VersionPlatformFilterInstance(conversionService.convert(webRequest.getParameterValues(getSingleQueryParam()), Platform[].class));
public @NotNull VersionPlatformFilterInstance create(final NativeWebRequest webRequest) {
return new VersionPlatformFilterInstance(this.conversionService.convert(webRequest.getParameterValues(this.getSingleQueryParam()), Platform[].class));
}
public static class VersionPlatformFilterInstance implements FilterInstance {
public static class VersionPlatformFilterInstance implements Filter.FilterInstance {
private final Platform[] platforms;
public VersionPlatformFilterInstance(Platform[] platforms) {
public VersionPlatformFilterInstance(final Platform[] platforms) {
this.platforms = platforms;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND (");
for (int i = 0; i < platforms.length; i++) {
for (int i = 0; i < this.platforms.length; i++) {
sb.append(":__platform_").append(i).append(" = ANY(sq.platforms)");
q.bind("__platform_" + i, platforms[i]);
if (i + 1 != platforms.length) {
q.bind("__platform_" + i, this.platforms[i]);
if (i + 1 != this.platforms.length) {
sb.append(" OR ");
}
}
@ -63,8 +61,8 @@ public class VersionPlatformFilter implements Filter<VersionPlatformFilterInstan
@Override
public String toString() {
return "VersionPlatformFilterInstance{" +
"platforms=" + Arrays.toString(platforms) +
'}';
"platforms=" + Arrays.toString(this.platforms) +
'}';
}
}
}

View File

@ -3,6 +3,7 @@ package io.papermc.hangar.controller.extras.pagination.filters.versions;
import io.papermc.hangar.controller.extras.pagination.Filter;
import io.papermc.hangar.controller.extras.pagination.filters.versions.VersionTagFilter.VersionTagFilterInstance;
import io.papermc.hangar.exceptions.HangarApiException;
import java.util.Set;
import org.jdbi.v3.core.statement.SqlStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
@ -11,8 +12,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.NativeWebRequest;
import java.util.Set;
@Component
public class VersionTagFilter implements Filter<VersionTagFilterInstance> {
@ -26,32 +25,31 @@ public class VersionTagFilter implements Filter<VersionTagFilterInstance> {
return "A version tag to filter for";
}
@NotNull
@Override
public VersionTagFilterInstance create(NativeWebRequest webRequest) {
MultiValueMap<String, String> versionTags = new LinkedMultiValueMap<>();
for (String tag : webRequest.getParameterValues(getSingleQueryParam())) {
public @NotNull VersionTagFilterInstance create(final NativeWebRequest webRequest) {
final MultiValueMap<String, String> versionTags = new LinkedMultiValueMap<>();
for (final String tag : webRequest.getParameterValues(this.getSingleQueryParam())) {
if (!tag.contains(":")) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "Must specify a version. e.g. Paper:1.14");
}
String[] splitTag = tag.split(":", 2);
final String[] splitTag = tag.split(":", 2);
versionTags.add(splitTag[0], splitTag[1]);
}
return new VersionTagFilterInstance(versionTags);
}
static class VersionTagFilterInstance implements FilterInstance {
static class VersionTagFilterInstance implements Filter.FilterInstance {
private final MultiValueMap<String, String> versionTags;
VersionTagFilterInstance(MultiValueMap<String, String> versionTags) {
VersionTagFilterInstance(final MultiValueMap<String, String> versionTags) {
this.versionTags = versionTags;
}
@Override
public void createSql(StringBuilder sb, SqlStatement<?> q) {
public void createSql(final StringBuilder sb, final SqlStatement<?> q) {
sb.append(" AND (");
versionTags.forEach((name, versions) -> {
this.versionTags.forEach((name, versions) -> {
q.bind("__vTag_name_" + name, name);
for (int i = 0; i < versions.size(); i++) {
sb.append(":__vTag_name_").append(name).append("_v_").append(i).append(" = ANY(SELECT unnest(vtsq.data) WHERE vtsq.name = :__vTag_name_").append(name).append(")");
@ -67,8 +65,8 @@ public class VersionTagFilter implements Filter<VersionTagFilterInstance> {
@Override
public String toString() {
return "VersionTagFilterInstance{" +
"versionTags=" + versionTags +
'}';
"versionTags=" + this.versionTags +
'}';
}
}
}

View File

@ -50,7 +50,7 @@ public class RequestPaginationResolver implements HandlerMethodArgumentResolver
}
@Override
public boolean supportsParameter(@NotNull final MethodParameter parameter) {
public boolean supportsParameter(final @NotNull MethodParameter parameter) {
return RequestPagination.class.isAssignableFrom(parameter.getParameterType());
}
@ -88,7 +88,7 @@ public class RequestPaginationResolver implements HandlerMethodArgumentResolver
}
@Override
public RequestPagination resolveArgument(@NotNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NotNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
public RequestPagination resolveArgument(final @NotNull MethodParameter parameter, final ModelAndViewContainer mavContainer, final @NotNull NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
final RequestPagination pagination = this.create(
ApiUtils.mapParameter(webRequest, "offset", Long::parseLong),
ApiUtils.mapParameter(webRequest, "limit", Long::parseLong),

View File

@ -13,19 +13,18 @@ public abstract class HangarModelPathVarResolver<M> extends PathVariableMethodAr
protected abstract Class<M> modelType();
@Override
public boolean supportsParameter(@NotNull final MethodParameter parameter) {
public boolean supportsParameter(final @NotNull MethodParameter parameter) {
return super.supportsParameter(parameter) && parameter.getParameterType().equals(this.modelType());
}
@Override
protected void handleMissingValue(@NotNull final String name, @NotNull final MethodParameter parameter) {
protected void handleMissingValue(final @NotNull String name, final @NotNull MethodParameter parameter) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
@Nullable
@Override
protected final M resolveName(@NotNull final String name, @NotNull final MethodParameter parameter, @NotNull final NativeWebRequest request) throws Exception {
String param = (String) super.resolveName(name, parameter, request);
protected final @Nullable M resolveName(final @NotNull String name, final @NotNull MethodParameter parameter, final @NotNull NativeWebRequest request) throws Exception {
final String param = (String) super.resolveName(name, parameter, request);
if (param == null) {
return null;
}

View File

@ -25,8 +25,8 @@ public class ProjectTableResolver extends HangarModelPathVarResolver<ProjectTabl
}
@Override
protected ProjectTable resolveParameter(@NotNull final String param) {
Long projectId = NumberUtils.createLong(param);
protected ProjectTable resolveParameter(final @NotNull String param) {
final Long projectId = NumberUtils.createLong(param);
if (projectId == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}

View File

@ -23,8 +23,8 @@ public class ProjectVersionTableResolver extends HangarModelPathVarResolver<Proj
}
@Override
protected ProjectVersionTable resolveParameter(@NotNull final String param) {
Long versionId = NumberUtils.createLong(param);
protected ProjectVersionTable resolveParameter(final @NotNull String param) {
final Long versionId = NumberUtils.createLong(param);
if (versionId == null) {
return null;
}

View File

@ -22,7 +22,7 @@ public class UserTableResolver extends HangarModelPathVarResolver<UserTable> {
}
@Override
protected UserTable resolveParameter(@NotNull final String param) {
protected UserTable resolveParameter(final @NotNull String param) {
return this.userService.getUserTable(param);
}
}

View File

@ -12,6 +12,8 @@ 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -25,9 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
import java.util.List;
// @el(user: io.papermc.hangar.model.db.UserTable)
@LoggedIn
@Controller
@ -40,7 +39,7 @@ public class ApiKeyController {
private final APIKeyService apiKeyService;
@Autowired
public ApiKeyController(PermissionService permissionService, APIKeyService apiKeyService) {
public ApiKeyController(final PermissionService permissionService, final APIKeyService apiKeyService) {
this.permissionService = permissionService;
this.apiKeyService = apiKeyService;
}
@ -48,22 +47,22 @@ public class ApiKeyController {
@ResponseStatus(HttpStatus.OK)
@CurrentUser("#user")
@GetMapping("/check-key/{user}")
public void checkKeyName(@PathVariable UserTable user, @RequestParam String name) {
apiKeyService.checkName(user, name);
public void checkKeyName(@PathVariable final UserTable user, @RequestParam final String name) {
this.apiKeyService.checkName(user, name);
}
@ResponseBody
@CurrentUser("#user")
@GetMapping(path = "/existing-keys/{user}", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ApiKey> getApiKeys(@PathVariable UserTable user) {
return apiKeyService.getApiKeys(user.getId());
public List<ApiKey> getApiKeys(@PathVariable final UserTable user) {
return this.apiKeyService.getApiKeys(user.getId());
}
@ResponseBody
@CurrentUser("#user")
@GetMapping(path = "/possible-perms/{user}", produces = MediaType.APPLICATION_JSON_VALUE)
public List<NamedPermission> getPossiblePermissions(@PathVariable UserTable user) {
return permissionService.getAllPossiblePermissions(user.getId()).toNamed();
public List<NamedPermission> getPossiblePermissions(@PathVariable final UserTable user) {
return this.permissionService.getAllPossiblePermissions(user.getId()).toNamed();
}
@Unlocked
@ -71,14 +70,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 UserTable user, @RequestBody @Valid CreateAPIKeyForm apiKeyForm) {
return apiKeyService.createApiKey(user, apiKeyForm, permissionService.getAllPossiblePermissions(user.getId()));
public String createApiKey(@PathVariable final UserTable user, @RequestBody final @Valid 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 UserTable user, @RequestBody @Valid StringContent nameContent) {
apiKeyService.deleteApiKey(user, nameContent.getContent());
public void deleteApiKey(@PathVariable final UserTable user, @RequestBody final @Valid StringContent nameContent) {
this.apiKeyService.deleteApiKey(user, nameContent.getContent());
}
}

View File

@ -39,7 +39,6 @@ import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller

View File

@ -14,7 +14,6 @@ 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.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.internal.projects.ChannelService;
import io.papermc.hangar.service.internal.projects.ProjectService;
import java.util.List;
@ -44,7 +43,7 @@ public class ChannelController extends HangarComponent {
private final ProjectService projectService;
@Autowired
public ChannelController(ChannelService channelService, ProjectService projectService) {
public ChannelController(final ChannelService channelService, final ProjectService projectService) {
this.channelService = channelService;
this.projectService = projectService;
}
@ -52,23 +51,23 @@ public class ChannelController extends HangarComponent {
@GetMapping("/checkName")
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
public void checkName(@RequestParam long projectId, @RequestParam String name, @RequestParam(required = false) String existingName) {
channelService.checkName(projectId, name, existingName);
public void checkName(@RequestParam final long projectId, @RequestParam final String name, @RequestParam(required = false) final String existingName) {
this.channelService.checkName(projectId, name, existingName);
}
@GetMapping("/checkColor")
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
public void checkColor(@RequestParam long projectId, @RequestParam Color color, @RequestParam(required = false) Color existingColor) {
channelService.checkColor(projectId, color, existingColor);
public void checkColor(@RequestParam final long projectId, @RequestParam final Color color, @RequestParam(required = false) final Color existingColor) {
this.channelService.checkColor(projectId, color, existingColor);
}
@Anyone
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@GetMapping(path = "/{author}/{slug}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<HangarChannel>> getChannels(@PathVariable String author, @PathVariable String slug) {
ProjectTable projectTable = projectService.getProjectTable(author, slug);
return ResponseEntity.ok(channelService.getProjectChannels(projectTable.getId()));
public ResponseEntity<List<HangarChannel>> getChannels(@PathVariable final String author, @PathVariable final String slug) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
return ResponseEntity.ok(this.channelService.getProjectChannels(projectTable.getId()));
}
@Unlocked
@ -76,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, @Valid @RequestBody final ChannelForm channelForm) {
public void createChannel(@PathVariable final long projectId, @RequestBody final @Valid 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);
@ -87,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, @Valid @RequestBody final EditChannelForm channelForm) {
public void editChannel(@PathVariable final long projectId, @RequestBody final @Valid 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);
@ -97,7 +96,7 @@ public class ChannelController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_CHANNEL, args = "{#projectId}")
@PostMapping("/{projectId}/delete/{channelId}")
public void deleteChannel(@PathVariable long projectId, @PathVariable long channelId) {
channelService.deleteProjectChannel(projectId, channelId);
public void deleteChannel(@PathVariable final long projectId, @PathVariable final long channelId) {
this.channelService.deleteProjectChannel(projectId, channelId);
}
}

View File

@ -16,8 +16,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
// @el(projectId: long)
@Unlocked
@Controller
@ -27,19 +25,19 @@ public class DiscourseController extends HangarComponent {
private final JobService jobService;
public DiscourseController(JobService jobService) {
public DiscourseController(final JobService jobService) {
this.jobService = jobService;
}
@PostMapping("/{projectId}/comment")
@ResponseBody
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 30)
@VisibilityRequired(type = Type.PROJECT, args = "{#projectId}")
public String createPost(@PathVariable long projectId, @RequestBody Map<String, String> content) {
if (!config.discourse.enabled()) {
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#projectId}")
public String createPost(@PathVariable final long projectId, @RequestBody final Map<String, String> content) {
if (!this.config.discourse.enabled()) {
throw new HangarApiException("Discourse is NOT enabled!");
}
jobService.save(new PostDiscourseReplyJob(projectId, getHangarPrincipal().getName(), content.get("content")));
this.jobService.save(new PostDiscourseReplyJob(projectId, this.getHangarPrincipal().getName(), content.get("content")));
return "dum";
}
}

View File

@ -1,16 +1,16 @@
package io.papermc.hangar.controller.internal;
import io.papermc.hangar.model.common.NamedPermission;
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.FakeDataService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.service.internal.FakeDataService;
@Unlocked
@Controller
@ -20,14 +20,14 @@ public class FakeDataController {
private final FakeDataService fakeDataService;
public FakeDataController(FakeDataService fakeDataService) {
public FakeDataController(final FakeDataService fakeDataService) {
this.fakeDataService = fakeDataService;
}
@ResponseStatus(HttpStatus.OK)
@GetMapping("/")
@PermissionRequired(NamedPermission.MANUAL_VALUE_CHANGES)
public void generateFakeData(@RequestParam int users, @RequestParam int projectsPerUser) {
fakeDataService.generate(users, projectsPerUser);
public void generateFakeData(@RequestParam final int users, @RequestParam final int projectsPerUser) {
this.fakeDataService.generate(users, projectsPerUser);
}
}

View File

@ -17,7 +17,7 @@ import io.papermc.hangar.model.internal.logs.LogAction;
import io.papermc.hangar.model.internal.logs.contexts.UserContext;
import io.papermc.hangar.model.internal.sso.Traits;
import io.papermc.hangar.model.internal.user.HangarUser;
import io.papermc.hangar.model.internal.user.notifications.HangarInvite.InviteType;
import io.papermc.hangar.model.internal.user.notifications.HangarInvite;
import io.papermc.hangar.model.internal.user.notifications.HangarNotification;
import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.security.annotations.LoggedIn;
@ -74,7 +74,7 @@ public class HangarUserController extends HangarComponent {
private final TokenService tokenService;
@Autowired
public HangarUserController(ObjectMapper mapper, UsersApiService usersApiService, UserService userService, NotificationService notificationService, ProjectRoleService projectRoleService, OrganizationRoleService organizationRoleService, ProjectInviteService projectInviteService, OrganizationInviteService organizationInviteService, final TokenService tokenService) {
public HangarUserController(final ObjectMapper mapper, final UsersApiService usersApiService, final UserService userService, final NotificationService notificationService, final ProjectRoleService projectRoleService, final OrganizationRoleService organizationRoleService, final ProjectInviteService projectInviteService, final OrganizationInviteService organizationInviteService, final TokenService tokenService) {
this.mapper = mapper;
this.usersApiService = usersApiService;
this.userService = userService;
@ -88,9 +88,9 @@ public class HangarUserController extends HangarComponent {
@Anyone
@GetMapping("/users/@me")
public ResponseEntity<?> getCurrentUser(HangarAuthenticationToken hangarAuthenticationToken, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) String refreshToken) {
String token;
String name;
public ResponseEntity<?> getCurrentUser(final HangarAuthenticationToken hangarAuthenticationToken, @CookieValue(name = SecurityConfig.REFRESH_COOKIE_NAME, required = false) final String refreshToken) {
final String token;
final String name;
if (hangarAuthenticationToken == null) {
// if we don't have a token, lets see if we can get one via our refresh token
if (refreshToken == null) {
@ -98,10 +98,10 @@ public class HangarUserController extends HangarComponent {
return ResponseEntity.noContent().build();
}
try {
TokenService.RefreshResponse refreshResponse = tokenService.refreshAccessToken(refreshToken);
token = refreshResponse.accessToken();
name = refreshResponse.userTable().getName();
} catch (HangarApiException ex) {
final TokenService.RefreshResponse refreshResponse = this.tokenService.refreshAccessToken(refreshToken);
token = refreshResponse.accessToken();
name = refreshResponse.userTable().getName();
} catch (final HangarApiException ex) {
// no token + no valid refresh token -> no content
System.out.println("getCurrentUser failed: " + ex.getMessage());
return ResponseEntity.noContent().build();
@ -112,7 +112,7 @@ public class HangarUserController extends HangarComponent {
name = hangarAuthenticationToken.getName();
}
// get user
HangarUser user = usersApiService.getUser(name, HangarUser.class);
final HangarUser user = this.usersApiService.getUser(name, HangarUser.class);
user.setAccessToken(token);
return ResponseEntity.ok(user);
}
@ -124,36 +124,36 @@ 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 String userName, @Valid @RequestBody StringContent content) {
UserTable userTable = userService.getUserTable(userName);
public void saveTagline(@PathVariable final String userName, @RequestBody final @Valid StringContent content) {
final UserTable userTable = this.userService.getUserTable(userName);
if (userTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
if (content.getContent().length() > config.user.maxTaglineLen()) {
if (content.getContent().length() > this.config.user.maxTaglineLen()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "author.error.invalidTagline");
}
String oldTagline = userTable.getTagline() == null ? "" : userTable.getTagline();
final String oldTagline = userTable.getTagline() == null ? "" : userTable.getTagline();
userTable.setTagline(content.getContent());
userService.updateUser(userTable);
actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), userTable.getTagline(), oldTagline));
this.userService.updateUser(userTable);
this.actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), userTable.getTagline(), oldTagline));
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.EDIT_OWN_USER_SETTINGS)
@PostMapping("/users/{userName}/settings/resetTagline")
public void resetTagline(@PathVariable String userName) {
UserTable userTable = userService.getUserTable(userName);
public void resetTagline(@PathVariable final String userName) {
final UserTable userTable = this.userService.getUserTable(userName);
if (userTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
String oldTagline = userTable.getTagline();
final String oldTagline = userTable.getTagline();
if (oldTagline == null) {
throw new HangarApiException(HttpStatus.BAD_REQUEST);
}
userTable.setTagline(null);
userService.updateUser(userTable);
actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), "", oldTagline));
this.userService.updateUser(userTable);
this.actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), "", oldTagline));
}
@Unlocked
@ -161,18 +161,18 @@ public class HangarUserController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 2)
@PermissionRequired(NamedPermission.EDIT_OWN_USER_SETTINGS)
@PostMapping("/users/{userName}/settings/")
public void saveSettings(@PathVariable String userName, @RequestBody UserSettings settings, HttpServletResponse response) {
UserTable userTable = userService.getUserTable(userName);
public void saveSettings(@PathVariable final String userName, @RequestBody final UserSettings settings, final HttpServletResponse response) {
final UserTable userTable = this.userService.getUserTable(userName);
if (userTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
userTable.setLanguage(settings.getLanguage());
userTable.setTheme(settings.getTheme());
// TODO user action logging
userService.updateUser(userTable);
this.userService.updateUser(userTable);
if (config.sso.enabled()) {
userService.updateSSO(userTable.getUuid(), new Traits(userTable.getEmail(), null, null, settings.getLanguage(), userTable.getName(), settings.getTheme()));
if (this.config.sso.enabled()) {
this.userService.updateSSO(userTable.getUuid(), new Traits(userTable.getEmail(), null, null, settings.getLanguage(), userTable.getName(), settings.getTheme()));
}
setThemeCookie(settings, response);
@ -182,12 +182,12 @@ public class HangarUserController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@RateLimit(overdraft = 5, refillTokens = 2)
@PostMapping("/users/anon/settings/")
public void saveSettings(@RequestBody UserSettings settings, HttpServletResponse response) {
public void saveSettings(@RequestBody final UserSettings settings, final HttpServletResponse response) {
setThemeCookie(settings, response);
}
private static void setThemeCookie(UserSettings settings, HttpServletResponse response) {
Cookie cookie = new Cookie("HANGAR_theme", settings.getTheme());
private static void setThemeCookie(final UserSettings settings, final HttpServletResponse response) {
final Cookie cookie = new Cookie("HANGAR_theme", settings.getTheme());
cookie.setPath("/");
cookie.setMaxAge((int) (60 * 60 * 24 * 356.24 * 1000));
// TODO make sure this cookie is cross hangar and auth
@ -195,77 +195,77 @@ public class HangarUserController extends HangarComponent {
}
@GetMapping("/recentnotifications")
public ResponseEntity<List<HangarNotification>> getRecentNotifications(@RequestParam int amount) {
return ResponseEntity.ok(notificationService.getRecentNotifications(amount));
public ResponseEntity<List<HangarNotification>> getRecentNotifications(@RequestParam final int amount) {
return ResponseEntity.ok(this.notificationService.getRecentNotifications(amount));
}
@GetMapping(path = "/notifications", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PaginatedResult<HangarNotification>> getNotifications(@NotNull RequestPagination pagination) {
return ResponseEntity.ok(notificationService.getNotifications(pagination, null));
public ResponseEntity<PaginatedResult<HangarNotification>> getNotifications(final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.notificationService.getNotifications(pagination, null));
}
@GetMapping(path = "/readnotifications", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PaginatedResult<HangarNotification>> getReadNotifications(@NotNull RequestPagination pagination) {
return ResponseEntity.ok(notificationService.getNotifications(pagination, true));
public ResponseEntity<PaginatedResult<HangarNotification>> getReadNotifications(final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.notificationService.getNotifications(pagination, true));
}
@GetMapping(path = "/unreadnotifications", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PaginatedResult<HangarNotification>> getUnreadNotifications(@NotNull RequestPagination pagination) {
return ResponseEntity.ok(notificationService.getNotifications(pagination, false));
public ResponseEntity<PaginatedResult<HangarNotification>> getUnreadNotifications(final @NotNull RequestPagination pagination) {
return ResponseEntity.ok(this.notificationService.getNotifications(pagination, false));
}
@GetMapping("/unreadcount")
public ResponseEntity<Long> getUnreadNotifications() {
return ResponseEntity.ok(notificationService.getUnreadNotifications());
return ResponseEntity.ok(this.notificationService.getUnreadNotifications());
}
@Unlocked
@PostMapping("/markallread")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void markNotificationAsRead() {
notificationService.markNotificationsAsRead();
this.notificationService.markNotificationsAsRead();
}
@Unlocked
@PostMapping("/notifications/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void markNotificationAsRead(@PathVariable long id) {
if (!notificationService.markNotificationAsRead(id)) {
public void markNotificationAsRead(@PathVariable final long id) {
if (!this.notificationService.markNotificationAsRead(id)) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
}
@GetMapping("/invites")
public ResponseEntity<ObjectNode> getUserInvites() {
ObjectNode invites = mapper.createObjectNode();
invites.set(InviteType.PROJECT.toString(), mapper.valueToTree(projectInviteService.getProjectInvites()));
invites.set(InviteType.ORGANIZATION.toString(), mapper.valueToTree(organizationInviteService.getOrganizationInvites()));
final ObjectNode invites = this.mapper.createObjectNode();
invites.set(HangarInvite.InviteType.PROJECT.toString(), this.mapper.valueToTree(this.projectInviteService.getProjectInvites()));
invites.set(HangarInvite.InviteType.ORGANIZATION.toString(), this.mapper.valueToTree(this.organizationInviteService.getOrganizationInvites()));
return ResponseEntity.ok(invites);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping("/read-prompt/{prompt}")
public void readPrompt(@PathVariable Prompt prompt) {
userService.markPromptRead(prompt);
public void readPrompt(@PathVariable final Prompt prompt) {
this.userService.markPromptRead(prompt);
}
@Unlocked
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping("/invites/project/{id}/{status}")
public void updateProjectInviteStatus(@PathVariable long id, @PathVariable InviteStatus status) {
updateRole(projectRoleService, projectInviteService, id, status);
public void updateProjectInviteStatus(@PathVariable final long id, @PathVariable final InviteStatus status) {
this.updateRole(this.projectRoleService, this.projectInviteService, id, status);
}
@Unlocked
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping("/invites/organization/{id}/{status}")
public void updateOrganizationInviteStatus(@PathVariable long id, @PathVariable InviteStatus status) {
updateRole(organizationRoleService, organizationInviteService, id, status);
public void updateOrganizationInviteStatus(@PathVariable final long id, @PathVariable final InviteStatus status) {
this.updateRole(this.organizationRoleService, this.organizationInviteService, id, status);
}
private <RT extends ExtendedRoleTable<? extends Role<RT>, ?>, RS extends RoleService<RT, ?, ?>, IS extends InviteService<?, ?, RT, ?>> void updateRole(RS roleService, IS inviteService, long id, InviteStatus status) {
RT table = roleService.getRole(id);
private <RT extends ExtendedRoleTable<? extends Role<RT>, ?>, RS extends RoleService<RT, ?, ?>, IS extends InviteService<?, ?, RT, ?>> void updateRole(final RS roleService, final IS inviteService, final long id, final InviteStatus status) {
final RT table = roleService.getRole(id);
if (table == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}

View File

@ -85,8 +85,8 @@ public class OrganizationController extends HangarComponent {
}
@GetMapping(value = "/org/{orgName}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<HangarOrganization> getOrganization(@PathVariable String orgName) {
return ResponseEntity.ok(organizationService.getHangarOrganization(orgName));
public ResponseEntity<HangarOrganization> getOrganization(@PathVariable final String orgName) {
return ResponseEntity.ok(this.organizationService.getHangarOrganization(orgName));
}
@Unlocked
@ -94,12 +94,12 @@ 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 String orgName, @Valid @RequestBody EditMembersForm.Member<OrganizationRole> member) {
OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
public void addOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
if (organizationTable == null) {
throw new HangarApiException("Org " + orgName + " doesn't exist");
}
inviteService.sendInvite(member, organizationTable);
this.inviteService.sendInvite(member, organizationTable);
}
@Unlocked
@ -107,9 +107,9 @@ 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 String orgName, @Valid @RequestBody EditMembersForm.Member<OrganizationRole> member) {
OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
memberService.editMember(member, organizationTable);
public void editOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.memberService.editMember(member, organizationTable);
}
@Unlocked
@ -117,18 +117,18 @@ 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 String orgName, @Valid @RequestBody EditMembersForm.Member<OrganizationRole> member) {
OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
memberService.removeMember(member, organizationTable);
public void removeOrganizationMember(@PathVariable final String orgName, @RequestBody final @Valid EditMembersForm.Member<OrganizationRole> member) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.memberService.removeMember(member, organizationTable);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.IS_SUBJECT_MEMBER, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/members/leave", consumes = MediaType.APPLICATION_JSON_VALUE)
public void leaveOrganization(@PathVariable String orgName) {
OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
memberService.leave(organizationTable);
public void leaveOrganization(@PathVariable final String orgName) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.memberService.leave(organizationTable);
}
@Unlocked
@ -136,26 +136,26 @@ 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 String orgName, @Valid @RequestBody StringContent nameContent) {
final OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
inviteService.sendTransferRequest(nameContent.getContent(), organizationTable);
public void transferOrganization(@PathVariable final String orgName, @RequestBody final @Valid StringContent nameContent) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.inviteService.sendTransferRequest(nameContent.getContent(), organizationTable);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.IS_SUBJECT_OWNER, args = "{#orgName}")
@PostMapping(path = "/org/{orgName}/canceltransfer", consumes = MediaType.APPLICATION_JSON_VALUE)
public void cancelOrganizationTransfer(@PathVariable String orgName) {
final OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
inviteService.cancelTransferRequest(organizationTable);
public void cancelOrganizationTransfer(@PathVariable final String orgName) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.inviteService.cancelTransferRequest(organizationTable);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@RateLimit(overdraft = 3, refillTokens = 1, refillSeconds = 60)
@PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public void create(@Valid @RequestBody CreateOrganizationForm createOrganizationForm) {
organizationFactory.createOrganization(createOrganizationForm.getName());
public void create(@RequestBody final @Valid CreateOrganizationForm createOrganizationForm) {
this.organizationFactory.createOrganization(createOrganizationForm.getName());
}
@Unlocked
@ -163,9 +163,9 @@ 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 String orgName, @RequestBody @Valid StringContent content) {
final OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
organizationFactory.deleteOrganization(organizationTable, content.getContent());
public void delete(@PathVariable final String orgName, @RequestBody final @Valid StringContent content) {
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
this.organizationFactory.deleteOrganization(organizationTable, content.getContent());
}
@Unlocked
@ -173,19 +173,19 @@ 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 String orgName, @Valid @RequestBody StringContent content) {
UserTable userTable = userService.getUserTable(orgName);
OrganizationTable organizationTable = organizationService.getOrganizationTable(orgName);
public void saveTagline(@PathVariable final String orgName, @RequestBody final @Valid StringContent content) {
final UserTable userTable = this.userService.getUserTable(orgName);
final OrganizationTable organizationTable = this.organizationService.getOrganizationTable(orgName);
if (userTable == null || organizationTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
if (content.getContent().length() > config.user.maxTaglineLen()) {
if (content.getContent().length() > this.config.user.maxTaglineLen()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "author.error.invalidTagline");
}
String oldTagline = userTable.getTagline() == null ? "" : userTable.getTagline();
final String oldTagline = userTable.getTagline() == null ? "" : userTable.getTagline();
userTable.setTagline(content.getContent());
userService.updateUser(userTable);
actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), userTable.getTagline(), oldTagline));
this.userService.updateUser(userTable);
this.actionLogger.user(LogAction.USER_TAGLINE_CHANGED.create(UserContext.of(userTable.getId()), userTable.getTagline(), oldTagline));
}
@Unlocked
@ -193,30 +193,30 @@ public class OrganizationController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PermissionRequired(type = PermissionType.ORGANIZATION, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#orgName}")
@PostMapping(value = "/org/{orgName}/settings/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void changeAvatar(@PathVariable String orgName, @RequestParam MultipartFile avatar) throws IOException {
authenticationService.changeAvatar(orgName, avatar);
public void changeAvatar(@PathVariable final String orgName, @RequestParam final MultipartFile avatar) throws IOException {
this.authenticationService.changeAvatar(orgName, avatar);
}
@Anyone
@GetMapping(path = "/{user}/userOrganizations", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, OrganizationRoleTable>> getUserOrganizationRoles(@PathVariable String user) {
public ResponseEntity<Map<String, OrganizationRoleTable>> getUserOrganizationRoles(@PathVariable final String user) {
boolean includeHidden = false;
Optional<HangarPrincipal> principal = getOptionalHangarPrincipal();
final Optional<HangarPrincipal> principal = this.getOptionalHangarPrincipal();
if (principal.isPresent()) {
includeHidden = principal.get().getName().equals(user);
if (!includeHidden) {
includeHidden = principal.get().isAllowedGlobal(Permission.SeeHidden);
}
}
return ResponseEntity.ok(organizationService.getUserOrganizationRoles(user, includeHidden));
return ResponseEntity.ok(this.organizationService.getUserOrganizationRoles(user, includeHidden));
}
@LoggedIn
@GetMapping(path = "/{user}/userOrganizationsVisibility", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Boolean>> getUserOrganizationMembershipVisibility(@PathVariable String user) {
HangarPrincipal principal = getHangarPrincipal();
public ResponseEntity<Map<String, Boolean>> getUserOrganizationMembershipVisibility(@PathVariable final String user) {
final HangarPrincipal principal = this.getHangarPrincipal();
if (principal.getName().equals(user) || principal.isAllowedGlobal(Permission.SeeHidden)) {
return ResponseEntity.ok(memberService.getUserOrganizationMembershipVisibility(user));
return ResponseEntity.ok(this.memberService.getUserOrganizationMembershipVisibility(user));
}
throw new HangarApiException(HttpStatus.BAD_REQUEST);
}
@ -224,11 +224,11 @@ public class OrganizationController extends HangarComponent {
@Unlocked
@PostMapping(path = "/{org}/userOrganizationsVisibility", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.ACCEPTED)
public void changeUserOrganizationMembershipVisibility(@RequestParam boolean hidden, @PathVariable String org) {
OrganizationTable table = organizationService.getOrganizationTable(org);
public void changeUserOrganizationMembershipVisibility(@RequestParam final boolean hidden, @PathVariable final String org) {
final OrganizationTable table = this.organizationService.getOrganizationTable(org);
if (table == null) {
throw new HangarApiException();
}
memberService.setMembershipVisibility(table.getOrganizationId(), getHangarPrincipal().getUserId(), hidden);
this.memberService.setMembershipVisibility(table.getOrganizationId(), this.getHangarPrincipal().getUserId(), hidden);
}
}

View File

@ -1,31 +1,29 @@
package io.papermc.hangar.controller.internal;
import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
import io.papermc.hangar.service.internal.PaypalService;
import java.net.URISyntaxException;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.net.URISyntaxException;
import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.service.internal.PaypalService;
@Controller
@RequestMapping("/api/internal/paypal")
public class PaypalController extends HangarComponent {
private final PaypalService paypalService;
public PaypalController(PaypalService paypalService) {
public PaypalController(final PaypalService paypalService) {
this.paypalService = paypalService;
}
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 3)
@PostMapping("/ipn")
public ResponseEntity<Object> ipn(@RequestBody String ipn) throws URISyntaxException {
paypalService.handle(ipn);
public ResponseEntity<Object> ipn(@RequestBody final String ipn) throws URISyntaxException {
this.paypalService.handle(ipn);
return ResponseEntity.ok().build();
}

View File

@ -9,11 +9,12 @@ 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -22,9 +23,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
import java.util.List;
@Unlocked
@Controller
@RateLimit(path = "review")
@ -35,61 +33,61 @@ public class ReviewController {
private final ReviewService reviewService;
@Autowired
public ReviewController(ReviewService reviewService) {
public ReviewController(final ReviewService reviewService) {
this.reviewService = reviewService;
}
@GetMapping(path = "/{versionId}/reviews", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<HangarReview>> getVersionReviews(@PathVariable long versionId) {
return ResponseEntity.ok(reviewService.getHangarReviews(versionId));
public ResponseEntity<List<HangarReview>> getVersionReviews(@PathVariable final long versionId) {
return ResponseEntity.ok(this.reviewService.getHangarReviews(versionId));
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/{versionId}/reviews/start", consumes = MediaType.APPLICATION_JSON_VALUE)
public void startVersionReview(@PathVariable long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.startReview(versionId, message);
public void startVersionReview(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.addReviewMessage(versionId, message);
public void addVersionReviewMessage(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.stopReview(versionId, message);
public void stopVersionReview(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.reopenReview(versionId, message, ReviewAction.REOPEN);
public void reopenVersionReview(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.approveReview(versionId, message, ReviewState.REVIEWED, ReviewAction.APPROVE);
public void approveVersionReview(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.approveReview(versionId, message, ReviewState.PARTIALLY_REVIEWED, ReviewAction.PARTIALLY_APPROVE);
public void approvePartialVersionReview(@PathVariable final long versionId, @RequestBody final @Valid 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 long versionId, @Valid @RequestBody ReviewMessage message) {
reviewService.undoApproval(versionId, message);
public void undoApproval(@PathVariable final long versionId, @RequestBody final @Valid ReviewMessage message) {
this.reviewService.undoApproval(versionId, message);
}
}

View File

@ -19,7 +19,6 @@ 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.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.internal.versions.DownloadService;
import io.papermc.hangar.service.internal.versions.PinnedVersionService;
import io.papermc.hangar.service.internal.versions.VersionDependencyService;
@ -61,7 +60,7 @@ public class VersionController extends HangarComponent {
private final PinnedVersionService pinnedVersionService;
@Autowired
public VersionController(VersionFactory versionFactory, VersionService versionService, VersionDependencyService versionDependencyService, DownloadService downloadService, final PinnedVersionService pinnedVersionService) {
public VersionController(final VersionFactory versionFactory, final VersionService versionService, final VersionDependencyService versionDependencyService, final DownloadService downloadService, final PinnedVersionService pinnedVersionService) {
this.versionFactory = versionFactory;
this.versionService = versionService;
this.versionDependencyService = versionDependencyService;
@ -69,22 +68,22 @@ public class VersionController extends HangarComponent {
this.pinnedVersionService = pinnedVersionService;
}
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@GetMapping(path = "/version/{author}/{slug}/versions/{versionString}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<HangarVersion> getVersion(@PathVariable String author, @PathVariable String slug, @PathVariable String versionString) {
return ResponseEntity.ok(versionService.getHangarVersion(author, slug, versionString));
public ResponseEntity<HangarVersion> getVersion(@PathVariable final String author, @PathVariable final String slug, @PathVariable final String versionString) {
return ResponseEntity.ok(this.versionService.getHangarVersion(author, slug, versionString));
}
@Unlocked
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 5)
@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") long projectId,
@RequestPart(required = false) @Size(max = 3, message = "version.new.error.invalidNumOfPlatforms") List<@Valid MultipartFile> files,
@RequestPart @Size(min = 1, max = 3, message = "version.new.error.invalidNumOfPlatforms") List<@Valid MultipartFileOrUrl> data,
@RequestPart @NotBlank String channel) {
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) {
// Use separate lists to hack around multipart form data limitations
return ResponseEntity.ok(versionFactory.createPendingVersion(projectId, data, files, channel));
return ResponseEntity.ok(this.versionFactory.createPendingVersion(projectId, data, files, channel));
}
@Unlocked
@ -92,8 +91,8 @@ 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") long projectId, @RequestBody @Valid PendingVersion pendingVersion) {
versionFactory.publishPendingVersion(projectId, pendingVersion);
public void createVersion(@PathVariable("id") final long projectId, @RequestBody final @Valid PendingVersion pendingVersion) {
this.versionFactory.publishPendingVersion(projectId, pendingVersion);
}
@Unlocked
@ -101,20 +100,20 @@ 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 long projectId, @PathVariable long versionId, @Valid @RequestBody StringContent stringContent) {
if (stringContent.getContent().length() > config.pages.maxLen()) {
public void saveDescription(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid StringContent stringContent) {
if (stringContent.getContent().length() > this.config.pages.maxLen()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.maxLength");
}
ProjectVersionTable projectVersionTable = versionService.getProjectVersionTable(versionId);
final ProjectVersionTable projectVersionTable = this.versionService.getProjectVersionTable(versionId);
if (projectVersionTable == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND);
}
String oldDesc = projectVersionTable.getDescription();
String newDesc = stringContent.getContent().trim();
final String oldDesc = projectVersionTable.getDescription();
final String newDesc = stringContent.getContent().trim();
projectVersionTable.setDescription(newDesc);
versionService.updateProjectVersionTable(projectVersionTable);
actionLogger.version(LogAction.VERSION_DESCRIPTION_CHANGED.create(VersionContext.of(projectId, versionId), newDesc, oldDesc));
this.versionService.updateProjectVersionTable(projectVersionTable);
this.actionLogger.version(LogAction.VERSION_DESCRIPTION_CHANGED.create(VersionContext.of(projectId, versionId), newDesc, oldDesc));
}
@Unlocked
@ -122,8 +121,8 @@ 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 long projectId, @PathVariable long versionId, @Valid @RequestBody UpdatePlatformVersions updatePlatformVersions) {
versionDependencyService.updateVersionPlatformVersions(projectId, versionId, updatePlatformVersions);
public void savePlatformVersions(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid UpdatePlatformVersions updatePlatformVersions) {
this.versionDependencyService.updateVersionPlatformVersions(projectId, versionId, updatePlatformVersions);
}
@Unlocked
@ -131,8 +130,8 @@ 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 long projectId, @PathVariable long versionId, @Valid @RequestBody UpdatePluginDependencies updatePluginDependencies) {
versionDependencyService.updateVersionPluginDependencies(projectId, versionId, updatePluginDependencies);
public void savePluginDependencies(@PathVariable final long projectId, @PathVariable final long versionId, @RequestBody final @Valid UpdatePluginDependencies updatePluginDependencies) {
this.versionDependencyService.updateVersionPluginDependencies(projectId, versionId, updatePluginDependencies);
}
@Unlocked
@ -153,40 +152,42 @@ 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 long projectId, @PathVariable("versionId") ProjectVersionTable version, @RequestBody @Valid StringContent commentContent) {
versionService.softDeleteVersion(projectId, version, commentContent.getContent());
public void softDeleteVersion(@PathVariable final long projectId, @PathVariable("versionId") final ProjectVersionTable version, @RequestBody final @Valid StringContent commentContent) {
this.versionService.softDeleteVersion(projectId, version, commentContent.getContent());
}
@Unlocked
@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") ProjectTable projectTable, @PathVariable("versionId") ProjectVersionTable projectVersionTable, @RequestBody @Valid StringContent commentContent) {
versionService.hardDeleteVersion(projectTable, projectVersionTable, commentContent.getContent());
public void hardDeleteVersion(@PathVariable("projectId") final ProjectTable projectTable, @PathVariable("versionId") final ProjectVersionTable projectVersionTable, @RequestBody final @Valid StringContent commentContent) {
this.versionService.hardDeleteVersion(projectTable, projectVersionTable, commentContent.getContent());
}
@Unlocked
@ResponseStatus(HttpStatus.CREATED)
@PermissionRequired(NamedPermission.RESTORE_VERSION)
@PostMapping("/version/{projectId}/{versionId}/restore")
public void restoreVersion(@PathVariable long projectId, @PathVariable("versionId") ProjectVersionTable version) {
versionService.restoreVersion(projectId, version);
public void restoreVersion(@PathVariable final long projectId, @PathVariable("versionId") final ProjectVersionTable version) {
this.versionService.restoreVersion(projectId, version);
}
@ResponseBody
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 20)
@VisibilityRequired(type = Type.VERSION, args = "{#author, #slug, #versionString, #platform}") // TODO is platform needed in the visibility check? it's not used in the VisibilityRequiredVoter
@VisibilityRequired(type = VisibilityRequired.Type.VERSION, args = "{#author, #slug, #versionString, #platform}")
// TODO is platform needed in the visibility check? it's not used in the VisibilityRequiredVoter
@GetMapping(path = "/version/{author}/{slug}/versions/{versionString}/{platform}/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Resource download(@PathVariable String author, @PathVariable String slug, @PathVariable String versionString, @PathVariable Platform platform, @RequestParam(required = false) String token) {
return downloadService.getVersionFile(author, slug, versionString, platform, true, token);
public Resource download(@PathVariable final String author, @PathVariable final String slug, @PathVariable final String versionString, @PathVariable final Platform platform, @RequestParam(required = false) final String token) {
return this.downloadService.getVersionFile(author, slug, versionString, platform, true, token);
}
@VisibilityRequired(type = Type.VERSION, args = "{#author, #slug, #versionString, #platform}") // TODO is platform needed in the visibility check? it's not used in the VisibilityRequiredVoter
@VisibilityRequired(type = VisibilityRequired.Type.VERSION, args = "{#author, #slug, #versionString, #platform}")
// TODO is platform needed in the visibility check? it's not used in the VisibilityRequiredVoter
@GetMapping(path = "/version/{author}/{slug}/versions/{versionString}/{platform}/downloadCheck")
public ResponseEntity<String> downloadCheck(@PathVariable String author, @PathVariable String slug, @PathVariable String versionString, @PathVariable Platform platform) {
boolean requiresConfirmation = downloadService.requiresConfirmation(author, slug, versionString, platform);
public ResponseEntity<String> downloadCheck(@PathVariable final String author, @PathVariable final String slug, @PathVariable final String versionString, @PathVariable final Platform platform) {
final boolean requiresConfirmation = this.downloadService.requiresConfirmation(author, slug, versionString, platform);
if (requiresConfirmation) {
String token = downloadService.createConfirmationToken(author, slug, versionString);
final String token = this.downloadService.createConfirmationToken(author, slug, versionString);
if (token == null) {
// null means we already had confirmed, no reason to confirm again
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();

View File

@ -10,6 +10,7 @@ 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.ActivityService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
@ -18,8 +19,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Unlocked
@Controller
@ResponseBody
@ -31,23 +30,23 @@ public class AdminActivityController extends HangarComponent {
private final ActivityService activityService;
@Autowired
public AdminActivityController(ActivityService activityService) {
public AdminActivityController(final ActivityService activityService) {
this.activityService = activityService;
}
@GetMapping("/flags")
public List<FlagActivity> getFlagActivity(@PathVariable UserTable user) {
public List<FlagActivity> getFlagActivity(@PathVariable final UserTable user) {
if (user.isOrganization()) {
throw new HangarApiException("userActivity.error.isOrg");
}
return activityService.getFlagActivity(user);
return this.activityService.getFlagActivity(user);
}
@GetMapping("/reviews")
public List<ReviewActivity> getReviewActivity(@PathVariable UserTable user) {
public List<ReviewActivity> getReviewActivity(@PathVariable final UserTable user) {
if (user.isOrganization()) {
throw new HangarApiException("userActivity.error.isOrg");
}
return activityService.getReviewActivity(user);
return this.activityService.getReviewActivity(user);
}
}

View File

@ -31,7 +31,7 @@ public class AdminApprovalController extends HangarComponent {
private final ObjectMapper mapper;
@Autowired
public AdminApprovalController(ProjectAdminService projectAdminService, ReviewService reviewService, ObjectMapper mapper) {
public AdminApprovalController(final ProjectAdminService projectAdminService, final ReviewService reviewService, final ObjectMapper mapper) {
this.projectAdminService = projectAdminService;
this.reviewService = reviewService;
this.mapper = mapper;
@ -39,29 +39,29 @@ public class AdminApprovalController extends HangarComponent {
@GetMapping("/versions")
public ResponseEntity<ObjectNode> getReviewQueue() {
List<HangarReviewQueueEntry> underReviewEntries = reviewService.getReviewQueue(ReviewState.UNDER_REVIEW);
List<HangarReviewQueueEntry> notStartedEntries = reviewService.getReviewQueue(ReviewState.UNREVIEWED);
ObjectNode objectNode = mapper.createObjectNode();
objectNode.set("underReview", mapper.valueToTree(underReviewEntries));
objectNode.set("notStarted", mapper.valueToTree(notStartedEntries));
final List<HangarReviewQueueEntry> underReviewEntries = this.reviewService.getReviewQueue(ReviewState.UNDER_REVIEW);
final List<HangarReviewQueueEntry> notStartedEntries = this.reviewService.getReviewQueue(ReviewState.UNREVIEWED);
final ObjectNode objectNode = this.mapper.createObjectNode();
objectNode.set("underReview", this.mapper.valueToTree(underReviewEntries));
objectNode.set("notStarted", this.mapper.valueToTree(notStartedEntries));
return ResponseEntity.ok(objectNode);
}
@GetMapping("/projects")
public ResponseEntity<ObjectNode> getProjectApprovals() {
ObjectNode objectNode = mapper.createObjectNode();
objectNode.set("needsApproval", mapper.valueToTree(projectAdminService.getProjectsNeedingApproval()));
objectNode.set("waitingProjects", mapper.valueToTree(projectAdminService.getProjectsWaitingForChanges()));
final ObjectNode objectNode = this.mapper.createObjectNode();
objectNode.set("needsApproval", this.mapper.valueToTree(this.projectAdminService.getProjectsNeedingApproval()));
objectNode.set("waitingProjects", this.mapper.valueToTree(this.projectAdminService.getProjectsWaitingForChanges()));
return ResponseEntity.ok(objectNode);
}
@GetMapping("/projectneedingapproval")
public ResponseEntity<Integer> getProjectApprovalQueueSize() {
return ResponseEntity.ok(projectAdminService.getApprovalQueueSize());
return ResponseEntity.ok(this.projectAdminService.getApprovalQueueSize());
}
@GetMapping("/versionsneedingapproval")
public ResponseEntity<Integer> getVersionApprovalQueueSize() {
return ResponseEntity.ok(reviewService.getApprovalQueueSize());
return ResponseEntity.ok(this.reviewService.getApprovalQueueSize());
}
}

View File

@ -33,10 +33,12 @@ 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 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;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
@ -50,10 +52,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
import java.time.LocalDate;
import java.util.List;
@Unlocked
@Controller
@RateLimit(path = "admin")
@ -69,7 +67,7 @@ public class AdminController extends HangarComponent {
private final GlobalRoleService globalRoleService;
@Autowired
public AdminController(PlatformService platformService, StatService statService, HealthService healthService, JobService jobService, UserService userService, ObjectMapper mapper, GlobalRoleService globalRoleService) {
public AdminController(final PlatformService platformService, final StatService statService, final HealthService healthService, final JobService jobService, final UserService userService, final ObjectMapper mapper, final GlobalRoleService globalRoleService) {
this.platformService = platformService;
this.statService = statService;
this.healthService = healthService;
@ -82,14 +80,14 @@ 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 @Valid ChangePlatformVersionsForm form) {
platformService.updatePlatformVersions(form);
public void changePlatformVersions(@RequestBody final @Valid ChangePlatformVersionsForm form) {
this.platformService.updatePlatformVersions(form);
}
@ResponseBody
@PermissionRequired(NamedPermission.VIEW_STATS)
@GetMapping(path = "/stats", produces = MediaType.APPLICATION_JSON_VALUE)
public ArrayNode getStats(@RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate from, @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate to) {
public ArrayNode getStats(@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
if (from == null) {
from = LocalDate.now().minusDays(30);
}
@ -99,26 +97,26 @@ public class AdminController extends HangarComponent {
if (to.isBefore(from)) {
to = from;
}
return mapper.valueToTree(statService.getStats(from, to));
return this.mapper.valueToTree(this.statService.getStats(from, to));
}
@ResponseBody
@PermissionRequired(NamedPermission.VIEW_HEALTH)
@GetMapping(path = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public HealthReport getHealthReport() {
List<UnhealthyProject> noTopicProjects = healthService.getProjectsWithoutTopic();
List<UnhealthyProject> staleProjects = healthService.getStaleProjects();
List<UnhealthyProject> nonPublicProjects = healthService.getNonPublicProjects();
List<MissingFileCheck> missingFiles = healthService.getVersionsWithMissingFiles();
List<JobTable> erroredJobs = jobService.getErroredJobs();
final List<UnhealthyProject> noTopicProjects = this.healthService.getProjectsWithoutTopic();
final List<UnhealthyProject> staleProjects = this.healthService.getStaleProjects();
final List<UnhealthyProject> nonPublicProjects = this.healthService.getNonPublicProjects();
final List<MissingFileCheck> missingFiles = this.healthService.getVersionsWithMissingFiles();
final List<JobTable> erroredJobs = this.jobService.getErroredJobs();
return new HealthReport(noTopicProjects, staleProjects, nonPublicProjects, missingFiles, erroredJobs);
}
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.IS_STAFF)
@PostMapping(value = "/lock-user/{user}/{locked}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void setUserLock(@PathVariable UserTable user, @PathVariable boolean locked, @RequestBody @Valid StringContent comment) {
userService.setLocked(user, locked, comment.getContent());
public void setUserLock(@PathVariable final UserTable user, @PathVariable final boolean locked, @RequestBody final @Valid StringContent comment) {
this.userService.setLocked(user, locked, comment.getContent());
}
@ResponseBody
@ -126,21 +124,21 @@ public class AdminController extends HangarComponent {
@PermissionRequired(NamedPermission.REVIEWER)
@ApplicableFilters({LogActionFilter.class, LogPageFilter.class, LogProjectFilter.class, LogSubjectFilter.class, LogUserFilter.class, LogVersionFilter.class})
// TODO add sorters
public PaginatedResult<HangarLoggedAction> getActionLog(@NotNull @ConfigurePagination(defaultLimit = 50, maxLimit = 100) RequestPagination pagination) {
return actionLogger.getLogs(pagination);
public PaginatedResult<HangarLoggedAction> getActionLog(@ConfigurePagination(defaultLimit = 50, maxLimit = 100) final @NotNull RequestPagination pagination) {
return this.actionLogger.getLogs(pagination);
}
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.EDIT_ALL_USER_SETTINGS)
@PostMapping("/user/{user}/{role}")
public void addRole(@PathVariable UserTable user, @PathVariable String role) {
globalRoleService.addRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
public void addRole(@PathVariable final UserTable user, @PathVariable final String role) {
this.globalRoleService.addRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
}
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.EDIT_ALL_USER_SETTINGS)
@DeleteMapping(value = "/user/{user}/{role}")
public void removeRole(@PathVariable UserTable user, @PathVariable String role) {
globalRoleService.deleteRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
@DeleteMapping("/user/{user}/{role}")
public void removeRole(@PathVariable final UserTable user, @PathVariable final String role) {
this.globalRoleService.deleteRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
}
}

View File

@ -44,57 +44,57 @@ 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 @Valid FlagForm form) {
flagService.createFlag(form.getProjectId(), form.getReason(), form.getComment());
public void flag(@RequestBody final @Valid FlagForm form) {
this.flagService.createFlag(form.getProjectId(), form.getReason(), form.getComment());
}
@Unlocked
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping("/{id}/resolve/{resolve}")
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public void resolve(@PathVariable long id, @PathVariable boolean resolve) {
flagService.markAsResolved(id, resolve);
public void resolve(@PathVariable final long id, @PathVariable final boolean resolve) {
this.flagService.markAsResolved(id, resolve);
}
@ResponseBody
@GetMapping(path = "/{projectId}", produces = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public List<HangarProjectFlag> getFlags(@PathVariable long projectId) {
return flagService.getFlags(projectId);
public List<HangarProjectFlag> getFlags(@PathVariable final long projectId) {
return this.flagService.getFlags(projectId);
}
@ResponseBody
@GetMapping(path = "/resolved", produces = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public PaginatedResult<HangarProjectFlag> getResolvedFlags(@NotNull RequestPagination pagination) {
return flagService.getFlags(pagination, true);
public PaginatedResult<HangarProjectFlag> getResolvedFlags(final @NotNull RequestPagination pagination) {
return this.flagService.getFlags(pagination, true);
}
@ResponseBody
@GetMapping(path = "/unresolved", produces = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public PaginatedResult<HangarProjectFlag> getUnresolvedFlags(@NotNull RequestPagination pagination) {
return flagService.getFlags(pagination, false);
public PaginatedResult<HangarProjectFlag> getUnresolvedFlags(final @NotNull RequestPagination pagination) {
return this.flagService.getFlags(pagination, false);
}
@GetMapping(path = "/unresolvedamount")
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public ResponseEntity<Long> getUnresolvedFlagsQueueSize() {
return ResponseEntity.ok(flagService.getFlagsQueueSize(false));
return ResponseEntity.ok(this.flagService.getFlagsQueueSize(false));
}
@ResponseBody
@GetMapping(path = "/{id}/notifications", produces = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
public List<HangarProjectFlagNotification> getNotifications(@PathVariable long id) {
return flagService.getFlagNotifications(id);
public List<HangarProjectFlagNotification> getNotifications(@PathVariable final long id) {
return this.flagService.getFlagNotifications(id);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
@PostMapping(path = "/{id}/notify", consumes = MediaType.APPLICATION_JSON_VALUE)
public void notifyReportParty(@PathVariable long id, @RequestBody ReportNotificationForm form) {
flagService.notifyParty(id, form.warning(), form.toReporter(), form.content());
public void notifyReportParty(@PathVariable final long id, @RequestBody final ReportNotificationForm form) {
this.flagService.notifyParty(id, form.warning(), form.toReporter(), form.content());
}
}

View File

@ -36,35 +36,35 @@ public class ProjectAdminController extends HangarComponent {
private final ProjectNoteService projectNoteService;
@Autowired
public ProjectAdminController(ProjectAdminService projectAdminService, ProjectNoteService projectNoteService) {
public ProjectAdminController(final ProjectAdminService projectAdminService, final ProjectNoteService projectNoteService) {
this.projectAdminService = projectAdminService;
this.projectNoteService = projectNoteService;
}
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
@GetMapping(path = "/notes/{projectId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<HangarProjectNote>> getProjectNotes(@PathVariable long projectId) {
return ResponseEntity.ok(projectNoteService.getNotes(projectId));
public ResponseEntity<List<HangarProjectNote>> getProjectNotes(@PathVariable final long projectId) {
return ResponseEntity.ok(this.projectNoteService.getNotes(projectId));
}
@ResponseStatus(HttpStatus.CREATED)
@PermissionRequired(NamedPermission.MOD_NOTES_AND_FLAGS)
@PostMapping(path = "/notes/{projectId}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addProjectNote(@PathVariable long projectId, @RequestBody @Valid StringContent content) {
projectNoteService.addNote(projectId, content.getContent());
public void addProjectNote(@PathVariable final long projectId, @RequestBody final @Valid 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 long projectId, @Valid @RequestBody VisibilityChangeForm visibilityChangeForm) {
projectAdminService.changeVisibility(projectId, visibilityChangeForm.getVisibility(), visibilityChangeForm.getComment());
public void changeProjectVisibility(@PathVariable final long projectId, @RequestBody final @Valid VisibilityChangeForm visibilityChangeForm) {
this.projectAdminService.changeVisibility(projectId, visibilityChangeForm.getVisibility(), visibilityChangeForm.getComment());
}
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_PAGE, args = "{#projectId}")
@PostMapping("/visibility/{projectId}/sendforapproval")
public void sendProjectForApproval(@PathVariable long projectId) {
projectAdminService.sendProjectForApproval(projectId);
public void sendProjectForApproval(@PathVariable final long projectId) {
this.projectAdminService.sendProjectForApproval(projectId);
}
}

View File

@ -20,7 +20,6 @@ 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.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.internal.admin.StatService;
import io.papermc.hangar.service.internal.organizations.OrganizationService;
import io.papermc.hangar.service.internal.perms.members.ProjectMemberService;
@ -66,7 +65,7 @@ public class ProjectController extends HangarComponent {
private final PinnedProjectService pinnedProjectService;
@Autowired
public ProjectController(ProjectFactory projectFactory, ProjectService projectService, UserService userService, OrganizationService organizationService, ProjectMemberService projectMemberService, ProjectInviteService projectInviteService, ImageService imageService, StatService statService, final PinnedProjectService pinnedProjectService) {
public ProjectController(final ProjectFactory projectFactory, final ProjectService projectService, final UserService userService, final OrganizationService organizationService, final ProjectMemberService projectMemberService, final ProjectInviteService projectInviteService, final ImageService imageService, final StatService statService, final PinnedProjectService pinnedProjectService) {
this.projectFactory = projectFactory;
this.projectService = projectService;
this.userService = userService;
@ -81,34 +80,34 @@ public class ProjectController extends HangarComponent {
@LoggedIn
@GetMapping("/validateName")
@ResponseStatus(HttpStatus.OK)
public void validateProjectName(@RequestParam long userId, @RequestParam String value) {
projectFactory.checkProjectAvailability(userId, value);
public void validateProjectName(@RequestParam final long userId, @RequestParam final String value) {
this.projectFactory.checkProjectAvailability(userId, value);
}
@LoggedIn
@GetMapping("/possibleOwners")
public ResponseEntity<List<PossibleProjectOwner>> possibleProjectCreators() {
List<PossibleProjectOwner> possibleProjectOwners = organizationService.getOrganizationTablesWithPermission(getHangarPrincipal().getId(), Permission.CreateProject).stream().map(PossibleProjectOwner::new).collect(Collectors.toList());
possibleProjectOwners.add(0, new PossibleProjectOwner(getHangarPrincipal()));
final List<PossibleProjectOwner> possibleProjectOwners = this.organizationService.getOrganizationTablesWithPermission(this.getHangarPrincipal().getId(), Permission.CreateProject).stream().map(PossibleProjectOwner::new).collect(Collectors.toList());
possibleProjectOwners.add(0, new PossibleProjectOwner(this.getHangarPrincipal()));
return ResponseEntity.ok(possibleProjectOwners);
}
@Unlocked
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createProject(@RequestBody @Valid NewProjectForm newProject) {
ProjectTable projectTable = projectFactory.createProject(newProject);
public ResponseEntity<String> createProject(@RequestBody final @Valid NewProjectForm newProject) {
final ProjectTable projectTable = this.projectFactory.createProject(newProject);
// need to do this here, outside the transactional
projectService.refreshHomeProjects();
this.projectService.refreshHomeProjects();
return ResponseEntity.ok(projectTable.getUrl());
}
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@GetMapping("/project/{author}/{slug}")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<HangarProject> getHangarProject(@PathVariable String author, @PathVariable String slug) {
HangarProject hangarProject = projectService.getHangarProject(author, slug);
statService.addProjectView(hangarProject);
public ResponseEntity<HangarProject> getHangarProject(@PathVariable final String author, @PathVariable final String slug) {
final HangarProject hangarProject = this.projectService.getHangarProject(author, slug);
this.statService.addProjectView(hangarProject);
return ResponseEntity.ok(hangarProject);
}
@ -117,8 +116,8 @@ 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 String author, @PathVariable String slug, @Valid @RequestBody ProjectSettingsForm settingsForm) {
projectService.saveSettings(author, slug, settingsForm);
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid ProjectSettingsForm settingsForm) {
this.projectService.saveSettings(author, slug, settingsForm);
}
@Unlocked
@ -126,11 +125,11 @@ 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 String author, @PathVariable String slug, @RequestBody @Valid StringContent content) {
if (content.getContent().length() > config.projects.maxSponsorsLen()) {
public void saveProjectSettings(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent content) {
if (content.getContent().length() > this.config.projects.maxSponsorsLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
projectService.saveSponsors(author, slug, content);
this.projectService.saveSponsors(author, slug, content);
}
@Unlocked
@ -139,8 +138,8 @@ public class ProjectController extends HangarComponent {
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 60)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/saveIcon", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String saveProjectIcon(@PathVariable String author, @PathVariable String slug, @RequestParam MultipartFile projectIcon) {
return projectService.saveIcon(author, slug, projectIcon);
public String saveProjectIcon(@PathVariable final String author, @PathVariable final String slug, @RequestParam final MultipartFile projectIcon) {
return this.projectService.saveIcon(author, slug, projectIcon);
}
@Unlocked
@ -148,16 +147,16 @@ public class ProjectController extends HangarComponent {
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.EDIT_SUBJECT_SETTINGS, args = "{#author, #slug}")
@PostMapping("/project/{author}/{slug}/resetIcon")
public String resetProjectIcon(@PathVariable String author, @PathVariable String slug) {
return projectService.resetIcon(author, slug);
public String resetProjectIcon(@PathVariable final String author, @PathVariable final String slug) {
return this.projectService.resetIcon(author, slug);
}
@Unlocked
@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 String author, @PathVariable String slug, @Valid @RequestBody StringContent nameContent) {
return ResponseEntity.ok(projectFactory.renameProject(author, slug, nameContent.getContent()));
public ResponseEntity<String> renameProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent nameContent) {
return ResponseEntity.ok(this.projectFactory.renameProject(author, slug, nameContent.getContent()));
}
@Unlocked
@ -165,18 +164,18 @@ 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 String author, @PathVariable String slug, @Valid @RequestBody StringContent nameContent) {
final ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectInviteService.sendTransferRequest(nameContent.getContent(), projectTable);
public void transferProject(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid StringContent nameContent) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectInviteService.sendTransferRequest(nameContent.getContent(), projectTable);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_OWNER, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/canceltransfer", consumes = MediaType.APPLICATION_JSON_VALUE)
public void cancelProjectTransfer(@PathVariable String author, @PathVariable String slug) {
final ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectInviteService.cancelTransferRequest(projectTable);
public void cancelProjectTransfer(@PathVariable final String author, @PathVariable final String slug) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectInviteService.cancelTransferRequest(projectTable);
}
@Unlocked
@ -184,9 +183,9 @@ 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 String author, @PathVariable String slug, @Valid @RequestBody EditMembersForm.Member<ProjectRole> member) {
ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectInviteService.sendInvite(member, projectTable);
public void addProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectInviteService.sendInvite(member, projectTable);
}
@Unlocked
@ -194,45 +193,45 @@ 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 String author, @PathVariable String slug, @Valid @RequestBody EditMembersForm.Member<ProjectRole> member) {
ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectMemberService.editMember(member, projectTable);
public void editProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectMemberService.editMember(member, projectTable);
}
@Unlocked
@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 String author, @PathVariable String slug, @Valid @RequestBody EditMembersForm.Member<ProjectRole> member) {
ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectMemberService.removeMember(member, projectTable);
public void removeProjectMember(@PathVariable final String author, @PathVariable final String slug, @RequestBody final @Valid EditMembersForm.Member<ProjectRole> member) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectMemberService.removeMember(member, projectTable);
}
@Unlocked
@ResponseStatus(HttpStatus.OK)
@PermissionRequired(type = PermissionType.PROJECT, perms = NamedPermission.IS_SUBJECT_MEMBER, args = "{#author, #slug}")
@PostMapping(path = "/project/{author}/{slug}/members/leave", consumes = MediaType.APPLICATION_JSON_VALUE)
public void leaveProject(@PathVariable String author, @PathVariable String slug) {
ProjectTable projectTable = projectService.getProjectTable(author, slug);
projectMemberService.leave(projectTable);
public void leaveProject(@PathVariable final String author, @PathVariable final String slug) {
final ProjectTable projectTable = this.projectService.getProjectTable(author, slug);
this.projectMemberService.leave(projectTable);
}
@Unlocked
@VisibilityRequired(type = Type.PROJECT, args = "{#projectId}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#projectId}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 10)
@PostMapping("/project/{id}/star/{state}")
@ResponseStatus(HttpStatus.OK)
public void setProjectStarred(@PathVariable("id") long projectId, @PathVariable boolean state) {
userService.toggleStarred(projectId, state);
public void setProjectStarred(@PathVariable("id") final long projectId, @PathVariable final boolean state) {
this.userService.toggleStarred(projectId, state);
}
@Unlocked
@VisibilityRequired(type = Type.PROJECT, args = "{#projectId}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#projectId}")
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 10)
@PostMapping("/project/{id}/watch/{state}")
@ResponseStatus(HttpStatus.OK)
public void setProjectWatching(@PathVariable("id") long projectId, @PathVariable boolean state) {
userService.toggleWatching(projectId, state);
public void setProjectWatching(@PathVariable("id") final long projectId, @PathVariable final boolean state) {
this.userService.toggleWatching(projectId, state);
}
@Unlocked
@ -242,9 +241,9 @@ public class ProjectController extends HangarComponent {
@PostMapping(path = "/project/{id}/pin/{state}")
public void setPinnedStatus(@PathVariable final long id, @PathVariable final boolean state) {
if (state) {
pinnedProjectService.addPinnedProject(getHangarUserId(), id);
this.pinnedProjectService.addPinnedProject(this.getHangarUserId(), id);
} else {
pinnedProjectService.removePinnedProject(getHangarUserId(), id);
this.pinnedProjectService.removePinnedProject(this.getHangarUserId(), id);
}
}
@ -253,26 +252,26 @@ 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") ProjectTable project, @RequestBody @Valid StringContent commentContent) {
projectFactory.softDelete(project, commentContent.getContent());
public void softDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody final @Valid StringContent commentContent) {
this.projectFactory.softDelete(project, commentContent.getContent());
}
@Unlocked
@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") ProjectTable project, @RequestBody @Valid StringContent commentContent) {
projectFactory.hardDelete(project, commentContent.getContent());
public void hardDeleteProject(@PathVariable("projectId") final ProjectTable project, @RequestBody final @Valid StringContent commentContent) {
this.projectFactory.hardDelete(project, commentContent.getContent());
}
// Can't put visibility required because the browser image requests don't include the JWT needed for authorization
@Anyone
@GetMapping(path = "/project/{author}/{slug}/icon", produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE})
public Object getProjectIcon(@PathVariable String author, @PathVariable String slug) {
public Object getProjectIcon(@PathVariable final String author, @PathVariable final String slug) {
try {
return imageService.getProjectIcon(author, slug);
} catch (InternalHangarException e) {
return new RedirectView(imageService.getUserIcon(author));
return this.imageService.getProjectIcon(author, slug);
} catch (final InternalHangarException e) {
return new RedirectView(this.imageService.getUserIcon(author));
}
}
}

View File

@ -13,7 +13,6 @@ 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.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired.Type;
import io.papermc.hangar.service.ValidationService;
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.service.internal.projects.ProjectPageService;
@ -46,7 +45,7 @@ public class ProjectPageController extends HangarComponent {
private final ValidationService validationService;
@Autowired
public ProjectPageController(ProjectPageService projectPageService, MarkdownService markdownService, ValidationService validationService) {
public ProjectPageController(final ProjectPageService projectPageService, final MarkdownService markdownService, final ValidationService validationService) {
this.projectPageService = projectPageService;
this.markdownService = markdownService;
this.validationService = validationService;
@ -55,45 +54,45 @@ 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 @Valid StringContent content) {
if (content.getContent().length() > config.projects.contentMaxLen()) {
public ResponseEntity<String> renderMarkdown(@RequestBody final @Valid StringContent content) {
if (content.getContent().length() > this.config.projects.contentMaxLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
return ResponseEntity.ok(markdownService.render(content.getContent()));
return ResponseEntity.ok(this.markdownService.render(content.getContent()));
}
@Unlocked
@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 @Valid StringContent bbCodeContent) {
if (bbCodeContent.getContent().length() > config.projects.maxBBCodeLen()) {
public String convertBBCode(@RequestBody final @Valid StringContent bbCodeContent) {
if (bbCodeContent.getContent().length() > this.config.projects.maxBBCodeLen()) {
throw new HangarApiException("page.new.error.name.maxLength");
}
BBCodeConverter bbCodeConverter = new BBCodeConverter();
final BBCodeConverter bbCodeConverter = new BBCodeConverter();
return bbCodeConverter.convertToMarkdown(bbCodeContent.getContent());
}
@Anyone
@ResponseStatus(HttpStatus.OK)
@GetMapping("/checkName")
public void checkName(@RequestParam long projectId, @RequestParam String name, @RequestParam(required = false) Long parentId) {
validationService.testPageName(name);
projectPageService.checkDuplicateName(projectId, name, parentId);
public void checkName(@RequestParam final long projectId, @RequestParam final String name, @RequestParam(required = false) final Long parentId) {
this.validationService.testPageName(name);
this.projectPageService.checkDuplicateName(projectId, name, parentId);
}
@VisibilityRequired(type = Type.PROJECT, args = "{#author, #slug}")
@VisibilityRequired(type = VisibilityRequired.Type.PROJECT, args = "{#author, #slug}")
@GetMapping(path = "/page/{author}/{slug}/**", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ExtendedProjectPage> getProjectPage(@PathVariable String author, @PathVariable String slug) {
return ResponseEntity.ok(projectPageService.getProjectPage(author, slug, request.getRequestURI()));
public ResponseEntity<ExtendedProjectPage> getProjectPage(@PathVariable final String author, @PathVariable final String slug) {
return ResponseEntity.ok(this.projectPageService.getProjectPage(author, slug, this.request.getRequestURI()));
}
@Unlocked
@RateLimit(overdraft = 5, refillTokens = 1, refillSeconds = 20)
@GetMapping(value = "/list/{projectId}")
@GetMapping("/list/{projectId}")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<Collection<HangarProjectPage>> listProjectPages(@PathVariable final long projectId) {
return ResponseEntity.ok(projectPageService.getProjectPages(projectId).values());
return ResponseEntity.ok(this.projectPageService.getProjectPages(projectId).values());
}
@Unlocked
@ -101,8 +100,8 @@ 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 long projectId, @RequestBody @Valid NewProjectPage newProjectPage) {
return ResponseEntity.ok(projectPageService.createProjectPage(projectId, newProjectPage));
public ResponseEntity<String> createProjectPage(@PathVariable final long projectId, @RequestBody final @Valid NewProjectPage newProjectPage) {
return ResponseEntity.ok(this.projectPageService.createProjectPage(projectId, newProjectPage));
}
@Unlocked
@ -110,15 +109,15 @@ 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 long projectId, @PathVariable long pageId, @RequestBody @Valid StringContent content) {
projectPageService.saveProjectPage(projectId, pageId, content.getContent());
public void saveProjectPage(@PathVariable final long projectId, @PathVariable final long pageId, @RequestBody final @Valid StringContent content) {
this.projectPageService.saveProjectPage(projectId, pageId, content.getContent());
}
@Unlocked
@PermissionRequired(perms = NamedPermission.EDIT_PAGE, type = PermissionType.PROJECT, args = "{#projectId}")
@PostMapping("/delete/{projectId}/{pageId}")
@ResponseStatus(HttpStatus.OK)
public void deleteProjectPage(@PathVariable long projectId, @PathVariable long pageId) {
projectPageService.deleteProjectPage(projectId, pageId);
public void deleteProjectPage(@PathVariable final long projectId, @PathVariable final long pageId) {
this.projectPageService.deleteProjectPage(projectId, pageId);
}
}

View File

@ -1,17 +1,16 @@
package io.papermc.hangar.controller.validations;
import org.springframework.beans.BeanUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.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)
@Retention(RetentionPolicy.RUNTIME)
@ -19,9 +18,13 @@ import java.lang.annotation.Target;
@Documented
public @interface AtLeastOneNotNull {
String[] fieldNames();
String message() default "Must have one non null field";
boolean includeBlankStrings() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class Validator implements ConstraintValidator<AtLeastOneNotNull, Object> {
@ -29,21 +32,21 @@ public @interface AtLeastOneNotNull {
private String[] fieldNames;
@Override
public void initialize(AtLeastOneNotNull constraintAnnotation) {
public void initialize(final AtLeastOneNotNull constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
}
try {
for (String fieldName : fieldNames) {
PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), fieldName);
for (final String fieldName : this.fieldNames) {
final PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), fieldName);
if (propertyDescriptor != null) {
Object property = propertyDescriptor.getReadMethod().invoke(value);
final Object property = propertyDescriptor.getReadMethod().invoke(value);
if (property != null) {
if (property instanceof String) {
return !((String) property).isBlank();
@ -54,7 +57,7 @@ public @interface AtLeastOneNotNull {
}
}
return false;
} catch (Exception e) {
} catch (final Exception e) {
e.printStackTrace();
return false;
}

View File

@ -25,8 +25,11 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
public @interface Validate {
@Language("SpEL")
String SpEL() default "";
String message() default "Invalid field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class ValidateConstraintValidator implements ConstraintValidator<Validate, Object> {
@ -36,14 +39,14 @@ public @interface Validate {
private Expression expression;
@Override
public void initialize(Validate constraintAnnotation) {
ExpressionParser expressionParser = new SpelExpressionParser();
expression = expressionParser.parseExpression(constraintAnnotation.SpEL());
public void initialize(final Validate constraintAnnotation) {
final ExpressionParser expressionParser = new SpelExpressionParser();
this.expression = expressionParser.parseExpression(constraintAnnotation.SpEL());
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Boolean bool = expression.getValue(evaluationContext, value, boolean.class);
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
final Boolean bool = this.expression.getValue(this.evaluationContext, value, boolean.class);
return bool != null && bool;
}
}

View File

@ -1,56 +1,55 @@
package io.papermc.hangar.controller.validations;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import io.papermc.hangar.util.PatternWrapper;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import io.papermc.hangar.util.PatternWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class Validations {
private final Map<String, Pattern> regexCache = new HashMap<>();
public boolean regex(String value, String regex) {
if (isEmpty(value)) return true;
Pattern pattern = regexCache.computeIfAbsent(regex, Pattern::compile);
public boolean regex(final String value, final String regex) {
if (this.isEmpty(value)) return true;
final Pattern pattern = this.regexCache.computeIfAbsent(regex, Pattern::compile);
return pattern.matcher(value).matches();
}
public boolean regex(String value, PatternWrapper regex) {
if (isEmpty(value)) return true;
public boolean regex(final String value, final PatternWrapper regex) {
if (this.isEmpty(value)) return true;
return regex.test(value);
}
public boolean required(String value) {
return !isEmpty(value);
public boolean required(final String value) {
return !this.isEmpty(value);
}
public boolean max(Collection<?> value, int max) {
public boolean max(final Collection<?> value, final int max) {
if (value != null) {
return value.size() <= max;
}
return true;
}
public boolean max(String value, int max) {
public boolean max(final String value, final int max) {
if (value != null) {
return value.length() <= max;
}
return true;
}
public boolean min(String value, int min) {
public boolean min(final String value, final int min) {
if (value != null) {
return value.length() >= min;
}
return true;
}
private boolean isEmpty(String value) {
private boolean isEmpty(final String value) {
return value == null || value.isBlank() || value.isEmpty();
}

View File

@ -6,10 +6,8 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.postgresql.util.PGobject;
import java.util.Map;
import org.postgresql.util.PGobject;
public class JSONB extends PGobject {
@ -18,66 +16,66 @@ public class JSONB extends PGobject {
private transient JsonNode json;
private transient Map<String, String> map;
public JSONB(String value) {
setType(TYPE_STRING);
public JSONB(final String value) {
this.setType(TYPE_STRING);
this.value = value;
parseJson();
this.parseJson();
}
public JSONB(Object value) {
setType(TYPE_STRING);
public JSONB(final Object value) {
this.setType(TYPE_STRING);
try {
this.value = new ObjectMapper().writeValueAsString(value);
} catch (JsonProcessingException e) {
} catch (final JsonProcessingException e) {
e.printStackTrace();
}
parseJson();
this.parseJson();
}
@JsonCreator
public JSONB(JsonNode json) {
setType(TYPE_STRING);
public JSONB(final JsonNode json) {
this.setType(TYPE_STRING);
this.value = json.toString();
this.json = json;
}
public JSONB() {
setType(TYPE_STRING);
this.setType(TYPE_STRING);
}
@JsonValue
public JsonNode getJson() {
return json;
return this.json;
}
public Map<String, String> getMap() {
if (this.map == null) {
try {
this.map = new ObjectMapper().readValue(value, new TypeReference<>() {
this.map = new ObjectMapper().readValue(this.value, new TypeReference<>() {
});
} catch (JsonProcessingException | ClassCastException e) {
} catch (final JsonProcessingException | ClassCastException e) {
e.printStackTrace();
}
}
return map;
return this.map;
}
@Override
public void setValue(String value) {
public void setValue(final String value) {
this.value = value;
parseJson();
this.parseJson();
}
private void parseJson() {
try {
this.json = new ObjectMapper().readTree(value);
} catch (JsonProcessingException | ClassCastException e) {
this.json = new ObjectMapper().readTree(this.value);
} catch (final JsonProcessingException | ClassCastException e) {
e.printStackTrace();
}
}
@Override
public boolean equals(Object obj) {
public boolean equals(final Object obj) {
return super.equals(obj);
}

View File

@ -1,8 +1,7 @@
package io.papermc.hangar.db.customtypes;
import org.postgresql.util.PGobject;
import java.util.Objects;
import org.postgresql.util.PGobject;
public class JobState extends PGobject {
@ -15,18 +14,18 @@ public class JobState extends PGobject {
//
}
public JobState(String value) {
setType("job_state");
public JobState(final String value) {
this.setType("job_state");
this.value = value;
}
@Override
public boolean equals(Object obj) {
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
if (obj == null || this.getClass() != obj.getClass()) return false;
if (!super.equals(obj)) return false;
JobState that = (JobState) obj;
final JobState that = (JobState) obj;
return Objects.equals(this.value, that.value);
}
@ -39,7 +38,7 @@ public class JobState extends PGobject {
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
return result;
}
}

View File

@ -1,9 +1,8 @@
package io.papermc.hangar.db.customtypes;
import com.fasterxml.jackson.annotation.JsonValue;
import org.postgresql.util.PGobject;
import java.util.Objects;
import org.postgresql.util.PGobject;
public class PGLoggedAction extends PGobject {
@ -61,13 +60,13 @@ public class PGLoggedAction extends PGobject {
private String value;
public PGLoggedAction(String value) {
setType("logged_action_type");
public PGLoggedAction(final String value) {
this.setType("logged_action_type");
this.value = value;
}
@Override
public void setValue(String value) {
public void setValue(final String value) {
this.value = value;
}
@ -78,19 +77,19 @@ public class PGLoggedAction extends PGobject {
}
@Override
public boolean equals(Object obj) {
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
if (obj == null || this.getClass() != obj.getClass()) return false;
if (!super.equals(obj)) return false;
PGLoggedAction that = (PGLoggedAction) obj;
final PGLoggedAction that = (PGLoggedAction) obj;
return Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
return result;
}
}

View File

@ -1,9 +1,8 @@
package io.papermc.hangar.db.customtypes;
import com.fasterxml.jackson.annotation.JsonValue;
import org.postgresql.util.PGobject;
import java.util.Objects;
import org.postgresql.util.PGobject;
public class RoleCategory extends PGobject {
@ -13,36 +12,36 @@ public class RoleCategory extends PGobject {
private String value;
RoleCategory(String value) {
RoleCategory(final String value) {
this();
this.value = value;
}
public RoleCategory() {
setType("role_category");
this.setType("role_category");
}
@Override
public void setValue(String value) {
public void setValue(final String value) {
this.value = value;
}
@Override
@JsonValue
public String getValue() {
return value;
return this.value;
}
@Override
public boolean equals(Object o) {
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (o == null || this.getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
RoleCategory that = (RoleCategory) o;
final RoleCategory that = (RoleCategory) o;
return Objects.equals(value, that.value);
return Objects.equals(this.value, that.value);
}
@Override
@ -53,7 +52,7 @@ public class RoleCategory extends PGobject {
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
return result;
}
}

View File

@ -2,18 +2,17 @@ package io.papermc.hangar.db.dao;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.UserTable;
import java.util.Map;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.config.ValueColumn;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Repository;
import java.util.Map;
@Repository
public interface PermissionsDAO {
@SqlQuery("SELECT COALESCE(gt.permission, B'0'::bit(64))::bigint perm_value" +
@SqlQuery("SELECT coalesce(gt.permission, B'0'::bit(64))::bigint perm_value" +
" FROM users u " +
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
" WHERE u.id = :userId OR u.name = :userName")
@ -27,10 +26,10 @@ public interface PermissionsDAO {
return this._getGlobalPermission(null, userName);
}
@SqlQuery("SELECT (COALESCE(gt.permission, B'0'::bit(64)) | COALESCE(pt.permission, B'0'::bit(64)) | COALESCE(ot.permission, B'0'::bit(64)))::bigint AS perm_value" +
@SqlQuery("SELECT (coalesce(gt.permission, B'0'::bit(64)) | coalesce(pt.permission, B'0'::bit(64)) | coalesce(ot.permission, B'0'::bit(64)))::bigint AS perm_value" +
" FROM users u " +
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
" LEFT JOIN projects p ON (LOWER(p.owner_name) = LOWER(:author) AND p.slug = :slug) OR p.id = :projectId" +
" LEFT JOIN projects p ON (lower(p.owner_name) = lower(:author) AND p.slug = :slug) OR p.id = :projectId" +
" LEFT JOIN project_trust pt ON u.id = pt.user_id AND pt.project_id = p.id" +
" LEFT JOIN organization_trust ot ON u.id = ot.user_id AND ot.organization_id = p.owner_id" +
" WHERE u.id = :userId")
@ -46,7 +45,7 @@ public interface PermissionsDAO {
@ValueColumn("permission")
@RegisterConstructorMapper(UserTable.class)
@SqlQuery("SELECT u.*, (COALESCE(gt.permission, B'0'::bit(64)) | COALESCE(pt.permission, B'0'::bit(64)) | COALESCE(ot.permission, B'0'::bit(64)))::bigint AS permission" +
@SqlQuery("SELECT u.*, (coalesce(gt.permission, B'0'::bit(64)) | coalesce(pt.permission, B'0'::bit(64)) | coalesce(ot.permission, B'0'::bit(64)))::bigint AS permission" +
" FROM users u" +
" JOIN project_trust pt ON u.id = pt.user_id" +
" JOIN projects p ON pt.project_id = p.id" +
@ -55,7 +54,7 @@ public interface PermissionsDAO {
" WHERE pt.project_id = :projectId")
Map<UserTable, Permission> getProjectMemberPermissions(long projectId);
@SqlQuery("SELECT (COALESCE(gt.permission, B'0'::bit(64)) | COALESCE(ot.permission, B'0'::bit(64)))::bigint AS perm_value" +
@SqlQuery("SELECT (coalesce(gt.permission, B'0'::bit(64)) | coalesce(ot.permission, B'0'::bit(64)))::bigint AS perm_value" +
" FROM users u " +
" LEFT JOIN organizations o ON o.name = :orgName OR o.id = :orgId" +
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
@ -71,9 +70,9 @@ public interface PermissionsDAO {
return this._getOrganizationPermission(userId, null, orgId);
}
@SqlQuery("SELECT COALESCE(BIT_OR(r.permission), B'0'::bit(64))::bigint perm_value FROM user_project_roles upr JOIN roles r ON upr.role_type = r.name WHERE upr.user_id = :userId")
@SqlQuery("SELECT coalesce(bit_or(r.permission), B'0'::bit(64))::bigint perm_value FROM user_project_roles upr JOIN roles r ON upr.role_type = r.name WHERE upr.user_id = :userId")
Permission getPossibleProjectPermissions(long userId);
@SqlQuery("SELECT COALESCE(BIT_OR(r.permission), B'0'::bit(64))::bigint perm_value FROM user_organization_roles uor JOIN roles r ON uor.role_type = r.name WHERE uor.user_id = :userId")
@SqlQuery("SELECT coalesce(bit_or(r.permission), B'0'::bit(64))::bigint perm_value FROM user_organization_roles uor JOIN roles r ON uor.role_type = r.name WHERE uor.user_id = :userId")
Permission getPossibleOrganizationPermissions(long userId);
}

View File

@ -5,18 +5,16 @@ import io.papermc.hangar.db.mappers.factories.RoleColumnMapperFactory;
import io.papermc.hangar.model.api.User;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.model.internal.user.HangarUser;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterColumnMapperFactory;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.AllowUnusedBindings;
import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.customizer.Define;
import org.jdbi.v3.sqlobject.statement.MapTo;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(HangarUser.class)
@RegisterConstructorMapper(User.class)
@ -24,59 +22,59 @@ import java.util.List;
public interface UsersDAO {
@SqlQuery("SELECT u.id, " +
" u.created_at," +
" u.name," +
" u.tagline," +
" u.join_date, " +
" array(SELECT role_id FROM user_global_roles WHERE u.id = user_id) AS roles," +
" (SELECT count(*)" +
" FROM project_members_all pma" +
" WHERE pma.user_id = u.id" +
" ) AS project_count," +
" u.read_prompts," +
" u.locked," +
" u.language," +
" u.theme," +
" exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization" +
" FROM users u" +
" WHERE u.name = :name" +
" OR u.id = :id" +
" GROUP BY u.id")
" u.created_at," +
" u.name," +
" u.tagline," +
" u.join_date, " +
" array(SELECT role_id FROM user_global_roles WHERE u.id = user_id) AS roles," +
" (SELECT count(*)" +
" FROM project_members_all pma" +
" WHERE pma.user_id = u.id" +
" ) AS project_count," +
" u.read_prompts," +
" u.locked," +
" u.language," +
" u.theme," +
" exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization" +
" FROM users u" +
" WHERE u.name = :name" +
" OR u.id = :id" +
" GROUP BY u.id")
<T extends User> T _getUser(String name, Long id, @MapTo Class<T> type);
default <T extends User> T getUser(String name, Class<T> type) {
return _getUser(name, null, type);
default <T extends User> T getUser(final String name, final Class<T> type) {
return this._getUser(name, null, type);
}
default <T extends User> T getUser(long id, Class<T> type) {
return _getUser(null, id, type);
default <T extends User> T getUser(final long id, final Class<T> type) {
return this._getUser(null, id, type);
}
@AllowUnusedBindings // query can be unused
@UseStringTemplateEngine
@SqlQuery("SELECT u.id," +
" u.created_at," +
" u.name," +
" u.tagline," +
" u.join_date," +
" array(SELECT role_id FROM user_global_roles WHERE u.id = user_id) AS roles," +
" (SELECT count(*) FROM project_members_all pma WHERE pma.user_id = u.id) AS project_count," +
" u.read_prompts," +
" u.locked," +
" u.language," +
" u.theme," +
" exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization" +
" FROM users u" +
" <if(hasQuery)>WHERE u.name ILIKE '%' || :query || '%'<endif>" +
" GROUP BY u.id " +
" <sorters>" +
" <offsetLimit>")
" u.created_at," +
" u.name," +
" u.tagline," +
" u.join_date," +
" array(SELECT role_id FROM user_global_roles WHERE u.id = user_id) AS roles," +
" (SELECT count(*) FROM project_members_all pma WHERE pma.user_id = u.id) AS project_count," +
" u.read_prompts," +
" u.locked," +
" u.language," +
" u.theme," +
" exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization" +
" FROM users u" +
" <if(hasQuery)>WHERE u.name ILIKE '%' || :query || '%'<endif>" +
" GROUP BY u.id " +
" <sorters>" +
" <offsetLimit>")
<T extends User> List<T> getUsers(@Define boolean hasQuery, String query, @BindPagination RequestPagination pagination, @MapTo Class<T> type);
@AllowUnusedBindings // query can be unused
@UseStringTemplateEngine
@SqlQuery("SELECT COUNT(*)" +
" FROM users u" +
" <if(hasQuery)>WHERE u.name ILIKE '%' || :query || '%'<endif>")
@SqlQuery("SELECT count(*)" +
" FROM users u" +
" <if(hasQuery)>WHERE u.name ILIKE '%' || :query || '%'<endif>")
long getUsersCount(@Define boolean hasQuery, String query);
}

View File

@ -2,38 +2,37 @@ package io.papermc.hangar.db.dao.internal;
import io.papermc.hangar.model.internal.admin.activity.FlagActivity;
import io.papermc.hangar.model.internal.admin.activity.ReviewActivity;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ActivityDAO {
@RegisterConstructorMapper(FlagActivity.class)
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" pf.resolved_at" +
" FROM project_flags pf" +
" JOIN projects p ON pf.project_id = p.id" +
" WHERE pf.user_id = :userId")
" p.slug," +
" pf.resolved_at" +
" FROM project_flags pf" +
" JOIN projects p ON pf.project_id = p.id" +
" WHERE pf.user_id = :userId")
List<FlagActivity> getFlagActivity(long userId);
@RegisterConstructorMapper(ReviewActivity.class)
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" pvr.ended_at," +
" pv.version_string," +
" array(SELECT DISTINCT plv.platform " +
" FROM project_version_platform_dependencies pvpd " +
" JOIN platform_versions plv ON pvpd.platform_version_id = plv.id" +
" WHERE pv.id = pvpd.version_id" +
" ORDER BY plv.platform" +
" ) platforms" +
" FROM project_version_reviews pvr" +
" JOIN project_versions pv ON pvr.version_id = pv.id" +
" JOIN projects p ON pv.project_id = p.id" +
" WHERE pvr.user_id = :userId")
" p.slug," +
" pvr.ended_at," +
" pv.version_string," +
" array(SELECT DISTINCT plv.platform " +
" FROM project_version_platform_dependencies pvpd " +
" JOIN platform_versions plv ON pvpd.platform_version_id = plv.id" +
" WHERE pv.id = pvpd.version_id" +
" ORDER BY plv.platform" +
" ) platforms" +
" FROM project_version_reviews pvr" +
" JOIN project_versions pv ON pvr.version_id = pv.id" +
" JOIN projects p ON pv.project_id = p.id" +
" WHERE pvr.user_id = :userId")
List<ReviewActivity> getReviewActivity(long userId);
}

View File

@ -1,12 +1,11 @@
package io.papermc.hangar.db.dao.internal;
import io.papermc.hangar.model.api.ApiKey;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(ApiKey.class)
public interface HangarApiKeysDAO {

View File

@ -4,25 +4,24 @@ import io.papermc.hangar.db.extras.BindPagination;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.model.internal.user.notifications.HangarInvite;
import io.papermc.hangar.model.internal.user.notifications.HangarNotification;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(HangarNotification.class)
public interface HangarNotificationsDAO {
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name as origin_user_name" +
" FROM notifications n" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +
" WHERE n.user_id = :userId" +
" ORDER BY n.created_at DESC" +
" LIMIT :amount")
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name AS origin_user_name" +
" FROM notifications n" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +
" WHERE n.user_id = :userId" +
" ORDER BY n.created_at DESC" +
" LIMIT :amount")
List<HangarNotification> getNotifications(long userId, int amount);
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name as origin_user_name" +
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name AS origin_user_name" +
" FROM notifications n" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +
" WHERE n.user_id = :userId" +
@ -30,7 +29,7 @@ public interface HangarNotificationsDAO {
" <offsetLimit>")
List<HangarNotification> getNotifications(long userId, @BindPagination RequestPagination pagination);
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name as origin_user_name" +
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name AS origin_user_name" +
" FROM notifications n" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +
" WHERE n.user_id = :userId AND n.read = :read" +
@ -39,26 +38,26 @@ public interface HangarNotificationsDAO {
List<HangarNotification> getNotifications(long userId, boolean read, @BindPagination RequestPagination pagination);
@RegisterConstructorMapper(HangarInvite.HangarProjectInvite.class)
@SqlQuery("SELECT upr.id roleTableId," +
" upr.role_type AS role," +
" p.name," +
" '/' || p.owner_name || '/' || p.slug url" +
" FROM user_project_roles upr" +
" JOIN projects p ON p.id = upr.project_id" +
" WHERE upr.user_id = :userId " +
" AND upr.accepted = FALSE" +
" ORDER BY upr.created_at DESC")
@SqlQuery("SELECT upr.id roletableid," +
" upr.role_type AS role," +
" p.name," +
" '/' || p.owner_name || '/' || p.slug url" +
" FROM user_project_roles upr" +
" JOIN projects p ON p.id = upr.project_id" +
" WHERE upr.user_id = :userId " +
" AND upr.accepted = FALSE" +
" ORDER BY upr.created_at DESC")
List<HangarInvite.HangarProjectInvite> getProjectInvites(long userId);
@RegisterConstructorMapper(HangarInvite.HangarOrganizationInvite.class)
@SqlQuery("SELECT uor.id roleTableId," +
" uor.role_type AS role," +
" o.name," +
" '/' || o.name url" +
" FROM user_organization_roles uor" +
" JOIN organizations o ON o.id = uor.organization_id" +
" WHERE uor.user_id = :userId" +
" AND uor.accepted = FALSE" +
" ORDER BY uor.created_at DESC")
@SqlQuery("SELECT uor.id roletableid," +
" uor.role_type AS role," +
" o.name," +
" '/' || o.name url" +
" FROM user_organization_roles uor" +
" JOIN organizations o ON o.id = uor.organization_id" +
" WHERE uor.user_id = :userId" +
" AND uor.accepted = FALSE" +
" ORDER BY uor.created_at DESC")
List<HangarInvite.HangarOrganizationInvite> getOrganizationInvites(long userId);
}

View File

@ -4,6 +4,7 @@ import io.papermc.hangar.db.mappers.factories.JoinableRowMapperFactory;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.model.db.roles.OrganizationRoleTable;
import io.papermc.hangar.model.internal.user.JoinableMember;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.config.RegisterRowMapperFactory;
import org.jdbi.v3.sqlobject.customizer.Define;
@ -11,8 +12,6 @@ import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface HangarOrganizationsDAO {
@ -21,16 +20,16 @@ public interface HangarOrganizationsDAO {
@RegisterConstructorMapper(value = OrganizationRoleTable.class, prefix = "uor_")
@UseStringTemplateEngine
@SqlQuery("SELECT u.*," +
" uor.id uor_id," +
" uor.created_at uor_created_at," +
" uor.user_id uor_user_id," +
" uor.role_type uor_role_type," +
" uor.organization_id uor_organization_id," +
" uor.accepted uor_accepted," +
" om.hidden hidden" +
" FROM user_organization_roles uor" +
" JOIN users u ON uor.user_id = u.id" +
" LEFT JOIN organization_members om ON om.user_id = u.id and om.organization_id = uor.organization_id" +
" WHERE uor.organization_id = :orgId <if(!canSeePending)>AND (uor.accepted IS TRUE OR uor.user_id = :userId) AND (om.hidden IS FALSE OR uor.user_id = :userId)<endif>")
" uor.id uor_id," +
" uor.created_at uor_created_at," +
" uor.user_id uor_user_id," +
" uor.role_type uor_role_type," +
" uor.organization_id uor_organization_id," +
" uor.accepted uor_accepted," +
" om.hidden hidden" +
" FROM user_organization_roles uor" +
" JOIN users u ON uor.user_id = u.id" +
" LEFT JOIN organization_members om ON om.user_id = u.id AND om.organization_id = uor.organization_id" +
" WHERE uor.organization_id = :orgId <if(!canSeePending)>AND (uor.accepted IS TRUE OR uor.user_id = :userId) AND (om.hidden IS FALSE OR uor.user_id = :userId)<endif>")
List<JoinableMember<OrganizationRoleTable>> getOrganizationMembers(long orgId, Long userId, @Define boolean canSeePending);
}

View File

@ -1,6 +1,8 @@
package io.papermc.hangar.db.dao.internal;
import io.papermc.hangar.model.internal.admin.DayStats;
import java.time.LocalDate;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.Define;
import org.jdbi.v3.sqlobject.locator.UseClasspathSqlLocator;
@ -9,9 +11,6 @@ import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface HangarStatsDAO {
@ -25,25 +24,25 @@ public interface HangarStatsDAO {
@UseStringTemplateEngine
@SqlUpdate("WITH d AS (" +
" UPDATE <individualTable> SET processed = processed + 1 " +
" WHERE user_id IS <if(withUserId)>NOT<endif> NULL" +
" RETURNING created_at, project_id, <if(includeVersionId)>version_id,<endif> <if(withUserId)>user_id<else>address<endif>, processed" +
" )" +
" INSERT " +
" INTO <dayTable> AS pvd (day, project_id, <if(includeVersionId)>version_id,<endif> <statColumn>)" +
" SELECT sq.day," +
" sq.project_id," +
" <if(includeVersionId)>sq.version_id,<endif>" +
" count(DISTINCT sq.<if(withUserId)>user_id<else>address<endif>) FILTER (WHERE sq.processed \\<@ ARRAY[1])" +
" FROM (SELECT date_trunc('DAY', d.created_at)::date AS day," +
" d.project_id," +
" <if(includeVersionId)>d.version_id,<endif>" +
" <if(withUserId)>user_id<else>address<endif>," +
" array_agg(d.processed) AS processed" +
" FROM d" +
" GROUP BY date_trunc('DAY', d.created_at), d.project_id, <if(includeVersionId)>d.version_id,<endif> <if(withUserId)>user_id<else>address<endif>) sq" +
" GROUP BY sq.day, <if(includeVersionId)>sq.version_id,<endif> sq.project_id" +
" ON CONFLICT(day, <if(includeVersionId)>version_id<else>project_id<endif>) DO UPDATE SET <statColumn> = pvd.<if(includeVersionId)>version_id<else>project_id<endif> + excluded.<if(includeVersionId)>version_id<else>project_id<endif>")
" UPDATE <individualTable> SET processed = processed + 1 " +
" WHERE user_id IS <if(withUserId)>NOT<endif> NULL" +
" RETURNING created_at, project_id, <if(includeVersionId)>version_id,<endif> <if(withUserId)>user_id<else>address<endif>, processed" +
" )" +
" INSERT " +
" INTO <dayTable> AS pvd (day, project_id, <if(includeVersionId)>version_id,<endif> <statColumn>)" +
" SELECT sq.day," +
" sq.project_id," +
" <if(includeVersionId)>sq.version_id,<endif>" +
" count(DISTINCT sq.<if(withUserId)>user_id<else>address<endif>) FILTER (WHERE sq.processed \\<@ ARRAY[1])" +
" FROM (SELECT date_trunc('DAY', d.created_at)::date AS day," +
" d.project_id," +
" <if(includeVersionId)>d.version_id,<endif>" +
" <if(withUserId)>user_id<else>address<endif>," +
" array_agg(d.processed) AS processed" +
" FROM d" +
" GROUP BY date_trunc('DAY', d.created_at), d.project_id, <if(includeVersionId)>d.version_id,<endif> <if(withUserId)>user_id<else>address<endif>) sq" +
" GROUP BY sq.day, <if(includeVersionId)>sq.version_id,<endif> sq.project_id" +
" ON CONFLICT(day, <if(includeVersionId)>version_id<else>project_id<endif>) DO UPDATE SET <statColumn> = pvd.<if(includeVersionId)>version_id<else>project_id<endif> + excluded.<if(includeVersionId)>version_id<else>project_id<endif>")
void processStatsMain(@Define String individualTable, @Define String dayTable, @Define String statColumn, @Define boolean withUserId, @Define boolean includeVersionId);
@SqlUpdate("DELETE FROM <table> WHERE processed != 0 AND created_at < now() + '30 days'::INTERVAL")

View File

@ -14,14 +14,14 @@ public interface HangarUsersDAO {
@RegisterConstructorMapper(UserTable.class)
@RegisterConstructorMapper(value = OrganizationTable.class, prefix = "o_")
@SqlQuery("SELECT u.*," +
" o.id o_id," +
" o.created_at o_created_at," +
" o.name o_name," +
" o.owner_id o_owner_id," +
" o.user_id o_user_id" +
" FROM users u " +
" LEFT JOIN organizations o ON u.id = o.user_id" +
" WHERE u.name = :userName")
" o.id o_id," +
" o.created_at o_created_at," +
" o.name o_name," +
" o.owner_id o_owner_id," +
" o.user_id o_user_id" +
" FROM users u " +
" LEFT JOIN organizations o ON u.id = o.user_id" +
" WHERE u.name = :userName")
Pair<UserTable, OrganizationTable> getUserAndOrg(String userName);
@SqlUpdate("INSERT INTO project_stars VALUES (:userId, :projectId)")

View File

@ -2,6 +2,7 @@ package io.papermc.hangar.db.dao.internal;
import io.papermc.hangar.model.internal.admin.health.MissingFileCheck;
import io.papermc.hangar.model.internal.admin.health.UnhealthyProject;
import java.util.List;
import org.jdbi.v3.core.enums.EnumStrategy;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.config.UseEnumStrategy;
@ -9,64 +10,62 @@ import org.jdbi.v3.sqlobject.customizer.Define;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(UnhealthyProject.class)
public interface HealthDAO {
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p" +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE p.topic_id IS NULL OR " +
" p.post_id IS NULL" +
" ORDER BY p.created_at DESC")
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p" +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE p.topic_id IS NULL OR " +
" p.post_id IS NULL" +
" ORDER BY p.created_at DESC")
List<UnhealthyProject> getProjectsWithoutTopic();
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p " +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE hp.last_updated < (now() - INTERVAL <age>)" +
" ORDER BY p.created_at DESC")
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p " +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE hp.last_updated < (now() - interval <age>)" +
" ORDER BY p.created_at DESC")
List<UnhealthyProject> getStaleProjects(@Define("age") String staleAgeSeconds);
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p " +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE p.visibility != 0" +
" ORDER BY p.created_at DESC")
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p " +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE p.visibility != 0" +
" ORDER BY p.created_at DESC")
List<UnhealthyProject> getNonPublicProjects();
@UseEnumStrategy(EnumStrategy.BY_ORDINAL)
@RegisterConstructorMapper(MissingFileCheck.class)
@SqlQuery("SELECT pv.version_string,\n" +
" pvd.file_name,\n" +
" p.owner_name \"owner\",\n" +
" p.slug,\n" +
" p.name,\n" +
" pq.platform\n" +
"FROM project_versions pv\n" +
" JOIN projects p ON pv.project_id = p.id\n" +
" JOIN (SELECT DISTINCT plv.platform, pvpd.version_id\n" +
" FROM project_version_platform_dependencies pvpd\n" +
" JOIN platform_versions plv ON pvpd.platform_version_id = plv.id) pq ON pv.id = pq.version_id\n" +
" JOIN project_version_downloads pvd ON pvd.version_id = pq.version_id\n" +
"WHERE pvd.file_name IS NOT NULL\n" +
"ORDER BY pv.created_at DESC")
" pvd.file_name,\n" +
" p.owner_name \"owner\",\n" +
" p.slug,\n" +
" p.name,\n" +
" pq.platform\n" +
"FROM project_versions pv\n" +
" JOIN projects p ON pv.project_id = p.id\n" +
" JOIN (SELECT DISTINCT plv.platform, pvpd.version_id\n" +
" FROM project_version_platform_dependencies pvpd\n" +
" JOIN platform_versions plv ON pvpd.platform_version_id = plv.id) pq ON pv.id = pq.version_id\n" +
" JOIN project_version_downloads pvd ON pvd.version_id = pq.version_id\n" +
"WHERE pvd.file_name IS NOT NULL\n" +
"ORDER BY pv.created_at DESC")
List<MissingFileCheck> getVersionsForMissingFiles();
}

View File

@ -9,6 +9,7 @@ import io.papermc.hangar.model.db.log.LoggedActionsProjectTable;
import io.papermc.hangar.model.db.log.LoggedActionsUserTable;
import io.papermc.hangar.model.db.log.LoggedActionsVersionTable;
import io.papermc.hangar.model.internal.logs.HangarLoggedAction;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterColumnMapper;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
@ -19,8 +20,6 @@ import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface LoggedActionsDAO {
@ -48,14 +47,14 @@ public interface LoggedActionsDAO {
@RegisterColumnMapper(LogActionColumnMapper.class)
@RegisterConstructorMapper(HangarLoggedAction.class)
@SqlQuery("SELECT * FROM v_logged_actions la " +
" WHERE true <filters>" +
" ORDER BY la.created_at DESC <offsetLimit>")
" WHERE TRUE <filters>" +
" ORDER BY la.created_at DESC <offsetLimit>")
// TODO add <sorters>
@DefineNamedBindings
List<HangarLoggedAction> getLog(@BindPagination RequestPagination pagination);
@UseStringTemplateEngine
@SqlQuery("SELECT count(*) FROM v_logged_actions la " +
" WHERE true <filters>")
" WHERE TRUE <filters>")
long getLogCount(@BindPagination(isCount = true) RequestPagination pagination);
}

View File

@ -1,17 +1,16 @@
package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.model.internal.projects.HangarProjectFlagNotification;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface HangarProjectFlagNofiticationsDAO {
@RegisterConstructorMapper(HangarProjectFlagNotification.class)
@SqlQuery("SELECT fn.id, n.user_id, n.type, n.message_args message, u.name as origin_user_name" +
@SqlQuery("SELECT fn.id, n.user_id, n.type, n.message_args message, u.name AS origin_user_name" +
" FROM project_flag_notifications fn" +
" LEFT OUTER JOIN notifications n ON fn.notification_id = n.id" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +

View File

@ -3,42 +3,41 @@ package io.papermc.hangar.db.dao.internal.projects;
import io.papermc.hangar.db.extras.BindPagination;
import io.papermc.hangar.model.api.requests.RequestPagination;
import io.papermc.hangar.model.internal.projects.HangarProjectFlag;
import java.util.List;
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterConstructorMapper(HangarProjectFlag.class)
public interface HangarProjectFlagsDAO {
@SqlQuery("SELECT pf.*, fu.name reported_by_name, ru.name resolved_by_name, p.owner_name project_owner_name, p.slug project_slug, p.visibility project_visibility " +
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.id = :flagId " +
"GROUP BY pf.id, fu.id, ru.id, p.id")
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.id = :flagId " +
"GROUP BY pf.id, fu.id, ru.id, p.id")
HangarProjectFlag getById(long flagId);
@SqlQuery("SELECT pf.*, fu.name reported_by_name, ru.name resolved_by_name, p.owner_name project_owner_name, p.slug project_slug, p.visibility project_visibility " +
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.project_id = :projectId " +
"GROUP BY pf.id, fu.id, ru.id, p.id")
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.project_id = :projectId " +
"GROUP BY pf.id, fu.id, ru.id, p.id")
List<HangarProjectFlag> getFlags(long projectId);
@SqlQuery("SELECT pf.*, fu.name reported_by_name, ru.name resolved_by_name, p.owner_name project_owner_name, p.slug project_slug, p.visibility project_visibility " +
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.resolved = :resolved " +
"GROUP BY pf.id, fu.id, ru.id, p.id <offsetLimit>")
"FROM project_flags pf " +
" JOIN projects p ON pf.project_id = p.id " +
" JOIN users fu ON pf.user_id = fu.id " +
" LEFT OUTER JOIN users ru ON ru.id = pf.resolved_by " +
"WHERE pf.resolved = :resolved " +
"GROUP BY pf.id, fu.id, ru.id, p.id <offsetLimit>")
List<HangarProjectFlag> getFlags(@BindPagination RequestPagination pagination, boolean resolved);
@SqlQuery("SELECT count(id) FROM project_flags WHERE resolved = :resolved")

Some files were not shown because too many files have changed in this diff Show More