Skip to content

Commit 20a9889

Browse files
committed
fix: follow contributing guidelines
1 parent b1f1f7f commit 20a9889

4 files changed

Lines changed: 68 additions & 20 deletions

File tree

server/api/gravatar.get.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { H3Event } from 'h3'
2-
import { CACHE_MAX_AGE_ONE_DAY } from '#shared/utils/constants'
2+
import { createError, getQuery } from 'h3'
3+
import * as v from 'valibot'
4+
import { GravatarQuerySchema } from '#shared/schemas/user'
35
import { getGravatarFromUsername } from '#server/utils/gravatar'
4-
import { assertValidUsername } from '#shared/utils/npm'
6+
import { handleApiError } from '#server/utils/error-handler'
57

68
function getQueryParam(event: H3Event, key: string): string {
79
const query = getQuery(event)
@@ -11,30 +13,31 @@ function getQueryParam(event: H3Event, key: string): string {
1113

1214
export default defineCachedEventHandler(
1315
async event => {
14-
const username = getQueryParam(event, 'username').trim()
16+
const rawUsername = getQueryParam(event, 'username')
17+
const rawSize = getQueryParam(event, 'size')
1518

16-
if (!username) {
17-
throw createError({
18-
statusCode: 400,
19-
message: 'Username is required',
19+
try {
20+
const { username, size } = v.parse(GravatarQuerySchema, {
21+
username: rawUsername,
22+
size: rawSize ? rawSize : undefined,
2023
})
21-
}
22-
23-
assertValidUsername(username)
2424

25-
const sizeParam = Number.parseInt(getQueryParam(event, 'size'), 10)
26-
const size = Number.isNaN(sizeParam) ? 80 : Math.max(16, Math.min(512, sizeParam))
25+
const url = await getGravatarFromUsername(username, size ?? 80)
2726

28-
const url = await getGravatarFromUsername(username, size)
27+
if (!url) {
28+
throw createError({
29+
statusCode: 404,
30+
message: ERROR_GRAVATAR_EMAIL_UNAVAILABLE,
31+
})
32+
}
2933

30-
if (!url) {
31-
throw createError({
32-
statusCode: 400,
33-
message: "User's email not accessible",
34+
return { url }
35+
} catch (error: unknown) {
36+
handleApiError(error, {
37+
statusCode: 502,
38+
message: ERROR_GRAVATAR_FETCH_FAILED,
3439
})
3540
}
36-
37-
return { url }
3841
},
3942
{
4043
maxAge: CACHE_MAX_AGE_ONE_DAY,

server/utils/npm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,6 @@ export const fetchUserEmail = defineCachedFunction(
122122
maxAge: CACHE_MAX_AGE_ONE_DAY,
123123
swr: true,
124124
name: 'npm-user-email',
125-
getKey: (username: string) => `npm-user-email:${username.toLowerCase()}`,
125+
getKey: (username: string) => `npm-user-email:${username.trim().toLowerCase()}`,
126126
},
127127
)

shared/schemas/user.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as v from 'valibot'
2+
3+
const NPM_USERNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i
4+
const NPM_USERNAME_MAX_LENGTH = 50
5+
6+
/**
7+
* Schema for npm usernames.
8+
*/
9+
export const NpmUsernameSchema = v.pipe(
10+
v.string(),
11+
v.trim(),
12+
v.nonEmpty('Username is required'),
13+
v.maxLength(NPM_USERNAME_MAX_LENGTH, 'Username is too long'),
14+
v.regex(NPM_USERNAME_RE, 'Invalid username format'),
15+
)
16+
17+
/**
18+
* Schema for Gravatar query inputs.
19+
*/
20+
export const GravatarQuerySchema = v.object({
21+
username: NpmUsernameSchema,
22+
size: v.optional(
23+
v.pipe(
24+
v.union([v.number(), v.string()]),
25+
v.transform(value => {
26+
if (typeof value === 'string') {
27+
const trimmed = value.trim()
28+
return /^\d+$/.test(trimmed) ? Number(trimmed) : Number.NaN
29+
}
30+
return value
31+
}),
32+
v.check(value => !Number.isNaN(value), 'Invalid size'),
33+
v.number(),
34+
v.minValue(16),
35+
v.maxValue(512),
36+
),
37+
),
38+
})
39+
40+
/** @public */
41+
export type NpmUsername = v.InferOutput<typeof NpmUsernameSchema>
42+
/** @public */
43+
export type GravatarQuery = v.InferOutput<typeof GravatarQuerySchema>

shared/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry
1919
export const UNSET_NUXT_SESSION_PASSWORD = 'NUXT_SESSION_PASSWORD not set'
2020
/** @public */
2121
export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'
22+
export const ERROR_GRAVATAR_FETCH_FAILED = 'Failed to fetch Gravatar profile.'
23+
export const ERROR_GRAVATAR_EMAIL_UNAVAILABLE = "User's email not accessible."
2224

2325
// microcosm services
2426
export const CONSTELLATION_HOST = 'constellation.microcosm.blue'

0 commit comments

Comments
 (0)