Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy search queries #1676

Merged
merged 3 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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