Skip to content

Commit

Permalink
Add searchPosts api to appview (#1845)
Browse files Browse the repository at this point in the history
* proxy search posts to search service

* add search posts proxy

* tidy

* add type annotations
  • Loading branch information
dholms authored Nov 15, 2023
1 parent 74b7fdf commit a434d58
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
123 changes: 123 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/searchPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import AppContext from '../../../../context'
import { Server } from '../../../../lexicon'
import { InvalidRequestError } from '@atproto/xrpc-server'
import AtpAgent from '@atproto/api'
import { AtUri } from '@atproto/syntax'
import { mapDefined } from '@atproto/common'
import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts'
import { Database } from '../../../../db'
import { FeedHydrationState, FeedService } from '../../../../services/feed'
import { ActorService } from '../../../../services/actor'
import { createPipeline } from '../../../../pipeline'

export default function (server: Server, ctx: AppContext) {
const searchPosts = createPipeline(
skeleton,
hydration,
noBlocks,
presentation,
)
server.app.bsky.feed.searchPosts({
auth: ctx.authOptionalVerifier,
handler: async ({ auth, params }) => {
const viewer = auth.credentials.did
const db = ctx.db.getReplica('search')
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)
const searchAgent = ctx.searchAgent
if (!searchAgent) {
throw new InvalidRequestError('Search not available')
}

const results = await searchPosts(
{ ...params, viewer },
{ db, feedService, actorService, searchAgent },
)

return {
encoding: 'application/json',
body: results,
}
},
})
}

const skeleton = async (
params: Params,
ctx: Context,
): Promise<SkeletonState> => {
const res = await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton(
params,
)
return {
params,
postUris: res.data.posts.map((a) => a.uri),
cursor: res.data.cursor,
}
}

const hydration = async (
state: SkeletonState,
ctx: Context,
): Promise<HydrationState> => {
const { feedService } = ctx
const { params, postUris } = state
const uris = new Set<string>(postUris)
const dids = new Set<string>(postUris.map((uri) => new AtUri(uri).hostname))
const hydrated = await feedService.feedHydration({
uris,
dids,
viewer: params.viewer,
})
return { ...state, ...hydrated }
}

const noBlocks = (state: HydrationState): HydrationState => {
const { viewer } = state.params
state.postUris = state.postUris.filter((uri) => {
const post = state.posts[uri]
if (!viewer || !post) return true
return !state.bam.block([viewer, post.creator])
})
return state
}

const presentation = (state: HydrationState, ctx: Context) => {
const { feedService, actorService } = ctx
const { postUris, profiles, params } = state
const actors = actorService.views.profileBasicPresentation(
Object.keys(profiles),
state,
{ viewer: params.viewer },
)

const postViews = mapDefined(postUris, (uri) =>
feedService.views.formatPostView(
uri,
actors,
state.posts,
state.threadgates,
state.embeds,
state.labels,
state.lists,
),
)
return { posts: postViews }
}

type Context = {
db: Database
feedService: FeedService
actorService: ActorService
searchAgent: AtpAgent
}

type Params = QueryParams & { viewer: string | null }

type SkeletonState = {
params: Params
postUris: string[]
cursor?: string
}

type HydrationState = SkeletonState & FeedHydrationState
2 changes: 2 additions & 0 deletions packages/bsky/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import getLikes from './app/bsky/feed/getLikes'
import getListFeed from './app/bsky/feed/getListFeed'
import getPostThread from './app/bsky/feed/getPostThread'
import getPosts from './app/bsky/feed/getPosts'
import searchPosts from './app/bsky/feed/searchPosts'
import getActorLikes from './app/bsky/feed/getActorLikes'
import getProfile from './app/bsky/actor/getProfile'
import getProfiles from './app/bsky/actor/getProfiles'
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function (server: Server, ctx: AppContext) {
getListFeed(server, ctx)
getPostThread(server, ctx)
getPosts(server, ctx)
searchPosts(server, ctx)
getActorLikes(server, ctx)
getProfile(server, ctx)
getProfiles(server, ctx)
Expand Down
2 changes: 2 additions & 0 deletions packages/pds/src/api/app/bsky/feed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import getPostThread from './getPostThread'
import getRepostedBy from './getRepostedBy'
import getSuggestedFeeds from './getSuggestedFeeds'
import getTimeline from './getTimeline'
import searchPosts from './searchPosts'

export default function (server: Server, ctx: AppContext) {
getActorFeeds(server, ctx)
Expand All @@ -28,4 +29,5 @@ export default function (server: Server, ctx: AppContext) {
getRepostedBy(server, ctx)
getSuggestedFeeds(server, ctx)
getTimeline(server, ctx)
searchPosts(server, ctx)
}
19 changes: 19 additions & 0 deletions packages/pds/src/api/app/bsky/feed/searchPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Server } from '../../../../lexicon'
import AppContext from '../../../../context'

export default function (server: Server, ctx: AppContext) {
server.app.bsky.feed.searchPosts({
auth: ctx.authVerifier.access,
handler: async ({ params, auth }) => {
const requester = auth.credentials.did
const res = await ctx.appViewAgent.api.app.bsky.feed.searchPosts(
params,
await ctx.serviceAuthHeaders(requester),
)
return {
encoding: 'application/json',
body: res.data,
}
},
})
}

0 comments on commit a434d58

Please sign in to comment.