Files
modrinth/apps/frontend/src/middleware/project.global.ts
T
Blodhgarm 876308516e feat: Add /version/{version} redirect (#7357)
feat: Add `/version/{version}` redirect to `{project_type}/{slug}/version/{version}` using global middleware
2026-08-31 07:19:03 +00:00

106 lines
3.2 KiB
TypeScript

import type { RouteLocationNormalized } from 'vue-router'
import { useGeneratedState } from '~/composables/generated'
import { projectQueryOptions, warmProjectCheckCaches } from '~/composables/queries/project'
import { useAppQueryClient } from '~/composables/query-client'
import { createModrinthClient } from '~/helpers/api.ts'
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
import { useServerModrinthClient } from '~/server/utils/api-client'
// All valid project type URL segments
const PROJECT_TYPES = [
'project',
'mod',
'plugin',
'datapack',
'shader',
'resourcepack',
'modpack',
'server',
'minecraft_java_server',
]
export default defineNuxtRouteMiddleware(async (to) => {
const routeProjectParam = to.params.project
const routeParam = Array.isArray(routeProjectParam) ? routeProjectParam[0] : routeProjectParam
const routeType = Array.isArray(to.params.type) ? to.params.type[0] : to.params.type
// Only handle project routes
if (!routeParam || !routeType || !PROJECT_TYPES.includes(routeType)) {
return
}
const queryClient = useAppQueryClient()
const client = await getProjectMiddlewareClient(to)
const tags = useGeneratedState()
if (import.meta.client) startLoading()
try {
// Resolve slug/ID to the canonical project ID, then fetch by ID only
const { id: projectId } = await queryClient.fetchQuery(
projectQueryOptions.check(routeParam, client),
)
warmProjectCheckCaches(queryClient, { id: projectId })
const [project, projectV3] = await Promise.all([
queryClient.fetchQuery(projectQueryOptions.v2(projectId, client)),
queryClient.fetchQuery(projectQueryOptions.v3(projectId, client)),
])
// Let page handle 404
if (!project) return
warmProjectCheckCaches(queryClient, project)
const projectType = projectV3.minecraft_server != null ? 'server' : project.project_type
// Determine the correct URL type
const correctType = getProjectTypeForUrlShorthand(projectType, project.loaders, tags.value)
// Preserve the rest of the path (subpages like /versions, /settings, etc.)
const pathParts = to.path.split('/')
pathParts.splice(0, 3) // Remove '', type, and id
const remainder = pathParts.filter((x) => x).join('/')
// Build the canonical path
const canonicalPath = `/${correctType}/${encodeURIComponent(project.slug)}${remainder ? `/${remainder}` : ''}`
// Only redirect if the path actually changed
if (to.path !== canonicalPath) {
return navigateTo(
{
path: canonicalPath,
query: to.query,
hash: to.hash,
},
{
redirectCode: 301,
replace: true,
},
)
}
} catch {
// Let the page handle 404s and other errors
} finally {
if (import.meta.client) stopLoading()
}
})
export async function getProjectMiddlewareClient(route: RouteLocationNormalized) {
if (import.meta.server) {
const authToken = useCookie('auth-token')
return useServerModrinthClient({ authToken: authToken.value || undefined })
}
const auth = await useAuth(null, route)
const config = useRuntimeConfig()
return createModrinthClient(auth, {
apiBaseUrl: config.public.apiBaseUrl.replace('/v2/', '/'),
archonBaseUrl: config.public.pyroBaseUrl.replace('/v2/', '/'),
sharedInstancesBaseUrl: config.public.sharedInstancesBaseUrl,
rateLimitKey: config.rateLimitKey,
})
}