diff --git a/packages/bsky/src/data-plane/server/routes/search.ts b/packages/bsky/src/data-plane/server/routes/search.ts index 7638d9399ca..101856553cd 100644 --- a/packages/bsky/src/data-plane/server/routes/search.ts +++ b/packages/bsky/src/data-plane/server/routes/search.ts @@ -1,12 +1,60 @@ import { ServiceImpl } from '@connectrpc/connect' import { Service } from '../../gen/bsky_connect' import { Database } from '../../../db' +import { + IndexedAtDidKeyset, + TimeCidKeyset, + paginate, +} from '../../../db/pagination' export default (db: Database): Partial> => ({ async searchActors(req) { - throw new Error('unimplemented') + const { term, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('actor') + .where('actor.handle', 'like', `%${term}%`) + .selectAll() + + const keyset = new IndexedAtDidKeyset( + ref('actor.indexedAt'), + ref('actor.did'), + ) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const res = await builder.execute() + + return { + dids: res.map((row) => row.did), + cursor: keyset.packFromResult(res), + } }, + async searchPosts(req) { - throw new Error('unimplemented') + const { term, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('post') + .where('post.text', 'like', `%${term}%`) + .selectAll() + + const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const res = await builder.execute() + return { + uris: res.map((row) => row.uri), + cursor: keyset.packFromResult(res), + } }, }) diff --git a/packages/bsky/src/db/pagination.ts b/packages/bsky/src/db/pagination.ts index d5887ae1fff..920f6658989 100644 --- a/packages/bsky/src/db/pagination.ts +++ b/packages/bsky/src/db/pagination.ts @@ -115,6 +115,15 @@ export class CreatedAtDidKeyset extends TimeCidKeyset<{ } } +export class IndexedAtDidKeyset extends TimeCidKeyset<{ + indexedAt: string + did: string // dids are treated identically to cids in TimeCidKeyset +}> { + labelResult(result: { indexedAt: string; did: string }) { + return { primary: result.indexedAt, secondary: result.did } + } +} + export const paginate = < QB extends AnyQb, K extends GenericKeyset, diff --git a/packages/bsky/tests/views/block-lists.test.ts b/packages/bsky/tests/views/block-lists.test.ts index 8d33f17a8cd..6672d690ce1 100644 --- a/packages/bsky/tests/views/block-lists.test.ts +++ b/packages/bsky/tests/views/block-lists.test.ts @@ -273,42 +273,41 @@ describe('pds views with blocking from block lists', () => { ).toBeFalsy() }) - // @TODO uncomment after implemeting search in dataplane mock - // it('does not return blocked accounts in actor search', async () => { - // const resCarol = await agent.api.app.bsky.actor.searchActors( - // { - // term: 'dan.test', - // }, - // { headers: await network.serviceHeaders(carol) }, - // ) - // expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() - - // const resDan = await agent.api.app.bsky.actor.searchActors( - // { - // term: 'carol.test', - // }, - // { headers: await network.serviceHeaders(dan) }, - // ) - // expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() - // }) - - // it('does not return blocked accounts in actor search typeahead', async () => { - // const resCarol = await agent.api.app.bsky.actor.searchActorsTypeahead( - // { - // term: 'dan.test', - // }, - // { headers: await network.serviceHeaders(carol) }, - // ) - // expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() - - // const resDan = await agent.api.app.bsky.actor.searchActorsTypeahead( - // { - // term: 'carol.test', - // }, - // { headers: await network.serviceHeaders(dan) }, - // ) - // expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() - // }) + it('does not return blocked accounts in actor search', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActors( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActors( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) + + it('does not return blocked accounts in actor search typeahead', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) it('does not return blocked accounts in get suggestions', async () => { // unfollow so they _would_ show up in suggestions if not for block diff --git a/packages/bsky/tests/views/blocks.test.ts b/packages/bsky/tests/views/blocks.test.ts index 9be229463c2..19986d02739 100644 --- a/packages/bsky/tests/views/blocks.test.ts +++ b/packages/bsky/tests/views/blocks.test.ts @@ -315,42 +315,41 @@ describe('pds views with blocking', () => { ).toBeFalsy() }) - // @TODO uncomment after adding search to dataplane mock - // it('does not return blocked accounts in actor search', async () => { - // const resCarol = await agent.api.app.bsky.actor.searchActors( - // { - // term: 'dan.test', - // }, - // { headers: await network.serviceHeaders(carol) }, - // ) - // expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() - - // const resDan = await agent.api.app.bsky.actor.searchActors( - // { - // term: 'carol.test', - // }, - // { headers: await network.serviceHeaders(dan) }, - // ) - // expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() - // }) - - // it('does not return blocked accounts in actor search typeahead', async () => { - // const resCarol = await agent.api.app.bsky.actor.searchActorsTypeahead( - // { - // term: 'dan.test', - // }, - // { headers: await network.serviceHeaders(carol) }, - // ) - // expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() - - // const resDan = await agent.api.app.bsky.actor.searchActorsTypeahead( - // { - // term: 'carol.test', - // }, - // { headers: await network.serviceHeaders(dan) }, - // ) - // expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() - // }) + it('does not return blocked accounts in actor search', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActors( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActors( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) + + it('does not return blocked accounts in actor search typeahead', async () => { + const resCarol = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'dan.test', + }, + { headers: await network.serviceHeaders(carol) }, + ) + expect(resCarol.data.actors.some((actor) => actor.did === dan)).toBeFalsy() + + const resDan = await agent.api.app.bsky.actor.searchActorsTypeahead( + { + term: 'carol.test', + }, + { headers: await network.serviceHeaders(dan) }, + ) + expect(resDan.data.actors.some((actor) => actor.did === carol)).toBeFalsy() + }) it('does not return blocked accounts in get suggestions', async () => { // unfollow so they _would_ show up in suggestions if not for block