diff --git a/packages/bsky/src/api/app/bsky/graph/getRelationships.ts b/packages/bsky/src/api/app/bsky/graph/getRelationships.ts new file mode 100644 index 00000000000..5ac4f3107c4 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/graph/getRelationships.ts @@ -0,0 +1,71 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { Relationship } from '../../../../lexicon/types/app/bsky/graph/defs' + +export default function (server: Server, ctx: AppContext) { + server.app.bsky.graph.getRelationships({ + handler: async ({ params }) => { + const { actor, others = [] } = params + if (others.length < 1) { + return { + encoding: 'application/json', + body: { + actor, + relationships: [], + }, + } + } + const db = ctx.db.getPrimary() + const { ref } = db.db.dynamic + const res = await db.db + .selectFrom('actor') + .select([ + 'actor.did', + db.db + .selectFrom('follow') + .where('creator', '=', actor) + .whereRef('subjectDid', '=', ref('actor.did')) + .select('uri') + .as('following'), + db.db + .selectFrom('follow') + .whereRef('creator', '=', ref('actor.did')) + .where('subjectDid', '=', actor) + .select('uri') + .as('followedBy'), + ]) + .where('actor.did', 'in', others) + .execute() + + const relationshipsMap = res.reduce((acc, cur) => { + return acc.set(cur.did, { + did: cur.did, + following: cur.following ?? undefined, + followedBy: cur.followedBy ?? undefined, + }) + }, new Map()) + + const relationships = others.map((did) => { + const relationship = relationshipsMap.get(did) + return relationship + ? { + $type: 'app.bsky.graph.defs#relationship', + ...relationship, + } + : { + $type: 'app.bsky.graph.defs#notFoundActor', + actor: did, + notFound: true, + } + }) + + return { + encoding: 'application/json', + body: { + actor, + relationships, + }, + } + }, + }) +} diff --git a/packages/bsky/src/api/index.ts b/packages/bsky/src/api/index.ts index 7628770507f..550f2d8596c 100644 --- a/packages/bsky/src/api/index.ts +++ b/packages/bsky/src/api/index.ts @@ -26,6 +26,7 @@ import getList from './app/bsky/graph/getList' import getLists from './app/bsky/graph/getLists' import getListMutes from './app/bsky/graph/getListMutes' import getMutes from './app/bsky/graph/getMutes' +import getRelationships from './app/bsky/graph/getRelationships' import muteActor from './app/bsky/graph/muteActor' import unmuteActor from './app/bsky/graph/unmuteActor' import muteActorList from './app/bsky/graph/muteActorList' @@ -82,6 +83,7 @@ export default function (server: Server, ctx: AppContext) { getLists(server, ctx) getListMutes(server, ctx) getMutes(server, ctx) + getRelationships(server, ctx) muteActor(server, ctx) unmuteActor(server, ctx) muteActorList(server, ctx) diff --git a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap index c85b0549de7..dfb5ff2ecb3 100644 --- a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap @@ -569,3 +569,31 @@ Object { }, } `; + +exports[`pds follow views fetches relationships between users 1`] = ` +Object { + "actor": "user(0)", + "relationships": Array [ + Object { + "$type": "app.bsky.graph.defs#relationship", + "did": "user(1)", + "followedBy": "record(1)", + "following": "record(0)", + }, + Object { + "$type": "app.bsky.graph.defs#relationship", + "did": "user(0)", + }, + Object { + "$type": "app.bsky.graph.defs#relationship", + "did": "user(2)", + "following": "record(2)", + }, + Object { + "$type": "app.bsky.graph.defs#notFoundActor", + "actor": "did:example:fake", + "notFound": true, + }, + ], +} +`; diff --git a/packages/bsky/tests/views/follows.test.ts b/packages/bsky/tests/views/follows.test.ts index 8367f2d1f61..d0f640c6813 100644 --- a/packages/bsky/tests/views/follows.test.ts +++ b/packages/bsky/tests/views/follows.test.ts @@ -294,4 +294,14 @@ describe('pds follow views', () => { }, ) }) + + it('fetches relationships between users', async () => { + const res = await agent.api.app.bsky.graph.getRelationships({ + actor: sc.dids.bob, + others: [sc.dids.alice, sc.dids.bob, sc.dids.carol, 'did:example:fake'], + }) + expect(res.data.actor).toEqual(sc.dids.bob) + expect(res.data.relationships.length).toBe(4) + expect(forSnapshot(res.data)).toMatchSnapshot() + }) })