forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import * as v from 'valibot'
2import { CACHE_MAX_AGE_ONE_DAY, BLUESKY_API } from '#shared/utils/constants'
3import { AuthorSchema } from '#shared/schemas/blog'
4import { Client } from '@atproto/lex'
5import type { Author, ResolvedAuthor } from '#shared/schemas/blog'
6import * as app from '#shared/types/lexicons/app'
7
8export default defineCachedEventHandler(
9 async event => {
10 const query = getQuery(event)
11 const authorsParam = query.authors
12
13 if (!authorsParam || typeof authorsParam !== 'string') {
14 throw createError({
15 statusCode: 400,
16 statusMessage: 'authors query parameter is required (JSON array)',
17 })
18 }
19
20 let authors: Author[]
21 try {
22 const parsed = JSON.parse(authorsParam)
23 authors = v.parse(v.array(AuthorSchema), parsed)
24 } catch (error) {
25 if (error instanceof v.ValiError) {
26 throw createError({
27 statusCode: 400,
28 statusMessage: `Invalid authors format: ${error.message}`,
29 })
30 }
31 throw createError({
32 statusCode: 400,
33 statusMessage: 'authors must be valid JSON',
34 })
35 }
36
37 if (!Array.isArray(authors) || authors.length === 0) {
38 return { authors: [] }
39 }
40
41 const handles = authors.map(a => a.blueskyHandle).filter(v => v != null)
42
43 if (handles.length === 0) {
44 return {
45 authors: authors.map(author => Object.assign(author, { avatar: null, profileUrl: null })),
46 }
47 }
48
49 const client = new Client({ service: BLUESKY_API })
50 const response = await client
51 .call(app.bsky.actor.getProfiles, { actors: handles })
52 .catch(() => ({ profiles: [] }))
53
54 const avatarMap = new Map<string, string>()
55 for (const profile of response.profiles) {
56 if (profile.avatar) {
57 avatarMap.set(profile.handle, profile.avatar)
58 }
59 }
60
61 const resolvedAuthors: ResolvedAuthor[] = authors.map(author =>
62 Object.assign(author, {
63 avatar: author.blueskyHandle ? avatarMap.get(author.blueskyHandle) || null : null,
64 profileUrl: author.blueskyHandle
65 ? `https://bsky.app/profile/${author.blueskyHandle}`
66 : null,
67 }),
68 )
69
70 return { authors: resolvedAuthors }
71 },
72 {
73 name: 'author-profiles',
74 maxAge: CACHE_MAX_AGE_ONE_DAY,
75 getKey: event => {
76 const { authors } = getQuery(event)
77 return `author-profiles:${authors ?? 'npmx.dev'}`
78 },
79 },
80)