Skip to content

Commit

Permalink
feed gen routes
Browse files Browse the repository at this point in the history
  • Loading branch information
dholms committed Dec 12, 2023
1 parent bb3d377 commit 396e210
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 155 deletions.
116 changes: 70 additions & 46 deletions packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,87 @@
import { InvalidRequestError } from '@atproto/xrpc-server'
import { mapDefined } from '@atproto/common'
import { Server } from '../../../../lexicon'
import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getActorFeeds'
import AppContext from '../../../../context'
import { TimeCidKeyset, paginate } from '../../../../db/pagination'
import { createPipelineNew, noRulesNew } from '../../../../pipeline'
import { HydrationState, Hydrator } from '../../../../hydration/hydrator'
import { Views } from '../../../../views'
import { DataPlaneClient } from '../../../../data-plane'
import { parseString } from '../../../../hydration/util'

export default function (server: Server, ctx: AppContext) {
const getActorFeeds = createPipelineNew(
skeleton,
hydration,
noRulesNew,
presentation,
)
server.app.bsky.feed.getActorFeeds({
auth: ctx.authOptionalVerifier,
handler: async ({ auth, params }) => {
const { actor, limit, cursor } = params
const viewer = auth.credentials.did

const db = ctx.db.getReplica()
const actorService = ctx.services.actor(db)
const feedService = ctx.services.feed(db)

const creatorRes = await actorService.getActor(actor)
if (!creatorRes) {
throw new InvalidRequestError(`Actor not found: ${actor}`)
const result = await getActorFeeds({ ...params, viewer }, ctx)
return {
encoding: 'application/json',
body: result,
}
},
})
}

const { ref } = db.db.dynamic
let feedsQb = feedService
.selectFeedGeneratorQb(viewer)
.where('feed_generator.creator', '=', creatorRes.did)
const skeleton = async (inputs: {
ctx: Context
params: Params
}): Promise<Skeleton> => {
const { ctx, params } = inputs
const [did] = await ctx.hydrator.actor.getDids([params.actor])
if (!did) {
throw new InvalidRequestError('Profile not found')
}
const feedsRes = await ctx.dataplane.getActorFeeds({
actorDid: did,
cursor: params.cursor,
limit: params.limit,
})
return {
feedUris: feedsRes.uris,
cursor: parseString(feedsRes.cursor),
}
}

const keyset = new TimeCidKeyset(
ref('feed_generator.createdAt'),
ref('feed_generator.cid'),
)
feedsQb = paginate(feedsQb, {
limit,
cursor,
keyset,
})
const hydration = async (inputs: {
ctx: Context
params: Params
skeleton: Skeleton
}) => {
const { ctx, params, skeleton } = inputs
return await ctx.hydrator.hydrateFeedGens(skeleton.feedUris, params.viewer)
}

const [feedsRes, profiles] = await Promise.all([
feedsQb.execute(),
actorService.views.profiles([creatorRes], viewer),
])
if (!profiles[creatorRes.did]) {
throw new InvalidRequestError(`Actor not found: ${actor}`)
}
const presentation = (inputs: {
ctx: Context
skeleton: Skeleton
hydration: HydrationState
}) => {
const { ctx, skeleton, hydration } = inputs
const feeds = mapDefined(skeleton.feedUris, (uri) =>
ctx.views.feedGenerator(uri, hydration),
)
return {
feeds,
cursor: skeleton.cursor,
}
}

const feeds = mapDefined(feedsRes, (row) => {
const feed = {
...row,
viewer: viewer ? { like: row.viewerLike } : undefined,
}
return feedService.views.formatFeedGeneratorView(feed, profiles)
})
type Context = {
hydrator: Hydrator
views: Views
dataplane: DataPlaneClient
}

return {
encoding: 'application/json',
body: {
cursor: keyset.packFromResult(feedsRes),
feeds,
},
}
},
})
type Params = QueryParams & { viewer: string | null }

type Skeleton = {
feedUris: string[]
cursor?: string
}
19 changes: 4 additions & 15 deletions packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,13 @@ export default function (server: Server, ctx: AppContext) {
const { feed } = params
const viewer = auth.credentials.did

const db = ctx.db.getReplica()
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)

const got = await feedService.getFeedGeneratorInfos([feed], viewer)
const feedInfo = got[feed]
const hydration = await ctx.hydrator.hydrateFeedGens([feed], viewer)
const feedInfo = hydration.feedgens?.get(feed)
if (!feedInfo) {
throw new InvalidRequestError('could not find feed')
}

const feedDid = feedInfo.feedDid
const feedDid = feedInfo.record.did
let resolved: DidDocument | null
try {
resolved = await ctx.idResolver.did.resolve(feedDid)
Expand All @@ -47,14 +43,7 @@ export default function (server: Server, ctx: AppContext) {
)
}

const profiles = await actorService.views.profilesBasic(
[feedInfo.creator],
viewer,
)
const feedView = feedService.views.formatFeedGeneratorView(
feedInfo,
profiles,
)
const feedView = ctx.views.feedGenerator(feed, hydration)
if (!feedView) {
throw new InvalidRequestError('could not find feed')
}
Expand Down
119 changes: 75 additions & 44 deletions packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
import { mapDefined } from '@atproto/common'
import { Server } from '../../../../lexicon'
import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getFeedGenerators'
import AppContext from '../../../../context'
import { FeedGenInfo, FeedService } from '../../../../services/feed'
import { createPipeline, noRules } from '../../../../pipeline'
import { ActorInfoMap, ActorService } from '../../../../services/actor'
import { Database } from '../../../../db'
import { createPipelineNew, noRulesNew } from '../../../../pipeline'
import { HydrationState, Hydrator } from '../../../../hydration/hydrator'
import { Views } from '../../../../views'

