import { mapDefined } from '@atproto/common' import { DidString } from '@atproto/lex' import { Server } from '@atproto/xrpc-server' import { AppContext } from '../../../../context.js' import { HydrateCtxWithViewer, HydrationState, Hydrator, } from '../../../../hydration/hydrator.js' import { app } from '../../../../lexicons/index.js' import { HydrationFnInput, SkeletonFnInput, createPipeline, } from '../../../../pipeline.js' import { RolodexClient } from '../../../../rolodex.js' import { Views } from '../../../../views/index.js' import { assertRolodexOrThrowUnimplemented, callRolodexClient } from './util.js' export default function (server: Server, ctx: AppContext) { const getMatches = createPipeline(skeleton, hydration, noBlocks, presentation) server.add(app.bsky.contact.getMatches, { auth: ctx.authVerifier.standard, handler: async ({ params, auth, req }) => { assertRolodexOrThrowUnimplemented(ctx) const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer, }) const result = await getMatches({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, } }, }) } const skeleton = async ( input: SkeletonFnInput, ): Promise => { const { params, ctx } = input const actor = params.hydrateCtx.viewer const { cursor, subjects } = await callRolodexClient( ctx.rolodexClient.getMatches({ actor: params.hydrateCtx.viewer, limit: params.limit, cursor: params.cursor, }), ) return { actor, subjects: subjects as DidString[], cursor: cursor || undefined, } } const hydration = async ( input: HydrationFnInput, ) => { const { ctx, params, skeleton } = input const { subjects } = skeleton return ctx.hydrator.hydrateProfiles(subjects, params.hydrateCtx) } const noBlocks = (inputs: { ctx: Context skeleton: SkeletonState hydration: HydrationState }) => { const { ctx, skeleton, hydration } = inputs skeleton.subjects = skeleton.subjects.filter((subject) => { return !ctx.views.viewerBlockExists(subject, hydration) }) return skeleton } const presentation = (input: { ctx: Context params: Params skeleton: SkeletonState hydration: HydrationState }) => { const { ctx, skeleton, hydration } = input const matches = mapDefined(skeleton.subjects, (did) => ctx.views.profile(did, hydration), ) return { matches } } type Context = { hydrator: Hydrator rolodexClient: RolodexClient views: Views } type Params = app.bsky.contact.getMatches.$Params & { hydrateCtx: HydrateCtxWithViewer } type SkeletonState = { actor: string subjects: DidString[] cursor?: string }