mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
validation work
This commit is contained in:
parent
4e7a66d929
commit
31fe7bb53e
@ -1,5 +1,6 @@
|
||||
package io.papermc.hangar;
|
||||
|
||||
import io.papermc.hangar.config.hangar.PagesConfig;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
@ -9,7 +10,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
@Import(JdbiBeanFactoryPostProcessor.class)
|
||||
@ConfigurationPropertiesScan("io.papermc.hangar.config.hangar")
|
||||
@ConfigurationPropertiesScan(value = "io.papermc.hangar.config.hangar", basePackageClasses = PagesConfig.class)
|
||||
public class HangarApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -126,7 +126,7 @@ public class WebConfig extends WebMvcConfigurationSupport {
|
||||
@Override
|
||||
public void configureMessageConverters(@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().get();
|
||||
ParameterNamesAnnotationIntrospector sAnnotationIntrospector = (ParameterNamesAnnotationIntrospector) mapper.getSerializationConfig().getAnnotationIntrospector().allIntrospectors().stream().filter(ParameterNamesAnnotationIntrospector.class::isInstance).findFirst().orElseThrow();
|
||||
mapper.setAnnotationIntrospectors(
|
||||
AnnotationIntrospector.pair(sAnnotationIntrospector, new HangarAnnotationIntrospector()),
|
||||
mapper.getDeserializationConfig().getAnnotationIntrospector()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.model.common.Color;
|
||||
import io.papermc.hangar.model.internal.api.responses.Validation;
|
||||
import io.papermc.hangar.util.PatternWrapper;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Size;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ -9,12 +11,16 @@ import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
@ConfigurationProperties(prefix = "hangar.channels")
|
||||
public record ChannelsConfig(
|
||||
@Min(1) @DefaultValue("15") int maxNameLen,
|
||||
@DefaultValue("^[a-zA-Z0-9]+$") String nameRegex,
|
||||
@DefaultValue("^[a-zA-Z0-9]+$") PatternWrapper nameRegex,
|
||||
@DefaultValue("cyan") Color colorDefault,
|
||||
@Size(min = 1, max = 15) @DefaultValue("Release") String nameDefault
|
||||
) {
|
||||
|
||||
public boolean isValidChannelName(String name) {
|
||||
return name.length() >= 1 && name.length() <= maxNameLen && name.matches(nameRegex);
|
||||
public boolean isValidChannelName(final String name) {
|
||||
return name.length() >= 1 && name.length() <= this.maxNameLen() && this.nameRegex().test(name);
|
||||
}
|
||||
|
||||
public Validation channelName() {
|
||||
return new Validation(this.nameRegex(), this.maxNameLen(), null);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.model.internal.api.responses.Validation;
|
||||
import io.papermc.hangar.util.PatternWrapper;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ConfigurationProperties(prefix = "hangar.orgs")
|
||||
public record OrganizationsConfig(
|
||||
@ -14,6 +12,10 @@ public record OrganizationsConfig(
|
||||
@DefaultValue("5") int createLimit,
|
||||
@DefaultValue("3") int minNameLen,
|
||||
@DefaultValue("20") int maxNameLen,
|
||||
@DefaultValue("[a-zA-Z0-9-_]*") String nameRegex
|
||||
@DefaultValue("[a-zA-Z0-9-_]*") PatternWrapper nameRegex
|
||||
) {
|
||||
|
||||
public Validation orgName() {
|
||||
return new Validation(this.nameRegex(), this.maxNameLen(), this.minNameLen());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import java.util.regex.Pattern;
|
||||
import io.papermc.hangar.model.internal.api.responses.Validation;
|
||||
import io.papermc.hangar.util.PatternWrapper;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
@ -10,27 +11,35 @@ import org.springframework.http.HttpStatus;
|
||||
@ConfigurationProperties(prefix = "hangar.pages")
|
||||
public record PagesConfig(
|
||||
@NestedConfigurationProperty Home home,
|
||||
@DefaultValue("^[a-zA-Z0-9-_ ]+$") String nameRegex,
|
||||
@DefaultValue("^[a-zA-Z0-9-_ ]+$") PatternWrapper nameRegex,
|
||||
@DefaultValue("3") int minNameLen,
|
||||
@DefaultValue("25") int maxNameLen,
|
||||
@DefaultValue("15") int minLen,
|
||||
@DefaultValue("32000") int maxLen
|
||||
) {
|
||||
|
||||
public void testPageName(final String name) {
|
||||
if (name.length() > this.maxNameLen) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.maxLength");
|
||||
} else if (name.length() < this.minNameLen) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.minLength");
|
||||
} else if (!this.nameRegex().test(name)) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.invalidChars");
|
||||
}
|
||||
}
|
||||
|
||||
public Validation pageName() {
|
||||
return new Validation(this.nameRegex(), this.maxNameLen(), this.minNameLen());
|
||||
}
|
||||
|
||||
public Validation pageContent() {
|
||||
return Validation.size(this.maxLen(), this.minLen());
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "hangar.pages.home")
|
||||
public record Home(
|
||||
@DefaultValue("Home") String name,
|
||||
@DefaultValue("Welcome to your new project!") String message
|
||||
) {
|
||||
}
|
||||
|
||||
public void testPageName(String name) {
|
||||
if (name.length() > maxNameLen) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.maxLength");
|
||||
} else if (name.length() < minNameLen) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.minLength");
|
||||
} else if (!Pattern.compile(nameRegex).matcher(name).matches()) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.invalidChars");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.model.internal.api.responses.Validation;
|
||||
import io.papermc.hangar.util.PatternWrapper;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
import org.springframework.boot.convert.DurationUnit;
|
||||
@ -33,4 +32,20 @@ public record ProjectsConfig( // TODO split into ProjectsConfig and VersionsConf
|
||||
@DefaultValue("10") @DurationUnit(ChronoUnit.MINUTES) Duration unsafeDownloadMaxAge,
|
||||
@DefaultValue("false") boolean showUnreviewedDownloadWarning
|
||||
) {
|
||||
|
||||
public Validation projectName() {
|
||||
return new Validation(this.nameRegex(), this.maxNameLen(), null);
|
||||
}
|
||||
|
||||
public Validation projectDescription() {
|
||||
return Validation.max(this.maxDescLen());
|
||||
}
|
||||
|
||||
public Validation projectKeywords() {
|
||||
return Validation.max(this.maxKeywords());
|
||||
}
|
||||
|
||||
public Validation versionName() {
|
||||
return new Validation(this.versionNameRegex(), this.maxVersionNameLen(), null);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.model.internal.api.responses.Validation;
|
||||
import java.util.List;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
@ -9,4 +10,8 @@ public record UserConfig(
|
||||
@DefaultValue("100") int maxTaglineLen,
|
||||
@DefaultValue({"Hangar_Admin", "Hangar_Mod"}) List<String> staffRoles
|
||||
) {
|
||||
|
||||
public Validation userTagline() {
|
||||
return Validation.max(this.maxTaglineLen());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package io.papermc.hangar.controller.internal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
@ -21,11 +19,11 @@ import io.papermc.hangar.model.common.projects.Visibility;
|
||||
import io.papermc.hangar.model.common.roles.GlobalRole;
|
||||
import io.papermc.hangar.model.common.roles.OrganizationRole;
|
||||
import io.papermc.hangar.model.common.roles.ProjectRole;
|
||||
import io.papermc.hangar.model.internal.api.responses.Validations;
|
||||
import io.papermc.hangar.security.annotations.Anyone;
|
||||
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
|
||||
import io.papermc.hangar.service.internal.PlatformService;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -48,24 +46,24 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@RequestMapping(path = "/api/internal/data", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
|
||||
public class BackendDataController {
|
||||
|
||||
private final ObjectMapper noJsonValueMapper;
|
||||
private final ObjectMapper objectMapper; // ignores JsonValue annotations
|
||||
private final HangarConfig config;
|
||||
private final PlatformService platformService;
|
||||
private final Optional<GitProperties> gitProperties;
|
||||
|
||||
@Autowired
|
||||
public BackendDataController(ObjectMapper mapper, HangarConfig config, PlatformService platformService, Optional<GitProperties> gitProperties) {
|
||||
public BackendDataController(final ObjectMapper mapper, final HangarConfig config, final PlatformService platformService, final Optional<GitProperties> gitProperties) {
|
||||
this.config = config;
|
||||
this.noJsonValueMapper = mapper.copy();
|
||||
this.objectMapper = mapper.copy();
|
||||
this.platformService = platformService;
|
||||
this.gitProperties = gitProperties;
|
||||
this.noJsonValueMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
|
||||
this.objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
|
||||
@Override
|
||||
protected <A extends Annotation> A _findAnnotation(Annotated annotated, Class<A> annoClass) {
|
||||
if (!annotated.hasAnnotation(JsonValue.class)) {
|
||||
return super._findAnnotation(annotated, annoClass);
|
||||
protected <A extends Annotation> A _findAnnotation(final Annotated annotated, final Class<A> annoClass) {
|
||||
if (annoClass == JsonValue.class) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
return super._findAnnotation(annotated, annoClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -73,15 +71,15 @@ public class BackendDataController {
|
||||
@GetMapping("/categories")
|
||||
@Cacheable("categories")
|
||||
public ResponseEntity<ArrayNode> getCategories() {
|
||||
return ResponseEntity.ok(noJsonValueMapper.valueToTree(Category.getValues()));
|
||||
return ResponseEntity.ok(this.objectMapper.valueToTree(Category.getValues()));
|
||||
}
|
||||
|
||||
@GetMapping("/permissions")
|
||||
@Cacheable("permissions")
|
||||
public ResponseEntity<ArrayNode> getPermissions() {
|
||||
ArrayNode arrayNode = noJsonValueMapper.createArrayNode();
|
||||
for (NamedPermission namedPermission : NamedPermission.getValues()) {
|
||||
ObjectNode namedPermissionObject = noJsonValueMapper.createObjectNode();
|
||||
final ArrayNode arrayNode = this.objectMapper.createArrayNode();
|
||||
for (final NamedPermission namedPermission : NamedPermission.getValues()) {
|
||||
final ObjectNode namedPermissionObject = this.objectMapper.createObjectNode();
|
||||
namedPermissionObject.put("value", namedPermission.getValue());
|
||||
namedPermissionObject.put("frontendName", namedPermission.getFrontendName());
|
||||
namedPermissionObject.put("permission", namedPermission.getPermission().toBinString());
|
||||
@ -93,10 +91,10 @@ public class BackendDataController {
|
||||
@GetMapping("/platforms")
|
||||
@Cacheable(CacheConfig.PLATFORMS)
|
||||
public ResponseEntity<ArrayNode> getPlatforms() {
|
||||
ArrayNode arrayNode = noJsonValueMapper.createArrayNode();
|
||||
for (Platform platform : Platform.getValues()) {
|
||||
ObjectNode objectNode = noJsonValueMapper.valueToTree(platform);
|
||||
objectNode.set("possibleVersions", noJsonValueMapper.valueToTree(platformService.getVersionsForPlatform(platform)));
|
||||
final ArrayNode arrayNode = this.objectMapper.createArrayNode();
|
||||
for (final Platform platform : Platform.getValues()) {
|
||||
final ObjectNode objectNode = this.objectMapper.valueToTree(platform);
|
||||
objectNode.set("possibleVersions", this.objectMapper.valueToTree(this.platformService.getVersionsForPlatform(platform)));
|
||||
arrayNode.add(objectNode);
|
||||
}
|
||||
return ResponseEntity.ok(arrayNode);
|
||||
@ -105,11 +103,11 @@ public class BackendDataController {
|
||||
@GetMapping("/channelColors")
|
||||
@Cacheable("channelColors")
|
||||
public ResponseEntity<ArrayNode> getColors() {
|
||||
ArrayNode arrayNode = noJsonValueMapper.createArrayNode();
|
||||
for (Color color : Color.getNonTransparentValues()) {
|
||||
ObjectNode objectNode = noJsonValueMapper.createObjectNode()
|
||||
.put("name", color.name())
|
||||
.put("hex", color.getHex());
|
||||
final ArrayNode arrayNode = this.objectMapper.createArrayNode();
|
||||
for (final Color color : Color.getNonTransparentValues()) {
|
||||
final ObjectNode objectNode = this.objectMapper.createObjectNode()
|
||||
.put("name", color.name())
|
||||
.put("hex", color.getHex());
|
||||
arrayNode.add(objectNode);
|
||||
}
|
||||
return ResponseEntity.ok(arrayNode);
|
||||
@ -119,11 +117,11 @@ public class BackendDataController {
|
||||
@GetMapping("/flagReasons")
|
||||
@Cacheable("flagReasons")
|
||||
public ResponseEntity<ArrayNode> getFlagReasons() {
|
||||
ArrayNode arrayNode = noJsonValueMapper.createArrayNode();
|
||||
for (FlagReason flagReason : FlagReason.getValues()) {
|
||||
ObjectNode objectNode = noJsonValueMapper.createObjectNode()
|
||||
.put("type", flagReason.name())
|
||||
.put("title", flagReason.getTitle());
|
||||
final ArrayNode arrayNode = this.objectMapper.createArrayNode();
|
||||
for (final FlagReason flagReason : FlagReason.getValues()) {
|
||||
final ObjectNode objectNode = this.objectMapper.createObjectNode()
|
||||
.put("type", flagReason.name())
|
||||
.put("title", flagReason.getTitle());
|
||||
arrayNode.add(objectNode);
|
||||
}
|
||||
return ResponseEntity.ok(arrayNode);
|
||||
@ -131,49 +129,55 @@ public class BackendDataController {
|
||||
|
||||
@GetMapping("/sponsor")
|
||||
@Cacheable("sponsor")
|
||||
public ResponseEntity<HangarConfig.Sponsor> getSponsor() {
|
||||
return ResponseEntity.ok(config.getSponsors().get(ThreadLocalRandom.current().nextInt(config.getSponsors().size())));
|
||||
@ResponseBody
|
||||
public HangarConfig.Sponsor getSponsor() {
|
||||
return this.config.getSponsors().get(ThreadLocalRandom.current().nextInt(this.config.getSponsors().size()));
|
||||
}
|
||||
|
||||
@GetMapping("/announcements")
|
||||
public ResponseEntity<List<Announcement>> getAnnouncements() {
|
||||
return ResponseEntity.ok(config.getAnnouncements());
|
||||
@ResponseBody
|
||||
public List<Announcement> getAnnouncements() {
|
||||
return this.config.getAnnouncements();
|
||||
}
|
||||
|
||||
@GetMapping("/projectRoles")
|
||||
@Cacheable("projectRoles")
|
||||
public ResponseEntity<List<ProjectRole>> getAssignableProjectRoles() {
|
||||
return ResponseEntity.ok(ProjectRole.getAssignableRoles());
|
||||
@ResponseBody
|
||||
public List<ProjectRole> getAssignableProjectRoles() {
|
||||
return ProjectRole.getAssignableRoles();
|
||||
}
|
||||
|
||||
@GetMapping("/globalRoles")
|
||||
@Cacheable("globalRoles")
|
||||
public ResponseEntity<List<GlobalRole>> getGlobalRoles() {
|
||||
return ResponseEntity.ok(Arrays.stream(GlobalRole.values()).toList());
|
||||
@ResponseBody
|
||||
public GlobalRole[] getGlobalRoles() {
|
||||
return GlobalRole.getValues();
|
||||
}
|
||||
|
||||
@GetMapping("/orgRoles")
|
||||
@Cacheable("orgRoles")
|
||||
public ResponseEntity<List<OrganizationRole>> getAssignableOrganizationRoles() {
|
||||
return ResponseEntity.ok(OrganizationRole.getAssignableRoles());
|
||||
@ResponseBody
|
||||
public List<OrganizationRole> getAssignableOrganizationRoles() {
|
||||
return OrganizationRole.getAssignableRoles();
|
||||
}
|
||||
|
||||
@GetMapping("/licenses")
|
||||
@Cacheable("licenses")
|
||||
public ResponseEntity<List<String>> getLicenses() {
|
||||
return ResponseEntity.ok(config.getLicenses());
|
||||
@ResponseBody
|
||||
public List<String> getLicenses() {
|
||||
return this.config.getLicenses();
|
||||
}
|
||||
|
||||
@GetMapping("/visibilities")
|
||||
@Cacheable("visibilities")
|
||||
public ResponseEntity<ArrayNode> getVisibilities() {
|
||||
ArrayNode arrayNode = noJsonValueMapper.createArrayNode();
|
||||
for (Visibility value : Visibility.getValues()) {
|
||||
ObjectNode objectNode = noJsonValueMapper.createObjectNode();
|
||||
final ArrayNode arrayNode = this.objectMapper.createArrayNode();
|
||||
for (final Visibility value : Visibility.getValues()) {
|
||||
final ObjectNode objectNode = this.objectMapper.createObjectNode();
|
||||
objectNode.put("name", value.getName())
|
||||
.put("showModal", value.getShowModal())
|
||||
.put("cssClass", value.getCssClass())
|
||||
.put("title", value.getTitle());
|
||||
.put("showModal", value.getShowModal())
|
||||
.put("cssClass", value.getCssClass())
|
||||
.put("title", value.getTitle());
|
||||
arrayNode.add(objectNode);
|
||||
}
|
||||
return ResponseEntity.ok(arrayNode);
|
||||
@ -207,50 +211,9 @@ public class BackendDataController {
|
||||
|
||||
@GetMapping("/validations")
|
||||
@Cacheable("validations")
|
||||
public ResponseEntity<ObjectNode> getValidations() {
|
||||
ObjectNode validations = noJsonValueMapper.createObjectNode();
|
||||
ObjectNode projectValidations = noJsonValueMapper.createObjectNode();
|
||||
projectValidations.set("name", noJsonValueMapper.valueToTree(new Validation(config.projects.nameRegex().strPattern(), config.projects.maxNameLen(), null)));
|
||||
projectValidations.set("desc", noJsonValueMapper.valueToTree(new Validation(null, config.projects.maxDescLen(), null)));
|
||||
projectValidations.set("keywords", noJsonValueMapper.valueToTree(new Validation(null, config.projects.maxKeywords(), null)));
|
||||
projectValidations.set("channels", noJsonValueMapper.valueToTree(new Validation(config.channels.nameRegex(), config.channels.maxNameLen(), null)));
|
||||
projectValidations.set("pageName", noJsonValueMapper.valueToTree(new Validation(config.pages.nameRegex(), config.pages.maxNameLen(), config.pages.minNameLen())));
|
||||
projectValidations.set("pageContent", noJsonValueMapper.valueToTree(new Validation(null, config.pages.maxLen(), config.pages.minLen())));
|
||||
projectValidations.put("maxPageCount", config.projects.maxPages());
|
||||
projectValidations.put("maxChannelCount", config.projects.maxChannels());
|
||||
validations.set("project", projectValidations);
|
||||
validations.set("userTagline", noJsonValueMapper.valueToTree(new Validation(null, config.user.maxTaglineLen(), null)));
|
||||
validations.set("version", noJsonValueMapper.valueToTree(new Validation(config.projects.versionNameRegex().strPattern(), config.projects.maxVersionNameLen(), null)));
|
||||
validations.set("org", noJsonValueMapper.valueToTree(new Validation(config.org.nameRegex(), config.org.maxNameLen(), config.org.minNameLen())));
|
||||
validations.put("maxOrgCount", config.org.createLimit());
|
||||
validations.put("urlRegex", config.getUrlRegex());
|
||||
return ResponseEntity.ok(validations);
|
||||
}
|
||||
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
private static class Validation {
|
||||
|
||||
private final String regex;
|
||||
private final Integer max;
|
||||
private final Integer min;
|
||||
|
||||
public Validation(String regex, Integer max, Integer min) {
|
||||
this.regex = regex;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
}
|
||||
|
||||
public Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
@ResponseBody
|
||||
public Validations getValidations() {
|
||||
return Validations.create(this.config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package io.papermc.hangar.db.dao;
|
||||
|
||||
import io.papermc.hangar.model.common.Permission;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
|
||||
import org.jdbi.v3.sqlobject.config.ValueColumn;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||
@ -12,64 +11,69 @@ import org.springframework.stereotype.Repository;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
@RegisterBeanMapper(value = Permission.class, prefix = "perm")
|
||||
public interface PermissionsDAO {
|
||||
|
||||
@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")
|
||||
@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")
|
||||
Permission _getGlobalPermission(Long userId, String userName);
|
||||
default Permission getGlobalPermission(long userId) {
|
||||
return _getGlobalPermission(userId, null);
|
||||
}
|
||||
default Permission getGlobalPermission(@NotNull String userName) {
|
||||
return _getGlobalPermission(null, userName);
|
||||
|
||||
default Permission getGlobalPermission(final long userId) {
|
||||
return this._getGlobalPermission(userId, null);
|
||||
}
|
||||
|
||||
@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 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")
|
||||
Permission _getProjectPermission(long userId, Long projectId, String author, String slug);
|
||||
default Permission getProjectPermission(long userId, long projectId) {
|
||||
return _getProjectPermission(userId, projectId, null, null);
|
||||
default Permission getGlobalPermission(final @NotNull String userName) {
|
||||
return this._getGlobalPermission(null, userName);
|
||||
}
|
||||
default Permission getProjectPermission(long userId, String author, String slug) {
|
||||
return _getProjectPermission(userId, null, author, slug);
|
||||
|
||||
@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 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")
|
||||
Permission _getProjectPermission(long userId, Long projectId, String author, String slug);
|
||||
|
||||
default Permission getProjectPermission(final long userId, final long projectId) {
|
||||
return this._getProjectPermission(userId, projectId, null, null);
|
||||
}
|
||||
|
||||
default Permission getProjectPermission(final long userId, final String author, final String slug) {
|
||||
return this._getProjectPermission(userId, null, author, slug);
|
||||
}
|
||||
|
||||
@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" +
|
||||
" FROM users u" +
|
||||
" JOIN project_trust pt ON u.id = pt.user_id" +
|
||||
" JOIN projects p ON pt.project_id = p.id" +
|
||||
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
|
||||
" LEFT JOIN organization_trust ot ON u.id = ot.user_id AND ot.organization_id = p.owner_id" +
|
||||
" WHERE pt.project_id = :projectId")
|
||||
@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" +
|
||||
" LEFT JOIN global_trust gt ON u.id = gt.user_id" +
|
||||
" LEFT JOIN organization_trust ot ON u.id = ot.user_id AND ot.organization_id = p.owner_id" +
|
||||
" 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" +
|
||||
" 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" +
|
||||
" LEFT JOIN organization_trust ot ON o.id = ot.organization_id AND ot.user_id = u.id" +
|
||||
" WHERE u.id = :userId")
|
||||
@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" +
|
||||
" LEFT JOIN organization_trust ot ON o.id = ot.organization_id AND ot.user_id = u.id" +
|
||||
" WHERE u.id = :userId")
|
||||
Permission _getOrganizationPermission(long userId, String orgName, Long orgId);
|
||||
default Permission getOrganizationPermission(long userId, String orgName) {
|
||||
return _getOrganizationPermission(userId, orgName, null);
|
||||
}
|
||||
default Permission getOrganizationPermission(long userId, long orgId) {
|
||||
return _getOrganizationPermission(userId, null, orgId);
|
||||
|
||||
default Permission getOrganizationPermission(final long userId, final String orgName) {
|
||||
return this._getOrganizationPermission(userId, orgName, null);
|
||||
}
|
||||
|
||||
@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")
|
||||
default Permission getOrganizationPermission(final long userId, final long orgId) {
|
||||
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")
|
||||
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);
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import java.sql.SQLException;
|
||||
public class LogActionColumnMapper implements ColumnMapper<LogAction<?>> {
|
||||
|
||||
@Override
|
||||
public LogAction<?> map(ResultSet r, int columnNumber, StatementContext ctx) throws SQLException {
|
||||
String action = r.getString(columnNumber);
|
||||
public LogAction<?> map(final ResultSet r, final int columnNumber, final StatementContext ctx) throws SQLException {
|
||||
final String action = r.getString(columnNumber);
|
||||
if (!LogAction.LOG_REGISTRY.containsKey(action)) {
|
||||
throw new SQLDataException(action + " is not a valid LogAction");
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* {@link Permission} should have it's own mapper just since its essentially a wrapper for a long.
|
||||
* {@link Permission} should have its own mapper just since it's essentially a wrapper for a long.
|
||||
*/
|
||||
@Component
|
||||
public class PermissionMapper implements ColumnMapper<Permission> {
|
||||
|
||||
@Override
|
||||
public Permission map(ResultSet r, int columnNumber, StatementContext ctx) throws SQLException {
|
||||
public Permission map(final ResultSet r, final int columnNumber, final StatementContext ctx) throws SQLException {
|
||||
return Permission.fromLong(r.getLong(columnNumber));
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,10 @@ import java.util.Optional;
|
||||
public class RoleColumnMapperFactory implements ColumnMapperFactory {
|
||||
|
||||
@Override
|
||||
public Optional<ColumnMapper<?>> build(Type type, ConfigRegistry config) {
|
||||
if (!(type instanceof Class) || !Role.class.isAssignableFrom((Class<?>) type) || !((Class<?>) type).isEnum()) {
|
||||
public Optional<ColumnMapper<?>> build(final Type type, final ConfigRegistry config) {
|
||||
if (!(type instanceof final Class<?> clazz) || !Role.class.isAssignableFrom((Class<?>) type) || !((Class<?>) type).isEnum()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Class<?> clazz = (Class<?>) type;
|
||||
if (clazz == GlobalRole.class) {
|
||||
return Optional.of((r, columnNumber, ctx) -> Role.ID_ROLES.get(r.getLong(columnNumber)));
|
||||
} else {
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.papermc.hangar.model.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
|
||||
public enum NamedPermission {
|
||||
VIEW_PUBLIC_INFO("view_public_info", Permission.ViewPublicInfo, "ViewPublicInfo"),
|
||||
EDIT_OWN_USER_SETTINGS("edit_own_user_settings", Permission.EditOwnUserSettings, "EditOwnUserSettings"),
|
||||
|
@ -67,19 +67,13 @@ public class Permission implements Comparable<Permission>, Argument {
|
||||
public static final Permission RestoreVersion = new Permission(1L << 44);
|
||||
public static final Permission RestoreProject = new Permission(1L << 45);
|
||||
|
||||
private long value;
|
||||
private final long value;
|
||||
|
||||
@JdbiConstructor
|
||||
public Permission(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Permission() { }
|
||||
|
||||
public void setValue(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Permission add(Permission other) {
|
||||
return new Permission(value | other.value);
|
||||
}
|
||||
@ -117,6 +111,7 @@ public class Permission implements Comparable<Permission>, Argument {
|
||||
public boolean isNone() {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String toBinString() {
|
||||
return Long.toBinaryString(value);
|
||||
|
@ -24,6 +24,8 @@ public enum GlobalRole implements Role<GlobalRoleTable> {
|
||||
|
||||
ORGANIZATION("Organization", 100, OrganizationRole.ORGANIZATION_OWNER.getPermissions(), "Organization", Color.PURPLE);
|
||||
|
||||
private static final GlobalRole[] VALUES = GlobalRole.values();
|
||||
|
||||
private final String value;
|
||||
private final long roleId;
|
||||
private final Permission permissions;
|
||||
@ -104,4 +106,8 @@ public enum GlobalRole implements Role<GlobalRoleTable> {
|
||||
}
|
||||
throw new IllegalArgumentException("No GlobalRole '" + apiValue + "'");
|
||||
}
|
||||
|
||||
public static GlobalRole[] getValues() {
|
||||
return VALUES;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package io.papermc.hangar.model.internal.api.responses;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.papermc.hangar.util.PatternWrapper;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record Validation(@Nullable String regex, @Nullable Integer max, @Nullable Integer min) {
|
||||
|
||||
public Validation(final PatternWrapper wrapper, final @Nullable Integer max, final @Nullable Integer min) {
|
||||
this(wrapper.strPattern(), max, min);
|
||||
}
|
||||
|
||||
public static Validation max(final int max) {
|
||||
return new Validation((String) null, max, null);
|
||||
}
|
||||
|
||||
public static Validation size(final int max, final int min) {
|
||||
return new Validation((String) null, max, min);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package io.papermc.hangar.model.internal.api.responses;
|
||||
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public record Validations(
|
||||
Project project,
|
||||
Validation userTagline,
|
||||
Validation version,
|
||||
Validation org,
|
||||
int maxOrgCount,
|
||||
String urlRegex
|
||||
) {
|
||||
|
||||
public record Project(
|
||||
Validation name,
|
||||
Validation desc,
|
||||
Validation keywords,
|
||||
Validation channels,
|
||||
Validation pageName,
|
||||
Validation pageContent,
|
||||
int maxPageCount,
|
||||
int maxChannelCount
|
||||
) {
|
||||
}
|
||||
|
||||
public static Validations create(final HangarConfig config) {
|
||||
final Project project = new Project(
|
||||
config.projects.projectName(),
|
||||
config.projects.projectDescription(),
|
||||
config.projects.projectKeywords(),
|
||||
config.channels.channelName(),
|
||||
config.pages.pageName(),
|
||||
config.pages.pageContent(),
|
||||
config.projects.maxPages(),
|
||||
config.projects.maxChannels()
|
||||
);
|
||||
return new Validations(
|
||||
project,
|
||||
config.user.userTagline(),
|
||||
config.projects.versionName(),
|
||||
config.org.orgName(),
|
||||
config.org.createLimit(),
|
||||
config.getUrlRegex()
|
||||
);
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ spring:
|
||||
# Fake User #
|
||||
#############
|
||||
fake-user:
|
||||
enabled: false
|
||||
enabled: true
|
||||
username: paper
|
||||
email: paper@papermc.io
|
||||
# id: -3
|
||||
|
@ -3,4 +3,5 @@
|
||||
|
||||
cd frontend
|
||||
pnpm prettier --write src/types/generated/**.*
|
||||
git add src/types/generated/**.*
|
||||
pnpm lint-staged
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2b34cb6eafc8ad015d349e05a5420acd539e75ff
|
||||
Subproject commit 03f449ccfbbf9c813f4675ec6505ddc818910944
|
13
frontend/src/types/generated/icons.d.ts
vendored
13
frontend/src/types/generated/icons.d.ts
vendored
@ -7,13 +7,9 @@ export {};
|
||||
|
||||
declare module "@vue/runtime-core" {
|
||||
export interface GlobalComponents {
|
||||
IconMdiAccountPlus: typeof import("~icons/mdi/account-plus")["default"];
|
||||
IconMdiAlert: typeof import("~icons/mdi/alert")["default"];
|
||||
IconMdiAlertBox: typeof import("~icons/mdi/alert-box")["default"];
|
||||
IconMdiAlertCircleOutline: typeof import("~icons/mdi/alert-circle-outline")["default"];
|
||||
IconMdiAlertOutline: typeof import("~icons/mdi/alert-outline")["default"];
|
||||
IconMdiBell: typeof import("~icons/mdi/bell")["default"];
|
||||
IconMdiBellOutline: typeof import("~icons/mdi/bell-outline")["default"];
|
||||
IconMdiCalendar: typeof import("~icons/mdi/calendar")["default"];
|
||||
IconMdiCancel: typeof import("~icons/mdi/cancel")["default"];
|
||||
IconMdiCashMultiple: typeof import("~icons/mdi/cash-multiple")["default"];
|
||||
@ -24,33 +20,24 @@ declare module "@vue/runtime-core" {
|
||||
IconMdiClipboardOutline: typeof import("~icons/mdi/clipboard-outline")["default"];
|
||||
IconMdiClose: typeof import("~icons/mdi/close")["default"];
|
||||
IconMdiCodeBracesBox: typeof import("~icons/mdi/code-braces-box")["default"];
|
||||
IconMdiContentSave: typeof import("~icons/mdi/content-save")["default"];
|
||||
IconMdiController: typeof import("~icons/mdi/controller")["default"];
|
||||
IconMdiDelete: typeof import("~icons/mdi/delete")["default"];
|
||||
IconMdiDownload: typeof import("~icons/mdi/download")["default"];
|
||||
IconMdiDownloadOutline: typeof import("~icons/mdi/download-outline")["default"];
|
||||
IconMdiEarth: typeof import("~icons/mdi/earth")["default"];
|
||||
IconMdiEye: typeof import("~icons/mdi/eye")["default"];
|
||||
IconMdiEyeOff: typeof import("~icons/mdi/eye-off")["default"];
|
||||
IconMdiFeather: typeof import("~icons/mdi/feather")["default"];
|
||||
IconMdiFlag: typeof import("~icons/mdi/flag")["default"];
|
||||
IconMdiGamepadRoundLeft: typeof import("~icons/mdi/gamepad-round-left")["default"];
|
||||
IconMdiGavel: typeof import("~icons/mdi/gavel")["default"];
|
||||
IconMdiHelp: typeof import("~icons/mdi/help")["default"];
|
||||
IconMdiHome: typeof import("~icons/mdi/home")["default"];
|
||||
IconMdiHorseVariant: typeof import("~icons/mdi/horse-variant")["default"];
|
||||
IconMdiInformation: typeof import("~icons/mdi/information")["default"];
|
||||
IconMdiKeyOutline: typeof import("~icons/mdi/key-outline")["default"];
|
||||
IconMdiMenu: typeof import("~icons/mdi/menu")["default"];
|
||||
IconMdiMenuDown: typeof import("~icons/mdi/menu-down")["default"];
|
||||
IconMdiOpenInNew: typeof import("~icons/mdi/open-in-new")["default"];
|
||||
IconMdiPencil: typeof import("~icons/mdi/pencil")["default"];
|
||||
IconMdiPlus: typeof import("~icons/mdi/plus")["default"];
|
||||
IconMdiShape: typeof import("~icons/mdi/shape")["default"];
|
||||
IconMdiShieldSun: typeof import("~icons/mdi/shield-sun")["default"];
|
||||
IconMdiSortVariant: typeof import("~icons/mdi/sort-variant")["default"];
|
||||
IconMdiStar: typeof import("~icons/mdi/star")["default"];
|
||||
IconMdiStarOutline: typeof import("~icons/mdi/star-outline")["default"];
|
||||
IconMdiTools: typeof import("~icons/mdi/tools")["default"];
|
||||
IconMdiTrophy: typeof import("~icons/mdi/trophy")["default"];
|
||||
IconMdiWeatherNight: typeof import("~icons/mdi/weather-night")["default"];
|
||||
|
Loading…
Reference in New Issue
Block a user