From 89b392dc3d7b4716df06173566be25179a9c7ef0 Mon Sep 17 00:00:00 2001 From: tdgao Date: Wed, 26 Aug 2026 13:27:47 -0600 Subject: [PATCH] fix: allow admin role to by pass error validator check --- .../[type]/[project]/settings/description.vue | 14 ++++++---- .../[type]/[project]/settings/disclosures.vue | 11 +++++--- .../[type]/[project]/settings/general.vue | 8 ++++-- .../pages/[type]/[project]/settings/index.vue | 7 +++-- .../[type]/[project]/settings/license.vue | 26 +++++++++++-------- .../pages/[type]/[project]/settings/links.vue | 17 ++++++------ .../[type]/[project]/settings/server.vue | 8 ++++-- .../pages/[type]/[project]/settings/tags.vue | 7 ++--- 8 files changed, 61 insertions(+), 37 deletions(-) diff --git a/apps/frontend/src/pages/[type]/[project]/settings/description.vue b/apps/frontend/src/pages/[type]/[project]/settings/description.vue index 6f40fec6fb..3fea8b9057 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/description.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/description.vue @@ -48,7 +48,7 @@ import { usePageLeaveSafety, useSavable, } from '@modrinth/ui' -import { TeamMemberPermission } from '@modrinth/utils' +import { isAdmin, TeamMemberPermission } from '@modrinth/utils' import { computed, useTemplateRef } from 'vue' import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue' @@ -81,11 +81,13 @@ const { const { confirmLeaveModal } = usePageLeaveSafety(hasChanges) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) const hasPermission = computed( () => - !!currentMember.value && - (currentMember.value.permissions & TeamMemberPermission.EDIT_BODY) === - TeamMemberPermission.EDIT_BODY, + isAdminUser.value || + (!!currentMember.value && + (currentMember.value.permissions & TeamMemberPermission.EDIT_BODY) === + TeamMemberPermission.EDIT_BODY), ) const { pending: descriptionLinksPending, validation: descriptionValidation } = useProjectDescriptionValidation(() => current.value.description) @@ -93,7 +95,9 @@ const hasValidationIssues = computed(() => descriptionValidation.value.some((validation) => validation.severity === 'error'), ) const canSave = computed( - () => hasPermission.value && !hasValidationIssues.value && !descriptionLinksPending.value, + () => + hasPermission.value && + (isAdminUser.value || (!hasValidationIssues.value && !descriptionLinksPending.value)), ) async function save() { diff --git a/apps/frontend/src/pages/[type]/[project]/settings/disclosures.vue b/apps/frontend/src/pages/[type]/[project]/settings/disclosures.vue index 0d79f28ca8..d1bbe8b702 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/disclosures.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/disclosures.vue @@ -15,7 +15,7 @@ import { useSavable, useVIntl, } from '@modrinth/ui' -import { isStaff, TeamMemberPermission } from '@modrinth/utils' +import { isAdmin, isStaff, TeamMemberPermission } from '@modrinth/utils' import { useQuery, useQueryClient } from '@tanstack/vue-query' import { computed, watch } from 'vue' @@ -172,8 +172,11 @@ function resolveUpdatedBy(userId: string | null | undefined): DisclosureUpdatedB ) } +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) const hasPermission = computed( - () => !!((currentMember.value?.permissions ?? 0) & TeamMemberPermission.EDIT_DETAILS), + () => + isAdminUser.value || + !!((currentMember.value?.permissions ?? 0) & TeamMemberPermission.EDIT_DETAILS), ) const { @@ -249,7 +252,9 @@ watch( const issues = computed(() => getDisclosureFormIssues(current.value, projectTypes.value)) -const canSave = computed(() => hasPermission.value && issues.value.length === 0) +const canSave = computed( + () => hasPermission.value && (isAdminUser.value || issues.value.length === 0), +) const saveDisabledReason = computed(() => { if (!hasPermission.value) { diff --git a/apps/frontend/src/pages/[type]/[project]/settings/general.vue b/apps/frontend/src/pages/[type]/[project]/settings/general.vue index 1dc9527a0c..4d98c37194 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/general.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/general.vue @@ -13,6 +13,7 @@ import { useSavable, useVIntl, } from '@modrinth/ui' +import { isAdmin } from '@modrinth/utils' import ValidationMessage from '~/components/ValidationMessage.vue' import SlugSuggestions from '~/components/ui/SlugSuggestions.vue' @@ -27,7 +28,7 @@ import { const { formatMessage } = useVIntl() -const { allMembers, projectV2: project, patchProject } = injectProjectPageContext() +const { allMembers, currentMember, projectV2: project, patchProject } = injectProjectPageContext() useProjectSettingsHeadTitle(commonProjectSettingsMessages.general) @@ -61,7 +62,10 @@ const taglineValidation = useProjectSummaryValidation( () => current.value.tagline, () => current.value.title, ) -const canSave = computed(() => !titleValidation.value && !taglineValidation.value) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) +const canSave = computed( + () => isAdminUser.value || (!titleValidation.value && !taglineValidation.value), +) const { onFocusIn: onSlugSuggestionFocusIn, onFocusOut: onSlugSuggestionFocusOut, diff --git a/apps/frontend/src/pages/[type]/[project]/settings/index.vue b/apps/frontend/src/pages/[type]/[project]/settings/index.vue index 50b56d20bd..8c7b7b3b61 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/index.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/index.vue @@ -327,7 +327,7 @@ import { usePageLeaveSafety, useVIntl, } from '@modrinth/ui' -import { fileIsValid, formatProjectStatus } from '@modrinth/utils' +import { fileIsValid, formatProjectStatus, isAdmin } from '@modrinth/utils' import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue' import SlugSuggestions from '~/components/ui/SlugSuggestions.vue' @@ -422,9 +422,12 @@ const bannerFile = ref(null) const bannerGalleryImage = computed(() => project.value.gallery?.find((img) => img.name === MC_SERVER_BANNER_NAME), ) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) const hasPermission = computed(() => { const EDIT_DETAILS = 1 << 2 - return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS + return ( + isAdminUser.value || ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS + ) }) const nameValidation = useProjectTitleValidation(name) diff --git a/apps/frontend/src/pages/[type]/[project]/settings/license.vue b/apps/frontend/src/pages/[type]/[project]/settings/license.vue index e0da19e6de..659ce9e2a7 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/license.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/license.vue @@ -162,7 +162,7 @@ import { usePageLeaveSafety, useSavable, } from '@modrinth/ui' -import { builtinLicenses, formatProjectType, TeamMemberPermission } from '@modrinth/utils' +import { builtinLicenses, formatProjectType, isAdmin, TeamMemberPermission } from '@modrinth/utils' import { computed } from 'vue' import ValidationMessage from '@/components/ValidationMessage.vue' @@ -250,19 +250,23 @@ const selectedLicense = computed({ }, }) -const hasPermission = computed(() => { - return (currentMember.value?.permissions ?? 0) & TeamMemberPermission.EDIT_DETAILS -}) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) +const hasPermission = computed( + () => + isAdminUser.value || + Boolean((currentMember.value?.permissions ?? 0) & TeamMemberPermission.EDIT_DETAILS), +) const canSave = computed( () => - Boolean(hasPermission.value) && - !( - current.value.license.friendly === 'Custom' && - (current.value.license.short === '' || current.value.licenseUrl === '') - ) && - effectiveLicenseCheck.value?.severity !== 'error' && - !isLinkCheckPending(licenseContext.value), + hasPermission.value && + (isAdminUser.value || + (!( + current.value.license.friendly === 'Custom' && + (current.value.license.short === '' || current.value.licenseUrl === '') + ) && + effectiveLicenseCheck.value?.severity !== 'error' && + !isLinkCheckPending(licenseContext.value))), ) async function save() { diff --git a/apps/frontend/src/pages/[type]/[project]/settings/links.vue b/apps/frontend/src/pages/[type]/[project]/settings/links.vue index d9a41ab494..12468d37e8 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/links.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/links.vue @@ -213,6 +213,7 @@ import { usePageLeaveSafety, useSavable, } from '@modrinth/ui' +import { isAdmin } from '@modrinth/utils' import ValidationMessage from '@/components/ValidationMessage.vue' @@ -362,9 +363,11 @@ watch( { deep: true, immediate: true }, ) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) + const hasPermission = computed(() => { const EDIT_DETAILS = 1 << 2 - return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS + return isAdminUser.value || (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS }) function donationsMapFromLinkUrls(linkUrls) { @@ -443,6 +446,9 @@ const patchData = computed(() => { }) const canSave = computed(() => { + if (!hasPermission.value || Object.keys(patchData.value).length === 0) return false + if (isAdminUser.value) return true + const checks = isServerProject.value ? [siteCheck, storeCheck, wikiCheck, discordInviteCheck] : [issuesCheck, sourceCheck, wikiCheck, discordInviteCheck] @@ -461,14 +467,7 @@ const canSave = computed(() => { (donationCheckTimers.size > 0 || donationLinks.value.some((row) => isLinkCheckPending(donationContext(row)))) - return ( - hasPermission.value && - !fieldsInvalid && - !fieldsPending && - !donationsInvalid && - !donationsPending && - Object.keys(patchData.value).length > 0 - ) + return !fieldsInvalid && !fieldsPending && !donationsInvalid && !donationsPending }) const saving = ref(false) diff --git a/apps/frontend/src/pages/[type]/[project]/settings/server.vue b/apps/frontend/src/pages/[type]/[project]/settings/server.vue index 7449201047..abf0f0cc1a 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/server.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/server.vue @@ -186,6 +186,7 @@ import { usePageLeaveSafety, useVIntl, } from '@modrinth/ui' +import { isAdmin } from '@modrinth/utils' import CompatibilityCard from '~/components/ui/project-settings/CompatibilityCard.vue' @@ -298,9 +299,12 @@ watch(javaAddress, () => { }, 500) }) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) const hasPermission = computed(() => { const EDIT_DETAILS = 1 << 2 - return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS + return ( + isAdminUser.value || ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS + ) }) async function pingJavaServer() { @@ -478,7 +482,7 @@ function resetChanges() { } async function handleSave() { - if (javaAddress.value.trim() && !javaPingResult.value?.online) { + if (!isAdminUser.value && javaAddress.value.trim() && !javaPingResult.value?.online) { addNotification({ title: formatMessage(messages.cannotSaveTitle), text: formatMessage(messages.cannotSaveText), diff --git a/apps/frontend/src/pages/[type]/[project]/settings/tags.vue b/apps/frontend/src/pages/[type]/[project]/settings/tags.vue index 76b7f0998b..e3b182e16d 100644 --- a/apps/frontend/src/pages/[type]/[project]/settings/tags.vue +++ b/apps/frontend/src/pages/[type]/[project]/settings/tags.vue @@ -19,7 +19,7 @@ import { useSavable, useVIntl, } from '@modrinth/ui' -import { capitalizeString, sortedCategories } from '@modrinth/utils' +import { capitalizeString, isAdmin, sortedCategories } from '@modrinth/utils' import { computed } from 'vue' interface Category { @@ -145,7 +145,7 @@ const groupDescriptionMessages: Record = { 'performance impact': messages.performanceImpactDescription, } -const { projectV2: project, projectV3, patchProject } = injectProjectPageContext() +const { currentMember, projectV2: project, projectV3, patchProject } = injectProjectPageContext() useProjectSettingsHeadTitle(messages.title) @@ -310,7 +310,8 @@ const isFeaturedLimitReached = computed( () => current.value.featuredTags.length >= MAX_FEATURED_TAGS, ) -const canSave = computed(() => current.value.featuredTags.length > 0) +const isAdminUser = computed(() => isAdmin(currentMember.value?.user)) +const canSave = computed(() => isAdminUser.value || current.value.featuredTags.length > 0) const tooManyTagsWarning = computed(() => { const tagCount = current.value.selectedTags.length