Skip to content

Commit

Permalink
fall back to popular follows, handle backfill differently
Browse files Browse the repository at this point in the history
  • Loading branch information
estrattonbailey committed Sep 11, 2023
1 parent 74b1acc commit 4e074ee
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 28 deletions.
93 changes: 65 additions & 28 deletions packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export default function (server: Server, ctx: AppContext) {
let suggestions: Awaited<
ReturnType<typeof actorService.views.hydrateProfiles>
> = []
let allActors: any[] = []

const viewerFollows = db.db
.selectFrom('follow')
.where('creator', '=', viewer)
.select('subjectDid')

if (likes.length >= 100) {
// get posts to get their authors
Expand Down Expand Up @@ -72,37 +78,68 @@ export default function (server: Server, ctx: AppContext) {
.map(([did]) => authors.find((a) => a.did === did))
.filter(Boolean) as typeof authors

const actors = sortedAuthors

if (suggestions.length < MAX_RESULTS_LENGTH) {
// backfill with suggested_follow table
const additional = await db.db
.selectFrom('actor')
.innerJoin('suggested_follow', 'actor.did', 'suggested_follow.did')
.where(
'actor.did',
'not in',
// exclude any we already have
authorDIDsExcludingActorAndViewer.concat([actorDid, viewer]),
)
.selectAll()
.execute()

actors.push(...additional)
}

// this handles blocks/mutes etc
suggestions = (
await actorService.views.hydrateProfiles(actors, viewer)
).filter((account) => {
return (
!account.viewer?.muted &&
!account.viewer?.blocking &&
!account.viewer?.blockedBy
allActors = sortedAuthors
} else {
const popularFollows = await db.db
.selectFrom('actor')
.selectAll()
.innerJoin(
db.db
.selectFrom('profile_agg')
.select(['did', 'followersCount'])
.innerJoin(
db.db
.selectFrom('follow')
.selectAll()
.where('creator', '=', actorDid)
.where('subjectDid', '!=', viewer)
.where('subjectDid', 'not in', viewerFollows)
.as('follows'),
'follows.subjectDid',
'profile_agg.did',
)
.orderBy('followersCount', 'desc')
.limit(20)
.as('popularFollows'),
'actor.did',
'popularFollows.did',
)
})
.orderBy('popularFollows.followersCount', 'desc')
.execute()

allActors = popularFollows
}

if (allActors.length < MAX_RESULTS_LENGTH) {
// backfill with suggested_follow table
const additional = await db.db
.selectFrom('actor')
.innerJoin('suggested_follow', 'actor.did', 'suggested_follow.did')
.where(
'actor.did',
'not in',
// exclude any we already have
allActors.map((a) => a.did).concat([actorDid, viewer]),
)
// and aren't already followed by viewer
.where('actor.did', 'not in', viewerFollows)
.selectAll()
.execute()

allActors.push(...additional)
}

// this handles blocks/mutes etc
suggestions = (
await actorService.views.hydrateProfiles(allActors, viewer)
).filter((account) => {
return (
!account.viewer?.muted &&
!account.viewer?.blocking &&
!account.viewer?.blockedBy
)
})

return {
encoding: 'application/json',
body: {
Expand Down
1 change: 1 addition & 0 deletions packages/bsky/tests/seeds/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default async (sc: SeedClient, users = true) => {
await sc.follow(bob, alice)
await sc.follow(bob, carol, createdAtMicroseconds())
await sc.follow(dan, bob, createdAtTimezone())
await sc.follow(bob, dan)
await sc.post(alice, posts.alice[0], undefined, undefined, undefined, {
labels: {
$type: 'com.atproto.label.defs#selfLabels',
Expand Down
16 changes: 16 additions & 0 deletions packages/bsky/tests/views/suggested-follows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,20 @@ describe('suggested follows', () => {
sc.getHeaders(sc.dids.bob),
)
})

it('returns sorted suggested follows based on foafs', async () => {
const result = await agent.api.app.bsky.graph.getSuggestedFollowsByActor(
{
actor: sc.dids.bob,
},
{ headers: await network.serviceHeaders(sc.dids.carol) },
)

expect(result.data.suggestions.length).toBe(3) // backfilled with 2 NPCs
expect(
result.data.suggestions.find((sug) => {
return [sc.dids.alice, sc.dids.carol, sc.dids.bob].includes(sug.did)
}),
).toBeFalsy() // not actor or viewer or followed
})
})

0 comments on commit 4e074ee

Please sign in to comment.