Skip to content

Commit 6ce52be

Browse files
authored
fix: normalize user and org names to lowercase in search and routing (#1519)
1 parent 48bc139 commit 6ce52be

File tree

6 files changed

+58
-12
lines changed

6 files changed

+58
-12
lines changed

app/components/SearchSuggestionCard.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ defineProps<{
1616
<NuxtLink
1717
:to="
1818
type === 'user'
19-
? { name: '~username', params: { username: name } }
20-
: { name: 'org', params: { org: name } }
19+
? { name: '~username', params: { username: name.toLowerCase() } }
20+
: { name: 'org', params: { org: name.toLowerCase() } }
2121
"
2222
:data-suggestion-index="index"
2323
class="flex items-center gap-4 focus-visible:outline-none after:content-[''] after:absolute after:inset-0"

app/composables/npm/useSearch.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,10 @@ export function useSearch(
122122

123123
const newSuggestions: SearchSuggestion[] = []
124124
if (isOrg) {
125-
newSuggestions.push({ type: 'org', name, exists: true })
125+
newSuggestions.push({ type: 'org', name: lowerName, exists: true })
126126
}
127127
if (isUser && !isOrg) {
128-
newSuggestions.push({ type: 'user', name, exists: true })
128+
newSuggestions.push({ type: 'user', name: lowerName, exists: true })
129129
}
130130
suggestions.value = newSuggestions
131131
} else {
@@ -378,7 +378,7 @@ export function useSearch(
378378

379379
if (wantOrg && existenceCache.value[`org:${lowerName}`] === undefined) {
380380
promises.push(
381-
checkOrgNpm(name)
381+
checkOrgNpm(lowerName)
382382
.then(exists => {
383383
existenceCache.value = { ...existenceCache.value, [`org:${lowerName}`]: exists }
384384
})
@@ -390,7 +390,7 @@ export function useSearch(
390390

391391
if (wantUser && existenceCache.value[`user:${lowerName}`] === undefined) {
392392
promises.push(
393-
checkUserNpm(name)
393+
checkUserNpm(lowerName)
394394
.then(exists => {
395395
existenceCache.value = { ...existenceCache.value, [`user:${lowerName}`]: exists }
396396
})
@@ -411,10 +411,10 @@ export function useSearch(
411411
const isUser = wantUser && existenceCache.value[`user:${lowerName}`]
412412

413413
if (isOrg) {
414-
result.push({ type: 'org', name, exists: true })
414+
result.push({ type: 'org', name: lowerName, exists: true })
415415
}
416416
if (isUser && !isOrg) {
417-
result.push({ type: 'user', name, exists: true })
417+
result.push({ type: 'user', name: lowerName, exists: true })
418418
}
419419
} finally {
420420
if (requestId === suggestionRequestId.value) {

app/pages/org/[org].vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ definePageMeta({
1010
const route = useRoute('org')
1111
const router = useRouter()
1212
13-
const orgName = computed(() => route.params.org)
13+
const orgName = computed(() => route.params.org.toLowerCase())
1414
1515
const { isConnected } = useConnector()
1616

app/pages/~[username]/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { normalizeSearchParam } from '#shared/utils/url'
55
const route = useRoute('~username')
66
const router = useRouter()
77
8-
const username = computed(() => route.params.username)
8+
const username = computed(() => route.params.username.toLowerCase())
99
1010
// Debounced URL update for page and filter/sort
1111
const updateUrl = debounce((updates: { page?: number; filter?: string; sort?: string }) => {

server/api/registry/org/[org]/packages.get.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function validateOrgName(name: string): void {
1616

1717
export default defineCachedEventHandler(
1818
async event => {
19-
const org = getRouterParam(event, 'org')
19+
const org = getRouterParam(event, 'org')?.toLowerCase()
2020

2121
if (!org) {
2222
throw createError({
@@ -54,7 +54,7 @@ export default defineCachedEventHandler(
5454
maxAge: CACHE_MAX_AGE_ONE_HOUR,
5555
swr: true,
5656
getKey: event => {
57-
const org = getRouterParam(event, 'org') ?? ''
57+
const org = getRouterParam(event, 'org')?.toLowerCase() ?? ''
5858
return `org-packages:v1:${org}`
5959
},
6060
},
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { parseSuggestionIntent } from '../../app/composables/npm/search-utils'
3+
4+
describe('Search case normalization', () => {
5+
describe('parseSuggestionIntent', () => {
6+
it('should preserve case in name extraction for org queries', () => {
7+
const result = parseSuggestionIntent('@AnGuLAR')
8+
expect(result.intent).toBe('org')
9+
expect(result.name).toBe('AnGuLAR')
10+
})
11+
12+
it('should preserve case in name extraction for user queries', () => {
13+
const result = parseSuggestionIntent('~daNIEL')
14+
expect(result.intent).toBe('user')
15+
expect(result.name).toBe('daNIEL')
16+
})
17+
18+
it('should preserve case in name extraction for both queries', () => {
19+
const result = parseSuggestionIntent('AnGuLAR')
20+
expect(result.intent).toBe('both')
21+
expect(result.name).toBe('AnGuLAR')
22+
})
23+
24+
it('should handle lowercase org names', () => {
25+
const result = parseSuggestionIntent('@angular')
26+
expect(result.intent).toBe('org')
27+
expect(result.name).toBe('angular')
28+
})
29+
30+
it('should handle lowercase user names', () => {
31+
const result = parseSuggestionIntent('~daniel')
32+
expect(result.intent).toBe('user')
33+
expect(result.name).toBe('daniel')
34+
})
35+
})
36+
37+
describe('Case normalization expectations', () => {
38+
it('should expect suggestions to normalize names to lowercase', () => {
39+
const mixedCaseOrg = parseSuggestionIntent('@AnGuLAR')
40+
expect(mixedCaseOrg.name).toBe('AnGuLAR')
41+
42+
const expectedNormalized = mixedCaseOrg.name.toLowerCase()
43+
expect(expectedNormalized).toBe('angular')
44+
})
45+
})
46+
})

0 commit comments

Comments
 (0)