[READ-ONLY] a fast, modern browser for the npm registry
at main 80 lines 2.4 kB view raw
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)