diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index eb52c3a7bf5..7d11fbaa4a5 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -1,6 +1,7 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import { mapDefined } from '@atproto/common' +import AtpAgent from '@atproto/api' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActors' import { HydrationFnInput, @@ -36,11 +37,24 @@ export default function (server: Server, ctx: AppContext) { const skeleton = async (inputs: SkeletonFnInput) => { const { ctx, params } = inputs - const term = params.q ?? params.term + const term = params.q ?? params.term ?? '' // @TODO // add hits total + if (ctx.searchAgent) { + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + q: term, + cursor: params.cursor, + limit: params.limit, + }) + return { + dids: res.actors.map(({ did }) => did), + cursor: parseString(res.cursor), + } + } + const res = await ctx.dataplane.searchActors({ term, limit: params.limit, @@ -84,6 +98,7 @@ type Context = { dataplane: DataPlaneClient hydrator: Hydrator views: Views + searchAgent?: AtpAgent } type Params = QueryParams & { viewer: string | null } diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 7e8e572d86b..540d3eae9a9 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -1,5 +1,6 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' +import AtpAgent from '@atproto/api' import { mapDefined } from '@atproto/common' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActorsTypeahead' import { @@ -36,12 +37,25 @@ export default function (server: Server, ctx: AppContext) { const skeleton = async (inputs: SkeletonFnInput) => { const { ctx, params } = inputs - const term = params.q ?? params.term + const term = params.q ?? params.term ?? '' // @TODO // add typeahead option // add hits total + if (ctx.searchAgent) { + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + typeahead: true, + q: term, + limit: params.limit, + }) + return { + dids: res.actors.map(({ did }) => did), + cursor: parseString(res.cursor), + } + } + const res = await ctx.dataplane.searchActors({ term, limit: params.limit, @@ -83,6 +97,7 @@ type Context = { dataplane: DataPlaneClient hydrator: Hydrator views: Views + searchAgent?: AtpAgent } type Params = QueryParams & { viewer: string | null } diff --git a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts index 61530452b8a..ca4d6359e27 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts @@ -13,7 +13,7 @@ import { } from '../../../../pipeline' import { Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { DataPlaneClient } from '../../../../data-plane' +import { DataPlaneClient, isDataplaneError, Code } from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { const getPostThread = createPipeline( @@ -49,14 +49,25 @@ export default function (server: Server, ctx: AppContext) { const skeleton = async (inputs: SkeletonFnInput) => { const { ctx, params } = inputs - const res = await ctx.dataplane.getThread({ - postUri: params.uri, - above: params.parentHeight, - below: params.depth, - }) - return { - anchor: params.uri, - uris: res.uris, + try { + const res = await ctx.dataplane.getThread({ + postUri: params.uri, + above: params.parentHeight, + below: params.depth, + }) + return { + anchor: params.uri, + uris: res.uris, + } + } catch (err) { + if (isDataplaneError(err, Code.NotFound)) { + return { + anchor: params.uri, + uris: [], + } + } else { + throw err + } } } diff --git a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts index 879f296d007..fb05f3a6068 100644 --- a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts @@ -1,5 +1,6 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' +import AtpAgent from '@atproto/api' import { mapDefined } from '@atproto/common' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts' import { @@ -37,6 +38,20 @@ export default function (server: Server, ctx: AppContext) { const skeleton = async (inputs: SkeletonFnInput) => { const { ctx, params } = inputs + + if (ctx.searchAgent) { + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton({ + q: params.q, + cursor: params.cursor, + limit: params.limit, + }) + return { + posts: res.posts.map(({ uri }) => uri), + cursor: parseString(res.cursor), + } + } + const res = await ctx.dataplane.searchPosts({ term: params.q, limit: params.limit, @@ -85,6 +100,7 @@ type Context = { dataplane: DataPlaneClient hydrator: Hydrator views: Views + searchAgent?: AtpAgent } type Params = QueryParams & { viewer: string | null } diff --git a/packages/bsky/src/api/app/bsky/notification/registerPush.ts b/packages/bsky/src/api/app/bsky/notification/registerPush.ts index b0c17642a48..241e2502f99 100644 --- a/packages/bsky/src/api/app/bsky/notification/registerPush.ts +++ b/packages/bsky/src/api/app/bsky/notification/registerPush.ts @@ -1,17 +1,12 @@ -import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.registerPush({ auth: ctx.authVerifier, - handler: async ({ auth, input }) => { - const { serviceDid } = input.body - if (serviceDid !== auth.artifacts.aud) { - throw new InvalidRequestError('Invalid serviceDid.') - } - // @TODO fix pending appview v2 buildout - throw new InvalidRequestError('not currently supported') + handler: async () => { + // @TODO for appview v2 + throw new Error('unimplemented') }, }) } diff --git a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index 61836284eef..096faa12732 100644 --- a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -6,13 +6,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.unspecced.getPopularFeedGenerators({ auth: ctx.authOptionalVerifier, handler: async (_reqCtx) => { - // @TODO not currently supported during appview v2 buildout - return { - encoding: 'application/json', - body: { - feeds: [], - }, - } + // @TODO for appview v2 + throw new Error('unimplemented') }, }) } diff --git a/packages/bsky/src/config.ts b/packages/bsky/src/config.ts index 1e8665a4276..d758c553671 100644 --- a/packages/bsky/src/config.ts +++ b/packages/bsky/src/config.ts @@ -10,6 +10,7 @@ export interface ServerConfigValues { dataplaneUrls: string[] dataplaneHttpVersion?: '1.1' | '2' dataplaneIgnoreBadTls?: boolean + searchEndpoint?: string didPlcUrl: string handleResolveNameservers?: string[] imgUriEndpoint?: string @@ -44,6 +45,7 @@ export class ServerConfig { const dataplaneHttpVersion = process.env.BSKY_DATAPLANE_HTTP_VERSION || '2' const dataplaneIgnoreBadTls = process.env.BSKY_DATAPLANE_IGNORE_BAD_TLS === 'true' + const searchEndpoint = process.env.BSKY_SEARCH_ENDPOINT || undefined const adminPassword = process.env.BSKY_ADMIN_PASSWORD || 'admin' const moderatorPassword = process.env.BSKY_MODERATOR_PASSWORD || undefined const triagePassword = process.env.BSKY_TRIAGE_PASSWORD || undefined @@ -59,6 +61,7 @@ export class ServerConfig { dataplaneUrls, dataplaneHttpVersion, dataplaneIgnoreBadTls, + searchEndpoint, didPlcUrl, handleResolveNameservers, imgUriEndpoint, @@ -119,6 +122,10 @@ export class ServerConfig { return this.cfg.dataplaneIgnoreBadTls } + get searchEndpoint() { + return this.cfg.searchEndpoint + } + get handleResolveNameservers() { return this.cfg.handleResolveNameservers } diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index fa372725724..32163c69535 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -16,6 +16,7 @@ export class AppContext { private opts: { cfg: ServerConfig dataplane: DataPlaneClient + searchAgent: AtpAgent | undefined hydrator: Hydrator views: Views signingKey: Keypair @@ -33,6 +34,10 @@ export class AppContext { return this.opts.dataplane } + get searchAgent(): AtpAgent | undefined { + return this.opts.searchAgent + } + get hydrator(): Hydrator { return this.opts.hydrator } diff --git a/packages/bsky/src/data-plane/client.ts b/packages/bsky/src/data-plane/client.ts index d3ccf8d879e..5f631f9a545 100644 --- a/packages/bsky/src/data-plane/client.ts +++ b/packages/bsky/src/data-plane/client.ts @@ -40,6 +40,18 @@ export const createDataPlaneClient = ( }) as DataPlaneClient } +export { Code } + +export const isDataplaneError = ( + err: unknown, + code?: Code, +): err is ConnectError => { + if (err instanceof ConnectError) { + return !code || err.code === code + } + return false +} + const createBaseClient = ( baseUrl: string, opts: { httpVersion?: HttpVersion; rejectUnauthorized?: boolean }, diff --git a/packages/bsky/src/data-plane/server/routes/search.ts b/packages/bsky/src/data-plane/server/routes/search.ts index 8ab15b93b5a..19405eb875c 100644 --- a/packages/bsky/src/data-plane/server/routes/search.ts +++ b/packages/bsky/src/data-plane/server/routes/search.ts @@ -4,6 +4,7 @@ import { Database } from '../db' import { IndexedAtDidKeyset, TimeCidKeyset, paginate } from '../db/pagination' export default (db: Database): Partial> => ({ + // @TODO actor search endpoints still fall back to search service async searchActors(req) { const { term, limit, cursor } = req const { ref } = db.db.dynamic @@ -31,6 +32,7 @@ export default (db: Database): Partial> => ({ } }, + // @TODO post search endpoint still falls back to search service async searchPosts(req) { const { term, limit, cursor } = req const { ref } = db.db.dynamic diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index 0f2b29d02ba..b72f9c22de0 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -5,6 +5,7 @@ import events from 'events' import { createHttpTerminator, HttpTerminator } from 'http-terminator' import cors from 'cors' import compression from 'compression' +import AtpAgent from '@atproto/api' import { DidCache, IdResolver } from '@atproto/identity' import API, { health, wellKnown, blobResolver } from './api' import * as error from './error' @@ -71,6 +72,9 @@ export class BskyAppView { ) } + const searchAgent = config.searchEndpoint + ? new AtpAgent({ service: config.searchEndpoint }) + : undefined const dataplane = createDataPlaneClient(config.dataplaneUrls, { httpVersion: config.dataplaneHttpVersion, rejectUnauthorized: !config.dataplaneIgnoreBadTls, @@ -81,6 +85,7 @@ export class BskyAppView { const ctx = new AppContext({ cfg: config, dataplane, + searchAgent, hydrator, views, signingKey, diff --git a/packages/pds/tests/proxied/views.test.ts b/packages/pds/tests/proxied/views.test.ts index 39525d33c52..9cfb7063bdf 100644 --- a/packages/pds/tests/proxied/views.test.ts +++ b/packages/pds/tests/proxied/views.test.ts @@ -333,7 +333,8 @@ describe('proxies view requests', () => { expect([...pt1.data.feed, ...pt2.data.feed]).toEqual(res.data.feed) }) - it('unspecced.getPopularFeedGenerators', async () => { + // @TODO disabled during appview v2 buildout + it.skip('unspecced.getPopularFeedGenerators', async () => { const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( {}, {