This commit is contained in:
2026-05-16 11:58:24 +01:00
parent bf1abe39c8
commit 4feac9a1fc
3 changed files with 59 additions and 5 deletions
+17
View File
@@ -1533,6 +1533,23 @@ function allowedApiTarget(req) {
return url
}
if (pathname === '/api/tss/teams/search') {
const keys = [...params.keys()]
const query = params.get('q') || params.get('name') || ''
const limit = Number(params.get('limit') || 10)
if (
keys.some((key) => !['q', 'name', 'limit'].includes(key)) ||
query.length < 2 ||
query.length > MAX_TEAM_NAME_LENGTH ||
!Number.isInteger(limit) ||
limit < 1 ||
limit > 20
) {
return null
}
return url
}
const teamMatch = pathname.match(/^\/api\/tss\/teams\/([^/]+)(?:\/(history|games))?$/)
if (!teamMatch || [...params.keys()].length) return null
+27 -4
View File
@@ -19,6 +19,7 @@ const apiEndpoints = {
teams: '/api/tss/leaderboard/teams?limit=100',
teamsHealth: '/api/tss/leaderboard/teams?limit=1',
resolve: (name) => `/api/tss/teams/resolve?name=${encodeURIComponent(name)}`,
searchTeams: (name) => `/api/tss/teams/search?q=${encodeURIComponent(name)}&limit=10`,
detail: (name) => `/api/tss/teams/${encodeURIComponent(name)}`,
history: (name) => `/api/tss/teams/${encodeURIComponent(name)}/history`,
games: (name) => `/api/tss/teams/${encodeURIComponent(name)}/games`,
@@ -547,6 +548,7 @@ function AppContent() {
const [showFloatingNav, setShowFloatingNav] = useState(() => window.scrollY > 40)
const [teamQuery, setTeamQuery] = useState('')
const [searchHint, setSearchHint] = useState({ status: 'idle', name: '' })
const [teamSearchResults, setTeamSearchResults] = useState([])
const [profile, setProfile] = useState({
teamName: '',
detail: { status: 'idle', data: null, error: null },
@@ -733,22 +735,42 @@ function AppContent() {
const query = teamQuery.trim()
if (query.length < 2) {
setSearchHint({ status: 'idle', name: '' })
setTeamSearchResults([])
return
}
const controller = new AbortController()
const timer = window.setTimeout(() => {
setSearchHint({ status: 'loading', name: '' })
fetchJson(apiEndpoints.resolve(query), controller.signal)
fetchJson(apiEndpoints.searchTeams(query), controller.signal)
.then((data) => {
const name = data.tag_name || data.short_name || data.long_name || query
setSearchHint({ status: 'ready', name })
const results = (data.teams || data.results || [])
.map((team) => ({
name: bestTeamName(team),
detail: team.long_name || team.short_name || '',
aliases: [team.tag_name, team.short_name, team.long_name].filter(Boolean),
}))
.filter((team) => team.name)
.slice(0, 10)
setTeamSearchResults(results)
setSearchHint(results.length ? { status: 'ready', name: results[0].name } : { status: 'error', name: '' })
})
.catch(() => {
if (!controller.signal.aborted) {
fetchJson(apiEndpoints.resolve(query), controller.signal)
.then((data) => {
const name = data.tag_name || data.short_name || data.long_name || ''
setTeamSearchResults(name ? [{ name, detail: data.long_name || data.short_name || '', aliases: [name] }] : [])
setSearchHint(name ? { status: 'ready', name } : { status: 'error', name: '' })
})
.catch(() => {
if (!controller.signal.aborted) {
setTeamSearchResults([])
setSearchHint({ status: 'error', name: '' })
}
})
}
})
}, 350)
return () => {
@@ -1060,7 +1082,7 @@ function AppContent() {
}, [route.page, route.teamName])
const topTeamName = bestTeamName(teams[0])
const teamSuggestions = useMemo(() => {
const localTeamSuggestions = useMemo(() => {
const query = teamQuery.trim().toLowerCase()
const seen = new Set()
@@ -1078,6 +1100,7 @@ function AppContent() {
})
.slice(0, 10)
}, [teamQuery, teams])
const teamSuggestions = teamSearchResults.length ? teamSearchResults : localTeamSuggestions
const searchPlaceholder =
searchHint.status === 'ready'
? `Found ${searchHint.name}`
+14
View File
@@ -92,6 +92,20 @@ function isAllowedApiUrl(req) {
return keys.every((key) => key === 'name') && name.length >= 2 && name.length <= MAX_TEAM_NAME_LENGTH
}
if (url.pathname === '/api/tss/teams/search') {
const keys = [...params.keys()]
const query = params.get('q') || params.get('name') || ''
const limit = Number(params.get('limit') || 10)
return (
keys.every((key) => ['q', 'name', 'limit'].includes(key)) &&
query.length >= 2 &&
query.length <= MAX_TEAM_NAME_LENGTH &&
Number.isInteger(limit) &&
limit >= 1 &&
limit <= 20
)
}
if ([...params.keys()].length) return false
try {