Skip to content

Commit

Permalink
Proxy search queries (bluesky-social#1676)
Browse files Browse the repository at this point in the history
* proxy search

* tweak profile resp

* fix admin.searchRepos
  • Loading branch information
dholms authored Sep 26, 2023
1 parent 5d621ab commit 65eb3c8
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 79 deletions.
57 changes: 35 additions & 22 deletions packages/bsky/src/api/app/bsky/actor/searchActors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { sql } from 'kysely'
import AppContext from '../../../../context'
import { Server } from '../../../../lexicon'
import {
cleanTerm,
cleanQuery,
getUserSearchQuery,
SearchKeyset,
} from '../../../../services/util/search'
Expand All @@ -11,37 +11,50 @@ export default function (server: Server, ctx: AppContext) {
server.app.bsky.actor.searchActors({
auth: ctx.authOptionalVerifier,
handler: async ({ auth, params }) => {
let { cursor, limit, term: rawTerm, q: rawQ } = params
const { cursor, limit } = params
const requester = auth.credentials.did

// prefer new 'q' query param over deprecated 'term'
if (rawQ) {
rawTerm = rawQ
}

const term = cleanTerm(rawTerm || '')

const rawQuery = params.q ?? params.term
const query = cleanQuery(rawQuery || '')
const db = ctx.db.getReplica('search')

const results = term
? await getUserSearchQuery(db, { term, limit, cursor })
.select('distance')
.selectAll('actor')
.execute()
: []
const keyset = new SearchKeyset(sql``, sql``)
let results: string[]
let resCursor: string | undefined
if (ctx.searchAgent) {
const res =
await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({
q: query,
cursor,
limit,
})
results = res.data.actors.map((a) => a.did)
resCursor = res.data.cursor
} else {
const res = query
? await getUserSearchQuery(db, { query, limit, cursor })
.select('distance')
.selectAll('actor')
.execute()
: []
results = res.map((a) => a.did)
const keyset = new SearchKeyset(sql``, sql``)
resCursor = keyset.packFromResult(res)
}

const actors = await ctx.services
.actor(db)
.views.profilesList(results, requester)
const filtered = actors.filter(
(actor) => !actor.viewer?.blocking && !actor.viewer?.blockedBy,
)
.views.profiles(results, requester)

const SKIP = []
const filtered = results.flatMap((did) => {
const actor = actors[did]
if (actor.viewer?.blocking || actor.viewer?.blockedBy) return SKIP
return actor
})

return {
encoding: 'application/json',
body: {
cursor: keyset.packFromResult(results),
cursor: resCursor,
actors: filtered,
},
}
Expand Down
40 changes: 23 additions & 17 deletions packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
import AppContext from '../../../../context'
import { Server } from '../../../../lexicon'
import {
cleanTerm,
cleanQuery,
getUserSearchQuerySimple,
} from '../../../../services/util/search'

export default function (server: Server, ctx: AppContext) {
server.app.bsky.actor.searchActorsTypeahead({
auth: ctx.authOptionalVerifier,
handler: async ({ params, auth }) => {
let { limit, term: rawTerm, q: rawQ } = params
const { limit } = params
const requester = auth.credentials.did

// prefer new 'q' query param over deprecated 'term'
if (rawQ) {
rawTerm = rawQ
}

const term = cleanTerm(rawTerm || '')

const rawQuery = params.q ?? params.term
const query = cleanQuery(rawQuery || '')
const db = ctx.db.getReplica('search')

const results = term
? await getUserSearchQuerySimple(db, { term, limit })
.selectAll('actor')
.execute()
: []
let results: string[]
if (ctx.searchAgent) {
const res =
await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({
q: query,
typeahead: true,
limit,
})
results = res.data.actors.map((a) => a.did)
} else {
const res = query
? await getUserSearchQuerySimple(db, { query, limit })
.selectAll('actor')
.execute()
: []
results = res.map((a) => a.did)
}

const actors = await ctx.services
.actor(db)
.views.profilesBasic(results, requester, { omitLabels: true })

const SKIP = []
const filtered = results.flatMap((res) => {
const actor = actors[res.did]
const filtered = results.flatMap((did) => {
const actor = actors[did]
if (actor.viewer?.blocking || actor.viewer?.blockedBy) return SKIP
return actor
})
Expand Down
13 changes: 5 additions & 8 deletions packages/bsky/src/api/com/atproto/admin/searchRepos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,20 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ params }) => {
const db = ctx.db.getPrimary()
const moderationService = ctx.services.moderation(db)
const { invitedBy } = params
const { invitedBy, limit, cursor } = params
if (invitedBy) {
throw new InvalidRequestError('The invitedBy parameter is unsupported')
}
// prefer new 'q' query param over deprecated 'term'
const { q } = params
if (q) {
params.term = q
}
const query = params.q ?? params.term

const { results, cursor } = await ctx.services
const { results, cursor: resCursor } = await ctx.services
.actor(db)
.getSearchResults({ ...params, includeSoftDeleted: true })
.getSearchResults({ query, limit, cursor, includeSoftDeleted: true })
return {
encoding: 'application/json',
body: {
cursor,
cursor: resCursor,
repos: await moderationService.views.repo(results),
},
}
Expand Down
7 changes: 7 additions & 0 deletions packages/bsky/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ServerConfigValues {
handleResolveNameservers?: string[]
imgUriEndpoint?: string
blobCacheLocation?: string
searchEndpoint?: string
labelerDid: string
adminPassword: string
moderatorPassword?: string
Expand Down Expand Up @@ -51,6 +52,7 @@ export class ServerConfig {
: []
const imgUriEndpoint = process.env.IMG_URI_ENDPOINT
const blobCacheLocation = process.env.BLOB_CACHE_LOC
const searchEndpoint = process.env.SEARCH_ENDPOINT
const dbPrimaryPostgresUrl =
overrides?.dbPrimaryPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL
let dbReplicaPostgresUrls = overrides?.dbReplicaPostgresUrls
Expand Down Expand Up @@ -97,6 +99,7 @@ export class ServerConfig {
handleResolveNameservers,
imgUriEndpoint,
blobCacheLocation,
searchEndpoint,
labelerDid,
adminPassword,
moderatorPassword,
Expand Down Expand Up @@ -183,6 +186,10 @@ export class ServerConfig {
return this.cfg.blobCacheLocation
}

get searchEndpoint() {
return this.cfg.searchEndpoint
}

get labelerDid() {
return this.cfg.labelerDid
}
Expand Down
6 changes: 6 additions & 0 deletions packages/bsky/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BackgroundQueue } from './background'
import { MountedAlgos } from './feed-gen/types'
import { LabelCache } from './label-cache'
import { NotificationServer } from './notifications'
import { AtpAgent } from '@atproto/api'

export class AppContext {
constructor(
Expand All @@ -22,6 +23,7 @@ export class AppContext {
didCache: DidSqlCache
labelCache: LabelCache
backgroundQueue: BackgroundQueue
searchAgent?: AtpAgent
algos: MountedAlgos
notifServer: NotificationServer
},
Expand Down Expand Up @@ -63,6 +65,10 @@ export class AppContext {
return this.opts.notifServer
}

get searchAgent(): AtpAgent | undefined {
return this.opts.searchAgent
}

get authVerifier() {
return auth.authVerifier(this.idResolver, { aud: this.cfg.serverDid })
}
Expand Down
5 changes: 5 additions & 0 deletions packages/bsky/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { BackgroundQueue } from './background'
import { MountedAlgos } from './feed-gen/types'
import { LabelCache } from './label-cache'
import { NotificationServer } from './notifications'
import { AtpAgent } from '@atproto/api'

export type { ServerConfigValues } from './config'
export type { MountedAlgos } from './feed-gen/types'
Expand Down Expand Up @@ -100,6 +101,9 @@ export class BskyAppView {
const backgroundQueue = new BackgroundQueue(db.getPrimary())
const labelCache = new LabelCache(db.getPrimary())
const notifServer = new NotificationServer(db.getPrimary())
const searchAgent = config.searchEndpoint
? new AtpAgent({ service: config.searchEndpoint })
: undefined

const services = createServices({
imgUriBuilder,
Expand All @@ -116,6 +120,7 @@ export class BskyAppView {
didCache,
labelCache,
backgroundQueue,
searchAgent,
algos,
notifServer,
})
Expand Down
18 changes: 9 additions & 9 deletions packages/bsky/src/services/actor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ export class ActorService {
async getSearchResults({
cursor,
limit = 25,
term = '',
query = '',
includeSoftDeleted,
}: {
cursor?: string
limit?: number
term?: string
query?: string
includeSoftDeleted?: boolean
}) {
const searchField = term.startsWith('did:') ? 'did' : 'handle'
const searchField = query.startsWith('did:') ? 'did' : 'handle'
let paginatedBuilder
const { ref } = this.db.db.dynamic
const paginationOptions = {
Expand All @@ -101,10 +101,10 @@ export class ActorService {
}
let keyset

if (term && searchField === 'handle') {
if (query && searchField === 'handle') {
keyset = new SearchKeyset(sql``, sql``)
paginatedBuilder = getUserSearchQuery(this.db, {
term,
query,
includeSoftDeleted,
...paginationOptions,
}).select('distance')
Expand All @@ -114,10 +114,10 @@ export class ActorService {
.select([sql<number>`0`.as('distance')])
keyset = new ListKeyset(ref('indexedAt'), ref('did'))

// When searchField === 'did', the term will always be a valid string because
// searchField is set to 'did' after checking that the term is a valid did
if (term && searchField === 'did') {
paginatedBuilder = paginatedBuilder.where('actor.did', '=', term)
// When searchField === 'did', the query will always be a valid string because
// searchField is set to 'did' after checking that the query is a valid did
if (query && searchField === 'did') {
paginatedBuilder = paginatedBuilder.where('actor.did', '=', query)
}
paginatedBuilder = paginate(paginatedBuilder, {
keyset,
Expand Down
Loading

0 comments on commit 65eb3c8

Please sign in to comment.