mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-27 06:01:08 +08:00
parent
31fe7bb53e
commit
7a77c88bb8
@ -10,8 +10,9 @@ insert_final_newline = true
|
||||
ij_any_block_comment_at_first_column = false
|
||||
ij_any_line_comment_at_first_column = false
|
||||
ij_any_block_comment_add_space = true
|
||||
ij_typescript_use_double_quotes = false
|
||||
ij_javascript_use_double_quotes = false
|
||||
max_line_length = 160
|
||||
ij_visual_guides = 160
|
||||
|
||||
ij_sass_use_double_quotes = false
|
||||
ij_html_quote_style = double
|
||||
|
||||
@ -21,11 +22,11 @@ ij_java_use_fq_class_names = false
|
||||
ij_java_class_count_to_use_import_on_demand = 99999
|
||||
ij_java_names_count_to_use_import_on_demand = 99999
|
||||
ij_java_imports_layout = *,|,$*
|
||||
ij_java_generate_final_locals = true
|
||||
ij_java_generate_final_parameters = true
|
||||
ij_java_continuation_indent_size = 4
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[*.yaml]
|
||||
[{*.yml, *.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
|
@ -1,9 +1,11 @@
|
||||
package io.papermc.hangar.config.hangar;
|
||||
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
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.http.HttpStatus;
|
||||
|
||||
@ConfigurationProperties(prefix = "hangar.orgs")
|
||||
public record OrganizationsConfig(
|
||||
@ -12,9 +14,16 @@ public record OrganizationsConfig(
|
||||
@DefaultValue("5") int createLimit,
|
||||
@DefaultValue("3") int minNameLen,
|
||||
@DefaultValue("20") int maxNameLen,
|
||||
@DefaultValue("[a-zA-Z0-9-_]*") PatternWrapper nameRegex
|
||||
@DefaultValue("^[a-zA-Z0-9-_]+$") PatternWrapper nameRegex
|
||||
) {
|
||||
|
||||
public boolean testOrgName(final String name) {
|
||||
if (name.length() > this.maxNameLen() || name.length() < this.minNameLen() || !this.nameRegex().test(name)) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "organization.new.error.invalidName");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Validation orgName() {
|
||||
return new Validation(this.nameRegex(), this.maxNameLen(), this.minNameLen());
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public class OrganizationController extends HangarComponent {
|
||||
private final ValidationService validationService;
|
||||
|
||||
@Autowired
|
||||
public OrganizationController(UserService userService, OrganizationFactory organizationFactory, OrganizationService organizationService, OrganizationMemberService memberService, OrganizationInviteService inviteService, AuthenticationService authenticationService, ValidationService validationService) {
|
||||
public OrganizationController(final UserService userService, final OrganizationFactory organizationFactory, final OrganizationService organizationService, final OrganizationMemberService memberService, final OrganizationInviteService inviteService, final AuthenticationService authenticationService, final ValidationService validationService) {
|
||||
this.userService = userService;
|
||||
this.organizationFactory = organizationFactory;
|
||||
this.organizationService = organizationService;
|
||||
@ -75,11 +75,11 @@ public class OrganizationController extends HangarComponent {
|
||||
@Anyone
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@GetMapping("/validate")
|
||||
public void validateName(@RequestParam String name) {
|
||||
if (!validationService.isValidUsername(name)) {
|
||||
public void validateName(@RequestParam final String name) {
|
||||
if (!this.validationService.isValidOrgName(name)) {
|
||||
throw new HangarApiException("author.error.invalidUsername");
|
||||
}
|
||||
if (userService.getUserTable(name) != null) {
|
||||
if (this.userService.getUserTable(name) != null) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,45 @@
|
||||
package io.papermc.hangar.service;
|
||||
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import io.papermc.hangar.service.internal.projects.ProjectFactory;
|
||||
import io.papermc.hangar.util.StringUtils;
|
||||
import java.util.Set;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class ValidationService {
|
||||
|
||||
private static final Set<String> BANNED_ROUTES = Set.of("api", "authors", "linkout", "logged-out", "new", "unread", "notifications", "staff", "admin", "organizations", "tools", "recommended", "null", "undefined", "tos", "settings");
|
||||
private final HangarConfig config;
|
||||
|
||||
private static final Set<String> bannedRoutes = Set.of("api", "authors", "linkout", "logged-out", "new", "unread", "notifications", "staff", "admin", "organizations", "tools", "recommended", "null", "undefined", "tos", "settings");
|
||||
|
||||
public ValidationService(HangarConfig config) {
|
||||
public ValidationService(final HangarConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public boolean isValidOrgName(final String name) {
|
||||
return this.config.org.testOrgName(name) && this.isValidUsername(name);
|
||||
}
|
||||
|
||||
public boolean isValidUsername(String name) {
|
||||
name = StringUtils.compact(name);
|
||||
if (bannedRoutes.contains(name)) {
|
||||
if (BANNED_ROUTES.contains(name)) {
|
||||
return false;
|
||||
}
|
||||
if (name.length() < 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return name.length() >= 1;
|
||||
}
|
||||
|
||||
public @Nullable String isValidProjectName(String name) {
|
||||
name = StringUtils.compact(name);
|
||||
String error = null;
|
||||
if (bannedRoutes.contains(name)) {
|
||||
if (BANNED_ROUTES.contains(name)) {
|
||||
error = "invalidName";
|
||||
} else if (name.length() < 3) {
|
||||
error = "tooShortName";
|
||||
} else if (name.length() > config.projects.maxNameLen()) {
|
||||
} else if (name.length() > this.config.projects.maxNameLen()) {
|
||||
error = "tooLongName";
|
||||
} else if (name.contains(ProjectFactory.SOFT_DELETION_SUFFIX) || !config.projects.nameRegex().test(name)) {
|
||||
} else if (name.contains(ProjectFactory.SOFT_DELETION_SUFFIX) || !this.config.projects.nameRegex().test(name)) {
|
||||
error = "invalidName";
|
||||
}
|
||||
return error != null ? "project.new.error." + error : null;
|
||||
@ -49,21 +47,18 @@ public class ValidationService {
|
||||
|
||||
public boolean isValidVersionName(String name) {
|
||||
name = StringUtils.compact(name);
|
||||
if (bannedRoutes.contains(name) || name.contains(ProjectFactory.SOFT_DELETION_SUFFIX)) {
|
||||
if (BANNED_ROUTES.contains(name) || name.contains(ProjectFactory.SOFT_DELETION_SUFFIX)) {
|
||||
return false;
|
||||
}
|
||||
if (name.length() < 1 || name.length() > config.projects.maxVersionNameLen() || !config.projects.versionNameRegex().test(name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return name.length() >= 1 && name.length() <= this.config.projects.maxVersionNameLen() && this.config.projects.versionNameRegex().test(name);
|
||||
}
|
||||
|
||||
public void testPageName(String name) {
|
||||
name = StringUtils.compact(name);
|
||||
if (bannedRoutes.contains(name)) {
|
||||
if (BANNED_ROUTES.contains(name)) {
|
||||
throw new HangarApiException("page.new.error.invalidName");
|
||||
}
|
||||
|
||||
config.pages.testPageName(name);
|
||||
this.config.pages.testPageName(name);
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ public class ProjectFactory extends HangarComponent {
|
||||
}
|
||||
|
||||
public void checkProjectAvailability(final long userId, final String name) {
|
||||
checkProjectAvailability(userId, name, false);
|
||||
this.checkProjectAvailability(userId, name, false);
|
||||
}
|
||||
|
||||
public void checkProjectAvailability(final long userId, final String name, final boolean skipNameChecking) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useSettingsStore } from "~/store/settings";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { settingsLog } from "~/lib/composables/useLog";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useSettingsStore } from "~/store/settings";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import Announcement from "~/components/Announcement.vue";
|
||||
import DropdownButton from "~/lib/components/design/DropdownButton.vue";
|
||||
import DropdownItem from "~/lib/components/design/DropdownItem.vue";
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 03f449ccfbbf9c813f4675ec6505ddc818910944
|
||||
Subproject commit 39d774394e68b982a56b7c28b06d50687be5c027
|
@ -11,7 +11,7 @@ import { hasPerms, toNamedPermission } from "~/composables/usePerm";
|
||||
import { NamedPermission, PermissionType } from "~/types/enums";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useSettingsStore } from "~/store/settings";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import * as domain from "~/composables/useDomain";
|
||||
|
||||
export const install: UserModule = async ({ request, response, router, redirect }) => {
|
||||
|
@ -258,7 +258,7 @@ useHead(
|
||||
<Tabs v-model="selectedTab" :tabs="tabs">
|
||||
<template #general>
|
||||
<ProjectSettingsSection title="project.settings.category" description="project.settings.categorySub">
|
||||
<InputSelect v-model="form.category" :values="backendData.categoryOptions" :rules="[required()]" />
|
||||
<InputSelect v-model="form.category" :values="backendData.categoryOptions" :rules="[required()]" i18n-text-values />
|
||||
</ProjectSettingsSection>
|
||||
<ProjectSettingsSection title="project.settings.description" description="project.settings.descriptionSub">
|
||||
<InputText
|
||||
|
@ -11,7 +11,7 @@ import { useRoute, useRouter } from "vue-router";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import Steps, { Step } from "~/lib/components/design/Steps.vue";
|
||||
import { useSettingsStore } from "~/store/settings";
|
||||
import { useSettingsStore } from "~/store/useSettingsStore";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
@ -202,6 +202,7 @@ function createProject() {
|
||||
:values="backendData.categoryOptions"
|
||||
:label="i18n.t('project.new.step2.projectCategory')"
|
||||
:rules="[required()]"
|
||||
i18n-text-values
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,6 @@ import { NamedPermission, Platform, ProjectCategory, Prompt } from "~/types/enum
|
||||
import { Announcement as AnnouncementObject, Announcement, IPermission, Role } from "hangar-api";
|
||||
import { fetchIfNeeded, useInternalApi } from "~/composables/useApi";
|
||||
import { Option } from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
interface Validation {
|
||||
regex?: string;
|
||||
@ -109,13 +108,11 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
||||
}
|
||||
}
|
||||
|
||||
const visibleCategories = computed(() => [...(projectCategories.value?.values() || [])].filter((value) => value.visible));
|
||||
const visibleCategories = computed<IProjectCategory[]>(() => [...(projectCategories.value?.values() || [])].filter((value) => value.visible));
|
||||
const visiblePlatforms = computed(() => (platforms.value ? [...platforms.value.values()].filter((value) => value.visible) : []));
|
||||
|
||||
const licenseOptions = computed<Option[]>(() => licenses.value.map<Option>((l) => ({ value: l, text: l })));
|
||||
const categoryOptions = computed<Option[]>(() =>
|
||||
visibleCategories.value.map<Option>((c) => ({ value: c.apiName, text: useI18n({ useScope: "global" }).t(c.title) }))
|
||||
);
|
||||
const categoryOptions = computed<Option[]>(() => visibleCategories.value.map<Option>((c) => ({ value: c.apiName, text: c.title })));
|
||||
|
||||
return {
|
||||
projectCategories,
|
||||
|
14
frontend/src/types/generated/icons.d.ts
vendored
14
frontend/src/types/generated/icons.d.ts
vendored
@ -7,6 +7,7 @@ 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"];
|
||||
IconMdiAlertOutline: typeof import("~icons/mdi/alert-outline")["default"];
|
||||
@ -16,11 +17,17 @@ declare module "@vue/runtime-core" {
|
||||
IconMdiChat: typeof import("~icons/mdi/chat")["default"];
|
||||
IconMdiCheck: typeof import("~icons/mdi/check")["default"];
|
||||
IconMdiCheckBold: typeof import("~icons/mdi/check-bold")["default"];
|
||||
IconMdiChevronDoubleDown: typeof import("~icons/mdi/chevron-double-down")["default"];
|
||||
IconMdiCircle: typeof import("~icons/mdi/circle")["default"];
|
||||
IconMdiClipboardOutline: typeof import("~icons/mdi/clipboard-outline")["default"];
|
||||
IconMdiClose: typeof import("~icons/mdi/close")["default"];
|
||||
IconMdiCloseCircle: typeof import("~icons/mdi/close-circle")["default"];
|
||||
IconMdiCloudSearch: typeof import("~icons/mdi/cloud-search")["default"];
|
||||
IconMdiCodeBracesBox: typeof import("~icons/mdi/code-braces-box")["default"];
|
||||
IconMdiCogTransfer: typeof import("~icons/mdi/cog-transfer")["default"];
|
||||
IconMdiContentSave: typeof import("~icons/mdi/content-save")["default"];
|
||||
IconMdiController: typeof import("~icons/mdi/controller")["default"];
|
||||
IconMdiDeleteAlert: typeof import("~icons/mdi/delete-alert")["default"];
|
||||
IconMdiDownload: typeof import("~icons/mdi/download")["default"];
|
||||
IconMdiEarth: typeof import("~icons/mdi/earth")["default"];
|
||||
IconMdiEye: typeof import("~icons/mdi/eye")["default"];
|
||||
@ -32,12 +39,19 @@ declare module "@vue/runtime-core" {
|
||||
IconMdiHorseVariant: typeof import("~icons/mdi/horse-variant")["default"];
|
||||
IconMdiInformation: typeof import("~icons/mdi/information")["default"];
|
||||
IconMdiKeyOutline: typeof import("~icons/mdi/key-outline")["default"];
|
||||
IconMdiLicense: typeof import("~icons/mdi/license")["default"];
|
||||
IconMdiLink: typeof import("~icons/mdi/link")["default"];
|
||||
IconMdiLockOpenOutline: typeof import("~icons/mdi/lock-open-outline")["default"];
|
||||
IconMdiLockOutline: typeof import("~icons/mdi/lock-outline")["default"];
|
||||
IconMdiMenu: typeof import("~icons/mdi/menu")["default"];
|
||||
IconMdiOpenInNew: typeof import("~icons/mdi/open-in-new")["default"];
|
||||
IconMdiPencil: typeof import("~icons/mdi/pencil")["default"];
|
||||
IconMdiRenameBox: typeof import("~icons/mdi/rename-box")["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"];
|
||||
IconMdiSubdirectoryArrowLeft: typeof import("~icons/mdi/subdirectory-arrow-left")["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