mirror of
https://github.com/modrinth/code.git
synced 2026-08-01 05:36:39 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72e528b33f | ||
|
|
0c1c7e2fe8 | ||
|
|
cf16939519 |
@@ -5,7 +5,7 @@
|
||||
v-if="!isServerProject"
|
||||
:project="data"
|
||||
:tags="{ loaders: allLoaders, gameVersions: allGameVersions }"
|
||||
:project-v3="projectV3"
|
||||
:v3-metadata="projectV3"
|
||||
class="project-sidebar-section"
|
||||
/>
|
||||
<ProjectSidebarServerInfo
|
||||
|
||||
@@ -114,12 +114,12 @@ pub async fn fetch_neo(
|
||||
}
|
||||
|
||||
let forge_versions = fetch_xml::<Metadata>(
|
||||
"https://maven.neoforged.net/releases/net/neoforged/forge/maven-metadata.xml",
|
||||
"https://maven.neoforged.net/net/neoforged/forge/maven-metadata.xml",
|
||||
&semaphore,
|
||||
)
|
||||
.await?;
|
||||
let neo_versions = fetch_xml::<Metadata>(
|
||||
"https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml",
|
||||
"https://maven.neoforged.net/net/neoforged/neoforge/maven-metadata.xml",
|
||||
&semaphore,
|
||||
)
|
||||
.await?;
|
||||
@@ -133,7 +133,7 @@ pub async fn fetch_neo(
|
||||
|
||||
Ok(ForgeVersion {
|
||||
format_version: 2,
|
||||
installer_url: format!("https://maven.neoforged.net/releases/net/neoforged/forge/{loader_version}/forge-{loader_version}-installer.jar"),
|
||||
installer_url: format!("https://maven.neoforged.net/net/neoforged/forge/{loader_version}/forge-{loader_version}-installer.jar"),
|
||||
raw: loader_version,
|
||||
loader_version: version_split,
|
||||
game_version: "1.20.1".to_string(), // All NeoForge Forge versions are for 1.20.1
|
||||
@@ -159,7 +159,7 @@ pub async fn fetch_neo(
|
||||
|
||||
Ok(ForgeVersion {
|
||||
format_version: 2,
|
||||
installer_url: format!("https://maven.neoforged.net/releases/net/neoforged/neoforge/{loader_version}/neoforge-{loader_version}-installer.jar"),
|
||||
installer_url: format!("https://maven.neoforged.net/net/neoforged/neoforge/{loader_version}/neoforge-{loader_version}-installer.jar"),
|
||||
loader_version: loader_version.clone(),
|
||||
raw: loader_version,
|
||||
game_version,
|
||||
|
||||
@@ -38,7 +38,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
showProjectPageQuickServerButton: false,
|
||||
newProjectGeneralSettings: false,
|
||||
newProjectEnvironmentSettings: true,
|
||||
archonSentryCapture: false,
|
||||
hideRussiaCensorshipBanner: false,
|
||||
disablePrettyProjectUrlRedirects: false,
|
||||
hidePreviewBanner: false,
|
||||
|
||||
@@ -13,8 +13,6 @@ import {
|
||||
} from '@modrinth/api-client'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
|
||||
async function getRateLimitKeyFromSecretsStore(): Promise<string | undefined> {
|
||||
try {
|
||||
const mod = 'cloudflare:workers'
|
||||
@@ -30,7 +28,6 @@ export function createModrinthClient(
|
||||
auth: Ref<{ token: string | undefined }>,
|
||||
config: { apiBaseUrl: string; archonBaseUrl: string; rateLimitKey?: string },
|
||||
): NuxtModrinthClient {
|
||||
const flags = useFeatureFlags()
|
||||
const optionalFeatures = [
|
||||
import.meta.dev ? (new VerboseLoggingFeature() as AbstractFeature) : undefined,
|
||||
].filter(Boolean) as AbstractFeature[]
|
||||
@@ -38,7 +35,6 @@ export function createModrinthClient(
|
||||
const clientConfig: NuxtClientConfig = {
|
||||
labrinthBaseUrl: config.apiBaseUrl,
|
||||
archonBaseUrl: config.archonBaseUrl,
|
||||
archonSentryCapture: () => flags.value.archonSentryCapture,
|
||||
rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore,
|
||||
features: [
|
||||
// for modrinth hosting
|
||||
|
||||
@@ -1313,9 +1313,6 @@
|
||||
"hosting.loader.failed-to-repair": {
|
||||
"message": "Failed to repair server"
|
||||
},
|
||||
"hosting.loader.failed-to-reset-to-onboarding": {
|
||||
"message": "Failed to reset server to onboarding"
|
||||
},
|
||||
"hosting.loader.failed-to-save-settings": {
|
||||
"message": "Failed to save installation settings"
|
||||
},
|
||||
@@ -1337,24 +1334,6 @@
|
||||
"hosting.loader.reset-server-description": {
|
||||
"message": "Removes all data on your server, including your worlds, mods, and configuration files. Backups will remain and can be restored."
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-button": {
|
||||
"message": "Reset to onboarding"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-modal-description": {
|
||||
"message": "This will send the server back into onboarding so setup can be completed again. Are you sure you want to continue?"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-modal-title": {
|
||||
"message": "Reset to onboarding"
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-success-description": {
|
||||
"message": "The server has been returned to the onboarding flow."
|
||||
},
|
||||
"hosting.loader.reset-to-onboarding-success-title": {
|
||||
"message": "Server reset to onboarding"
|
||||
},
|
||||
"hosting.loader.support-options-title": {
|
||||
"message": "Support options"
|
||||
},
|
||||
"hosting.plan.out-of-stock": {
|
||||
"message": "Out of stock"
|
||||
},
|
||||
|
||||
@@ -877,7 +877,7 @@
|
||||
v-if="projectV3Loaded && !isServerProject"
|
||||
:project="project"
|
||||
:tags="tags"
|
||||
:project-v3="projectV3"
|
||||
:v3-metadata="projectV3"
|
||||
class="card flex-card experimental-styles-within"
|
||||
/>
|
||||
<AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" />
|
||||
|
||||
@@ -341,8 +341,7 @@
|
||||
<div v-if="project.project_type !== 'resourcepack'">
|
||||
<h4>Loaders</h4>
|
||||
|
||||
<span v-if="noModpackLoader">No mod loader</span>
|
||||
<Categories v-else :categories="version.loaders ?? []" :type="project.project_type" />
|
||||
<Categories :categories="version.loaders" :type="project.project_type" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>Game versions</h4>
|
||||
@@ -699,25 +698,6 @@ const title = computed(
|
||||
() => `${isCreating.value ? 'Create Version' : version.value.name} - ${project.value.title}`,
|
||||
)
|
||||
|
||||
const modpackLoaders = computed<string[]>(() => {
|
||||
if (project.value.project_type !== 'modpack') {
|
||||
return []
|
||||
}
|
||||
|
||||
if (Array.isArray(version.value.mrpack_loaders) && version.value.mrpack_loaders.length > 0) {
|
||||
return version.value.mrpack_loaders
|
||||
}
|
||||
|
||||
return (version.value.loaders ?? []).filter((loader: string) => loader !== 'mrpack')
|
||||
})
|
||||
|
||||
const noModpackLoader = computed(
|
||||
() =>
|
||||
project.value.project_type === 'modpack' &&
|
||||
modpackLoaders.value.length === 1 &&
|
||||
modpackLoaders.value[0] === 'minecraft',
|
||||
)
|
||||
|
||||
const description = computed(
|
||||
() =>
|
||||
`Download ${project.value.title} ${
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<h1>{{ formatMessage(commonMessages.errorLabel) }}</h1>
|
||||
</div>
|
||||
<p>
|
||||
<span>{{ error.data?.error }}: </span>
|
||||
{{ error.data?.description }}
|
||||
<span>{{ error.data.error }}: </span>
|
||||
{{ error.data.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="app && createdBy && authorizationData" class="oauth-items">
|
||||
<div v-else class="oauth-items">
|
||||
<div class="connected-items">
|
||||
<div class="profile-pics">
|
||||
<Avatar size="md" :src="app.icon_url" />
|
||||
@@ -164,31 +164,24 @@ const {
|
||||
data: authorizationData,
|
||||
isPending: pending,
|
||||
error,
|
||||
suspense: authSusp,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['authorization', clientId, redirectUri, scope, state]),
|
||||
queryFn: getFlowIdAuthorization,
|
||||
enabled: computed(() => !!clientId && !!redirectUri && !!scope),
|
||||
})
|
||||
|
||||
const { data: app, suspense: appSusp } = useQuery({
|
||||
const { data: app } = useQuery({
|
||||
queryKey: computed(() => ['oauth/app', clientId]),
|
||||
queryFn: () => client.labrinth.oauth_internal.getApp(clientId),
|
||||
enabled: computed(() => !!clientId),
|
||||
})
|
||||
|
||||
const { data: createdBy, suspense: userSusp } = useQuery({
|
||||
const { data: createdBy } = useQuery({
|
||||
queryKey: computed(() => ['user', app.value?.created_by]),
|
||||
queryFn: () => client.labrinth.users_v2.get(app.value.created_by),
|
||||
enabled: computed(() => !!app.value?.created_by),
|
||||
})
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
await authSusp()
|
||||
await appSusp()
|
||||
await userSusp()
|
||||
})
|
||||
|
||||
const scopeDefinitions = computed(() =>
|
||||
scopesToDefinitions(BigInt(authorizationData.value?.requested_scopes || 0)),
|
||||
)
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 rounded-2xl bg-surface-3 p-6">
|
||||
<ConfirmModal
|
||||
ref="resetToOnboardingModal"
|
||||
:title="formatMessage(messages.resetToOnboardingModalTitle)"
|
||||
:description="formatMessage(messages.resetToOnboardingModalDescription)"
|
||||
:proceed-label="formatMessage(messages.resetToOnboardingButton)"
|
||||
@proceed="confirmResetToOnboarding"
|
||||
/>
|
||||
|
||||
<InstallationSettingsLayout ref="installationSettingsLayout">
|
||||
<template #extra>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
@@ -36,24 +28,6 @@
|
||||
/>
|
||||
</template>
|
||||
</InstallationSettingsLayout>
|
||||
|
||||
<div v-if="isSiteAdmin" class="flex flex-col gap-2.5">
|
||||
<span class="text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.supportOptionsTitle) }}
|
||||
</span>
|
||||
<div>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
class="!shadow-none"
|
||||
:disabled="!worldId || isResettingToOnboarding"
|
||||
@click="resetToOnboardingModal?.show()"
|
||||
>
|
||||
<RotateCounterClockwiseIcon class="size-5" />
|
||||
{{ formatMessage(messages.resetToOnboardingButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -63,7 +37,6 @@ import { RotateCounterClockwiseIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
formatLoaderLabel,
|
||||
injectModrinthClient,
|
||||
@@ -133,35 +106,6 @@ const messages = defineMessages({
|
||||
id: 'hosting.loader.failed-to-unlink',
|
||||
defaultMessage: 'Failed to unlink modpack',
|
||||
},
|
||||
supportOptionsTitle: {
|
||||
id: 'hosting.loader.support-options-title',
|
||||
defaultMessage: 'Support options',
|
||||
},
|
||||
resetToOnboardingButton: {
|
||||
id: 'hosting.loader.reset-to-onboarding-button',
|
||||
defaultMessage: 'Reset to onboarding',
|
||||
},
|
||||
resetToOnboardingModalTitle: {
|
||||
id: 'hosting.loader.reset-to-onboarding-modal-title',
|
||||
defaultMessage: 'Reset to onboarding',
|
||||
},
|
||||
resetToOnboardingModalDescription: {
|
||||
id: 'hosting.loader.reset-to-onboarding-modal-description',
|
||||
defaultMessage:
|
||||
'This will send the server back into onboarding so setup can be completed again. Are you sure you want to continue?',
|
||||
},
|
||||
resetToOnboardingSuccessTitle: {
|
||||
id: 'hosting.loader.reset-to-onboarding-success-title',
|
||||
defaultMessage: 'Server reset to onboarding',
|
||||
},
|
||||
resetToOnboardingSuccessDescription: {
|
||||
id: 'hosting.loader.reset-to-onboarding-success-description',
|
||||
defaultMessage: 'The server has been returned to the onboarding flow.',
|
||||
},
|
||||
failedToResetToOnboarding: {
|
||||
id: 'hosting.loader.failed-to-reset-to-onboarding',
|
||||
defaultMessage: 'Failed to reset server to onboarding',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -212,13 +156,8 @@ const modpackVersionsQuery = useQuery({
|
||||
enabled: computed(() => !!modpack.value?.spec.project_id),
|
||||
})
|
||||
|
||||
const auth = await useAuth()
|
||||
const isSiteAdmin = computed(() => auth.value?.user?.role === 'admin')
|
||||
|
||||
const editingPlatform = ref(server.value?.loader?.toLowerCase() ?? 'vanilla')
|
||||
const editingGameVersion = ref(server.value?.mc_version ?? '')
|
||||
const resetToOnboardingModal = ref<InstanceType<typeof ConfirmModal>>()
|
||||
const isResettingToOnboarding = ref(false)
|
||||
|
||||
const modLoaders = ['fabric', 'forge', 'quilt', 'neoforge']
|
||||
|
||||
@@ -398,17 +337,11 @@ provideInstallationSettings({
|
||||
const currentPlatform = server.value?.loader?.toLowerCase() ?? 'vanilla'
|
||||
const platformChanged = platform !== currentPlatform
|
||||
|
||||
let resolvedLoaderVersion = loaderVersionId
|
||||
if (!resolvedLoaderVersion && platform !== 'vanilla') {
|
||||
const versions = getLoaderVersionsForGameVersion(platform, gameVersion)
|
||||
resolvedLoaderVersion = versions[0]?.id ?? null
|
||||
}
|
||||
|
||||
debug('save: emitting reinstall before API call')
|
||||
emit(
|
||||
'reinstall',
|
||||
platformChanged
|
||||
? { loader: platform, lVersion: resolvedLoaderVersion, mVersion: gameVersion }
|
||||
? { loader: platform, lVersion: loaderVersionId, mVersion: gameVersion }
|
||||
: { mVersion: gameVersion },
|
||||
)
|
||||
try {
|
||||
@@ -416,7 +349,7 @@ provideInstallationSettings({
|
||||
const request: Archon.Content.v1.InstallWorldContent = {
|
||||
content_variant: 'bare',
|
||||
loader: toApiLoader(platform),
|
||||
version: resolvedLoaderVersion ?? '',
|
||||
version: loaderVersionId ?? '',
|
||||
game_version: gameVersion || undefined,
|
||||
soft_override: true,
|
||||
}
|
||||
@@ -657,30 +590,4 @@ function onBrowseModpacks() {
|
||||
query: { sid: serverId, from: 'reset-server', wid: worldId.value },
|
||||
})
|
||||
}
|
||||
|
||||
async function confirmResetToOnboarding() {
|
||||
if (!worldId.value) return
|
||||
|
||||
try {
|
||||
isResettingToOnboarding.value = true
|
||||
await client.archon.servers_v1.resetToOnboarding(serverId, worldId.value)
|
||||
server.value.flows = { intro: true }
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', serverId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['servers', 'v1', 'detail', serverId] }),
|
||||
])
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: formatMessage(messages.resetToOnboardingSuccessTitle),
|
||||
text: formatMessage(messages.resetToOnboardingSuccessDescription),
|
||||
})
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : formatMessage(messages.failedToResetToOnboarding),
|
||||
})
|
||||
} finally {
|
||||
isResettingToOnboarding.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -196,6 +196,10 @@ import { isDarkTheme, type Theme } from '~/plugins/theme/index.ts'
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
useHead({
|
||||
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
headTitle: {
|
||||
id: 'settings.head-title',
|
||||
@@ -215,10 +219,6 @@ const developerModeBanner = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||
})
|
||||
|
||||
const layoutMode = defineMessages({
|
||||
rows: {
|
||||
id: 'settings.display.project-list-layouts.mode.rows',
|
||||
|
||||
Generated
+2
-3
@@ -29,8 +29,7 @@
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"severe",
|
||||
"malware"
|
||||
"severe"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -46,7 +45,7 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "10e2a3b31ba94b93ed2d6c9753a5aabf13190a0b336089e6521022069813cf17"
|
||||
|
||||
Generated
+2
-3
@@ -44,8 +44,7 @@
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"severe",
|
||||
"malware"
|
||||
"severe"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -80,7 +79,7 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
|
||||
Generated
+1
-2
@@ -25,8 +25,7 @@
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"severe",
|
||||
"malware"
|
||||
"severe"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
-2
@@ -22,8 +22,7 @@
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"severe",
|
||||
"malware"
|
||||
"severe"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ When the user refers to "perform[ing] pre-PR checks", do the following:
|
||||
|
||||
- Run `cargo clippy -p labrinth --all-targets` — there must be ZERO warnings, otherwise CI will fail
|
||||
- DO NOT run tests unless explicitly requested (they take a long time)
|
||||
- Prepare the sqlx cache: cd into `apps/labrinth` and run `cargo sqlx prepare -- --tests`
|
||||
- Prepare the sqlx cache: cd into `apps/labrinth` and run `cargo sqlx prepare`
|
||||
- NEVER run `cargo sqlx prepare --workspace`
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -788,20 +788,6 @@ impl RedisConnection {
|
||||
.await?;
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn incr(
|
||||
&mut self,
|
||||
namespace: &str,
|
||||
id: &str,
|
||||
) -> Result<Option<u64>, DatabaseError> {
|
||||
let key = format!("{}_{namespace}:{id}", self.meta_namespace);
|
||||
let value = cmd("INCR")
|
||||
.arg(key)
|
||||
.query_async(&mut self.connection)
|
||||
.await?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
||||
@@ -293,5 +293,4 @@ vars! {
|
||||
SERVER_PING_RETRIES: usize = 3usize;
|
||||
SERVER_PING_MIN_INTERVAL_SEC: u64 = 30u64 * 60;
|
||||
SERVER_PING_TIMEOUT_MS: u64 = 3u64 * 1000;
|
||||
SERVER_PING_MAX_FAIL_COUNT: u64 = 3u64;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ pub struct ServerPingQueue {
|
||||
}
|
||||
|
||||
pub const REDIS_NAMESPACE: &str = "minecraft_java_server_ping";
|
||||
pub const REDIS_FAILURE_NAMESPACE: &str = "minecraft_java_server_ping_failures";
|
||||
pub const CLICKHOUSE_TABLE: &str = "minecraft_java_server_pings";
|
||||
|
||||
impl ServerPingQueue {
|
||||
@@ -119,65 +118,27 @@ impl ServerPingQueue {
|
||||
.await
|
||||
.wrap_err("failed to write ping record")?;
|
||||
|
||||
let mut updated_project = false;
|
||||
if data.is_some() {
|
||||
// ping succeeded; immediately update its online status in redis
|
||||
|
||||
redis
|
||||
.set_serialized_to_json(
|
||||
REDIS_NAMESPACE,
|
||||
project_id,
|
||||
ping,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.wrap_err("failed to set redis key")?;
|
||||
updated_project = true;
|
||||
|
||||
redis
|
||||
.delete(REDIS_FAILURE_NAMESPACE, project_id)
|
||||
.await
|
||||
.wrap_err("failed to delete failure count")?;
|
||||
} else {
|
||||
// ping failed; if it's failed too many times, mark it as offline in redis
|
||||
// otherwise, just add to the fail counter
|
||||
|
||||
let failure_count = redis
|
||||
.incr(REDIS_FAILURE_NAMESPACE, &project_id.to_string())
|
||||
.await
|
||||
.wrap_err("failed to increment failure count")?;
|
||||
|
||||
if let Some(count) = failure_count
|
||||
&& count >= ENV.SERVER_PING_MAX_FAIL_COUNT
|
||||
{
|
||||
redis
|
||||
.set_serialized_to_json(
|
||||
REDIS_NAMESPACE,
|
||||
project_id,
|
||||
ping,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.wrap_err(
|
||||
"failed to set failed ping record in redis",
|
||||
)?;
|
||||
updated_project = true;
|
||||
}
|
||||
}
|
||||
|
||||
if updated_project {
|
||||
DBProject::clear_cache(
|
||||
(*project_id).into(),
|
||||
redis
|
||||
.set_serialized_to_json(
|
||||
REDIS_NAMESPACE,
|
||||
project_id,
|
||||
ping,
|
||||
None,
|
||||
None,
|
||||
&self.redis,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
warn!("failed to clear project cache: {err:#}")
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
.wrap_err("failed to set redis key")?;
|
||||
|
||||
DBProject::clear_cache(
|
||||
(*project_id).into(),
|
||||
None,
|
||||
None,
|
||||
&self.redis,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
warn!("failed to clear project cache: {err:#}")
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
ch.end()
|
||||
|
||||
@@ -126,7 +126,6 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
|
||||
...options.headers,
|
||||
},
|
||||
}
|
||||
this.attachArchonSentryCaptureHeader(mergedOptions)
|
||||
|
||||
const headers = mergedOptions.headers
|
||||
if (headers && 'Content-Type' in headers && headers['Content-Type'] === '') {
|
||||
@@ -310,21 +309,6 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient {
|
||||
return headers
|
||||
}
|
||||
|
||||
protected attachArchonSentryCaptureHeader(options: RequestOptions): void {
|
||||
if (options.api !== 'archon' || !options.headers || !this.shouldCaptureArchonRequests()) {
|
||||
return
|
||||
}
|
||||
|
||||
options.headers['modrinth-sentry-capture'] = '1'
|
||||
}
|
||||
|
||||
private shouldCaptureArchonRequests(): boolean {
|
||||
const archonSentryCapture = this.config.archonSentryCapture
|
||||
return typeof archonSentryCapture === 'function'
|
||||
? archonSentryCapture()
|
||||
: archonSentryCapture === true
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the actual HTTP request
|
||||
*
|
||||
|
||||
@@ -54,16 +54,4 @@ export class ArchonServersV1Module extends AbstractModule {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a world to onboarding
|
||||
* POST /v1/servers/:id/worlds/:wid/onboard
|
||||
*/
|
||||
public async resetToOnboarding(serverId: string, worldId: string): Promise<void> {
|
||||
await this.client.request(`/servers/${serverId}/worlds/${worldId}/onboard`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +593,6 @@ export namespace Labrinth {
|
||||
categories: string[]
|
||||
additional_categories: string[]
|
||||
loaders: string[]
|
||||
mrpack_loaders: string[]
|
||||
versions: string[]
|
||||
icon_url?: string
|
||||
link_urls: Record<string, Link>
|
||||
|
||||
@@ -46,7 +46,6 @@ export abstract class XHRUploadClient extends AbstractModrinthClient {
|
||||
...options.headers,
|
||||
},
|
||||
}
|
||||
this.attachArchonSentryCaptureHeader(mergedOptions)
|
||||
|
||||
const context = this.buildUploadContext(url, path, mergedOptions)
|
||||
|
||||
|
||||
@@ -55,14 +55,6 @@ export interface ClientConfig {
|
||||
*/
|
||||
headers?: Record<string, string>
|
||||
|
||||
/**
|
||||
* Whether to attach `modrinth-sentry-capture: 1` to Archon requests.
|
||||
* Can be a callback so apps can drive this from runtime feature flags.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
archonSentryCapture?: boolean | (() => boolean)
|
||||
|
||||
/**
|
||||
* Features to enable for this client
|
||||
* Features are applied in the order they appear in this array
|
||||
|
||||
@@ -10,28 +10,6 @@ export type VersionEntry = {
|
||||
}
|
||||
|
||||
const VERSIONS: VersionEntry[] = [
|
||||
{
|
||||
date: `2026-03-26T07:22:22+00:00`,
|
||||
product: 'web',
|
||||
body: `## Fixed
|
||||
- Fixed error on settings page.
|
||||
- Fixed the "500 Server Error" error on the "Authorized apps" settings page.`,
|
||||
},
|
||||
{
|
||||
date: `2026-03-26T07:22:22+00:00`,
|
||||
product: 'hosting',
|
||||
body: `## Fixed
|
||||
- Fixed wrong mod being disabled on hosted servers - Disabling a non-Modrinth mod could incorrectly disable a different mod instead
|
||||
- Fixed content list not refreshing after installing mods - Navigating back to the content tab after installing something from Browse would show stale data for up to 10 seconds
|
||||
- Fixed copying mod filenames inserting a newline - Copying a filename or version number from the content tab no longer includes a line break in the middle
|
||||
- Fixed NeoForge installs sending an empty loader version - Changing to NeoForge on a hosted server could send a blank loader version, resulting in a broken installation`,
|
||||
},
|
||||
{
|
||||
date: `2026-03-24T21:14:30-08:00`,
|
||||
product: 'hosting',
|
||||
body: `## Added
|
||||
- Added support for Java 25.`,
|
||||
},
|
||||
{
|
||||
date: `2026-03-17T19:30:00-08:00`,
|
||||
product: 'web',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex flex-wrap justify-between gap-2">
|
||||
<VersionFilterControl
|
||||
ref="versionFilters"
|
||||
:versions="normalizedVersions"
|
||||
:versions="versions"
|
||||
:game-versions="gameVersions"
|
||||
:base-id="`${baseId}-filter`"
|
||||
@update:query="updateQuery"
|
||||
@@ -110,18 +110,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pointer-events-none relative z-[1] flex flex-col justify-center overflow-hidden min-w-32"
|
||||
class="pointer-events-none relative z-[1] flex flex-col justify-center"
|
||||
:class="{
|
||||
'group-hover:underline': !!versionLink,
|
||||
}"
|
||||
title="`${version.version_number} - ${version.name}`"
|
||||
>
|
||||
<div class="font-bold text-contrast text-ellipsis overflow-hidden">
|
||||
{{ version.version_number }}
|
||||
</div>
|
||||
<div class="text-xs font-medium text-ellipsis overflow-hidden">
|
||||
{{ version.name }}
|
||||
</div>
|
||||
<div class="font-bold text-contrast">{{ version.version_number }}</div>
|
||||
<div class="text-xs font-medium">{{ version.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center gap-2 sm:contents">
|
||||
@@ -132,7 +127,7 @@
|
||||
v-for="gameVersion in formatVersionsForDisplay(
|
||||
version.game_versions,
|
||||
gameVersions,
|
||||
).slice(0, maxGameVersionTags)"
|
||||
)"
|
||||
:key="`version-tag-${gameVersion}`"
|
||||
v-tooltip="`Toggle filter for ${gameVersion}`"
|
||||
class="z-[1]"
|
||||
@@ -142,61 +137,21 @@
|
||||
>
|
||||
{{ gameVersion }}
|
||||
</TagItem>
|
||||
<Menu
|
||||
v-if="
|
||||
formatVersionsForDisplay(version.game_versions, gameVersions).length >
|
||||
maxGameVersionTags
|
||||
"
|
||||
:delay="{ hide: 50, show: 0 }"
|
||||
no-auto-focus
|
||||
class="z-[1] cursor-default"
|
||||
>
|
||||
<TagItem tabindex="0">
|
||||
+{{
|
||||
formatVersionsForDisplay(version.game_versions, gameVersions).length -
|
||||
maxGameVersionTags
|
||||
}}
|
||||
</TagItem>
|
||||
<template #popper>
|
||||
<div class="flex gap-1 flex-wrap max-w-[20rem]">
|
||||
<TagItem
|
||||
v-for="gameVersion in formatVersionsForDisplay(
|
||||
version.game_versions,
|
||||
gameVersions,
|
||||
).slice(maxGameVersionTags)"
|
||||
:key="`overflow-version-tag-${gameVersion}`"
|
||||
:action="
|
||||
() =>
|
||||
versionFilters?.toggleFilters('gameVersion', version.game_versions)
|
||||
"
|
||||
>
|
||||
{{ gameVersion }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</template>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<template v-if="version.noModLoader">
|
||||
<TagItem class="z-[1] border !border-solid border-surface-5">
|
||||
No mod loader
|
||||
</TagItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TagItem
|
||||
v-for="platform in version.loaders"
|
||||
:key="`platform-tag-${platform}`"
|
||||
v-tooltip="`Toggle filter for ${platform}`"
|
||||
class="z-[1]"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
:action="() => versionFilters?.toggleFilter('platform', platform)"
|
||||
>
|
||||
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
|
||||
<FormattedTag :tag="platform" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</template>
|
||||
<TagItem
|
||||
v-for="platform in version.loaders"
|
||||
:key="`platform-tag-${platform}`"
|
||||
v-tooltip="`Toggle filter for ${platform}`"
|
||||
class="z-[1]"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
:action="() => versionFilters?.toggleFilter('platform', platform)"
|
||||
>
|
||||
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
|
||||
<FormattedTag :tag="platform" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasMultipleEnvironments" class="flex items-center">
|
||||
@@ -207,7 +162,7 @@
|
||||
class="z-[1] text-center"
|
||||
>
|
||||
<component :is="tag.icon" />
|
||||
{{ formatMessage(tag.label).replace('and', '&') }}
|
||||
{{ formatMessage(tag.label) }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,7 +233,6 @@ import {
|
||||
type GameVersionTag,
|
||||
type Version,
|
||||
} from '@modrinth/utils'
|
||||
import { Menu } from 'floating-vue'
|
||||
import { computed, type Ref, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@@ -297,11 +251,6 @@ const formatDateTime = useFormatDateTime({
|
||||
type VersionWithDisplayUrlEnding = Version & {
|
||||
displayUrlEnding: string
|
||||
environment?: Labrinth.Projects.v3.Environment
|
||||
mrpack_loaders?: string[]
|
||||
}
|
||||
|
||||
type DisplayVersion = VersionWithDisplayUrlEnding & {
|
||||
noModLoader: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -329,39 +278,6 @@ const props = withDefaults(
|
||||
},
|
||||
)
|
||||
|
||||
function getModpackLoaders(version: VersionWithDisplayUrlEnding): string[] {
|
||||
if (props.project.project_type !== 'modpack') {
|
||||
return version.loaders
|
||||
}
|
||||
|
||||
if (version.mrpack_loaders?.length) {
|
||||
return version.mrpack_loaders
|
||||
}
|
||||
|
||||
return version.loaders.filter((loader) => loader !== 'mrpack')
|
||||
}
|
||||
|
||||
function hasNoModLoader(loaders: string[]): boolean {
|
||||
return (
|
||||
props.project.project_type === 'modpack' && loaders.length === 1 && loaders[0] === 'minecraft'
|
||||
)
|
||||
}
|
||||
|
||||
const normalizedVersions = computed<DisplayVersion[]>(() =>
|
||||
props.versions.map((version) => {
|
||||
const loaders = getModpackLoaders(version)
|
||||
const noModLoader = hasNoModLoader(loaders)
|
||||
|
||||
return {
|
||||
...version,
|
||||
loaders: noModLoader ? [] : loaders,
|
||||
noModLoader,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const maxGameVersionTags = 6
|
||||
|
||||
const currentPage: Ref<number> = ref(1)
|
||||
const pageSize: Ref<number> = ref(20)
|
||||
const versionFilters: Ref<InstanceType<typeof VersionFilterControl> | null> = ref(null)
|
||||
@@ -380,7 +296,7 @@ const hasMultipleEnvironments = computed(() => {
|
||||
})
|
||||
|
||||
const filteredVersions = computed(() => {
|
||||
return normalizedVersions.value.filter(
|
||||
return props.versions.filter(
|
||||
(version) =>
|
||||
hasAnySelected(version.game_versions, selectedGameVersions.value) &&
|
||||
hasAnySelected(version.loaders, selectedPlatforms.value) &&
|
||||
|
||||
@@ -15,22 +15,15 @@
|
||||
<section v-if="project.project_type !== 'resourcepack'" class="flex flex-col gap-2">
|
||||
<h3 class="text-primary text-base m-0">{{ formatMessage(messages.platforms) }}</h3>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<template v-if="noModpackLoader">
|
||||
<TagItem class="border !border-solid border-surface-5 hover:no-underline">
|
||||
No mod loader
|
||||
</TagItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<TagItem
|
||||
v-for="platform in project.loaders"
|
||||
:key="`platform-tag-${platform}`"
|
||||
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
>
|
||||
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
|
||||
<FormattedTag :tag="platform" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</template>
|
||||
<TagItem
|
||||
v-for="platform in project.loaders"
|
||||
:key="`platform-tag-${platform}`"
|
||||
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
>
|
||||
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
|
||||
<FormattedTag :tag="platform" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="showEnvironments" class="flex flex-col gap-2">
|
||||
@@ -92,7 +85,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
ClientIcon,
|
||||
getLoaderIcon,
|
||||
@@ -101,7 +93,7 @@ import {
|
||||
UserIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { FormattedTag, TagItem } from '@modrinth/ui'
|
||||
import type { EnvironmentV3, GameVersionTag, PlatformTag } from '@modrinth/utils'
|
||||
import type { EnvironmentV3, GameVersionTag, PlatformTag, ProjectV3Partial } from '@modrinth/utils'
|
||||
import { getVersionsToDisplay } from '@modrinth/utils'
|
||||
import { type Component, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -136,24 +128,17 @@ const props = defineProps<{
|
||||
gameVersions: GameVersionTag[]
|
||||
loaders: PlatformTag[]
|
||||
}
|
||||
projectV3?: Labrinth.Projects.v3.Project
|
||||
v3Metadata?: ProjectV3Partial
|
||||
}>()
|
||||
|
||||
const noModpackLoader = computed(
|
||||
() =>
|
||||
props.projectV3?.project_types.includes('modpack') &&
|
||||
props.projectV3?.mrpack_loaders.length === 1 &&
|
||||
props.projectV3?.mrpack_loaders[0] === 'minecraft',
|
||||
)
|
||||
|
||||
const showEnvironments = computed(
|
||||
() =>
|
||||
TYPES_WITH_ENVS.some((x) => props.projectV3?.project_types.includes(x)) &&
|
||||
TYPES_WITH_ENVS.some((x) => props.v3Metadata?.project_types.includes(x)) &&
|
||||
primaryEnvironment.value,
|
||||
)
|
||||
|
||||
const primaryEnvironment = computed<EnvironmentV3 | undefined>(() =>
|
||||
props.projectV3?.environment?.find((x) => x !== 'unknown'),
|
||||
props.v3Metadata?.environment?.find((x) => x !== 'unknown'),
|
||||
)
|
||||
|
||||
type EnvironmentTag = {
|
||||
|
||||
@@ -212,8 +212,8 @@ const deleteHovered = ref(false)
|
||||
>
|
||||
<span ref="versionNumberRef" class="truncate">{{
|
||||
version.version_number.slice(0, Math.ceil(version.version_number.length / 2))
|
||||
}}</span
|
||||
><span class="shrink-0">{{
|
||||
}}</span>
|
||||
<span class="shrink-0">{{
|
||||
version.version_number.slice(Math.ceil(version.version_number.length / 2))
|
||||
}}</span>
|
||||
</AutoLink>
|
||||
@@ -223,8 +223,8 @@ const deleteHovered = ref(false)
|
||||
>
|
||||
<span ref="fileNameRef" class="truncate">{{
|
||||
version.file_name.slice(0, Math.ceil(version.file_name.length / 2))
|
||||
}}</span
|
||||
><span class="shrink-0">{{
|
||||
}}</span>
|
||||
<span class="shrink-0">{{
|
||||
version.file_name.slice(Math.ceil(version.file_name.length / 2))
|
||||
}}</span>
|
||||
</span>
|
||||
|
||||
@@ -115,7 +115,6 @@ const contentQuery = useQuery({
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
|
||||
enabled: computed(() => worldId.value !== null),
|
||||
staleTime: 0,
|
||||
})
|
||||
|
||||
const modpackProjectId = computed(() => contentQuery.data.value?.modpack?.spec.project_id ?? null)
|
||||
@@ -484,7 +483,7 @@ function addonToContentItem(addon: Archon.Content.v1.Addon): ContentItem {
|
||||
link: `/${addon.owner.type}/${addon.owner.id}`,
|
||||
}
|
||||
: undefined,
|
||||
id: addon.id ?? addon.filename,
|
||||
id: addon.id,
|
||||
enabled: !addon.disabled,
|
||||
file_name: addon.filename,
|
||||
project_type: addon.kind,
|
||||
|
||||
Reference in New Issue
Block a user