export default function (server: Server, ctx: AppContext) {
const getFeedGenerators = createPipeline(
const getFeedGenerators = createPipelineNew(
skeleton,
hydration,
noRules,
noRulesNew,
presentation,
)
server.app.bsky.feed.getFeedGenerators({
auth: ctx.authOptionalVerifier,
handler: async ({ params, auth }) => {
const { feeds } = params
const viewer = auth.credentials.did
const db = ctx.db.getReplica()
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)

const view = await getFeedGenerators(
{ feeds, viewer },
{ db, feedService, actorService },
)

const view = await getFeedGenerators({ ...params, viewer }, ctx)
return {
encoding: 'application/json',
body: view,
Expand All @@ -35,46 +26,86 @@ export default function (server: Server, ctx: AppContext) {
})
}

const skeleton = async (params: Params, ctx: Context) => {
const { feedService } = ctx
const genInfos = await feedService.getFeedGeneratorInfos(
params.feeds,
params.viewer,
)
const skeleton = async (inputs: { params: Params }): Promise<Skeleton> => {
return {
params,
generators: Object.values(genInfos),
feedUris: inputs.params.feeds,
}
}

const hydration = async (state: SkeletonState, ctx: Context) => {
const { actorService } = ctx
const profiles = await actorService.views.profilesBasic(
state.generators.map((gen) => gen.creator),
state.params.viewer,
const hydration = async (inputs: {
ctx: Context
params: Params
skeleton: Skeleton
}) => {
const { ctx, params, skeleton } = inputs
return await ctx.hydrator.hydrateFeedGens(skeleton.feedUris, params.viewer)
}

const presentation = (inputs: {
ctx: Context
skeleton: Skeleton
hydration: HydrationState
}) => {
const { ctx, skeleton, hydration } = inputs
const feeds = mapDefined(skeleton.feedUris, (uri) =>
ctx.views.feedGenerator(uri, hydration),
)
return {
...state,
profiles,
feeds,
}
}

const presentation = (state: HydrationState, ctx: Context) => {
const { feedService } = ctx
const feeds = mapDefined(state.generators, (gen) =>
feedService.views.formatFeedGeneratorView(gen, state.profiles),
)
return { feeds }
type Context = {
hydrator: Hydrator
views: Views
}

type Context = {
db: Database
feedService: FeedService
actorService: ActorService
type Params = QueryParams & { viewer: string | null }

type Skeleton = {
feedUris: string[]
}

type Params = { viewer: string | null; feeds: string[] }
// const skeleton = async (params: Params, ctx: Context) => {
// const { feedService } = ctx
// const genInfos = await feedService.getFeedGeneratorInfos(
// params.feeds,
// params.viewer,
// )
// return {
// params,
// generators: Object.values(genInfos),
// }
// }

// const hydration = async (state: SkeletonState, ctx: Context) => {
// const { actorService } = ctx
// const profiles = await actorService.views.profilesBasic(
// state.generators.map((gen) => gen.creator),
// state.params.viewer,
// )
// return {
// ...state,
// profiles,
// }
// }

// const presentation = (state: HydrationState, ctx: Context) => {
// const { feedService } = ctx
// const feeds = mapDefined(state.generators, (gen) =>
// feedService.views.formatFeedGeneratorView(gen, state.profiles),
// )
// return { feeds }
// }

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

// type Params = { viewer: string | null; feeds: string[] }

type SkeletonState = { params: Params; generators: FeedGenInfo[] }
// type SkeletonState = { params: Params; generators: FeedGenInfo[] }

type HydrationState = SkeletonState & { profiles: ActorInfoMap }
// type HydrationState = SkeletonState & { profiles: ActorInfoMap }
31 changes: 12 additions & 19 deletions packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
import { mapDefined } from '@atproto/common'
import { Server } from '../../../../lexicon'
import AppContext from '../../../../context'
import { parseString } from '../../../../hydration/util'

export default function (server: Server, ctx: AppContext) {
server.app.bsky.feed.getSuggestedFeeds({
auth: ctx.authOptionalVerifier,
handler: async ({ auth }) => {
handler: async ({ params, auth }) => {
const viewer = auth.credentials.did

const db = ctx.db.getReplica()
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)
const feedsRes = await db.db
.selectFrom('suggested_feed')
.orderBy('suggested_feed.order', 'asc')
.selectAll()
.execute()
const genInfos = await feedService.getFeedGeneratorInfos(
feedsRes.map((r) => r.uri),
viewer,
)
const genList = feedsRes.map((r) => genInfos[r.uri]).filter(Boolean)
const creators = genList.map((gen) => gen.creator)
const profiles = await actorService.views.profilesBasic(creators, viewer)

const feedViews = mapDefined(genList, (gen) =>
feedService.views.formatFeedGeneratorView(gen, profiles),
const suggestedRes = await ctx.dataplane.getSuggestedFeeds({
actorDid: viewer ?? undefined,
limit: params.limit,
cursor: params.cursor,
})
const uris = suggestedRes.uris
const hydration = await ctx.hydrator.hydrateFeedGens(uris, viewer)
const feedViews = mapDefined(uris, (uri) =>
ctx.views.feedGenerator(uri, hydration),
)

return {
encoding: 'application/json',
body: {
feeds: feedViews,
cursor: parseString(suggestedRes.cursor),
},
}
},
Expand Down
Loading

0 comments on commit 396e210

Please sign in to comment.