Skip to content

Commit

Permalink
Include takendown posts on author feed for admins (#2172)
Browse files Browse the repository at this point in the history
* ✨ Allow admins to view author feed on takendown account

* ✅ Add tests for admin behavior
  • Loading branch information
foysalit authored Feb 13, 2024
1 parent b400fae commit 0acd6c1
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 18 deletions.
16 changes: 12 additions & 4 deletions packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export default function (server: Server, ctx: AppContext) {

const [result, repoRev] = await Promise.all([
getAuthorFeed(
{ ...params, viewer },
{
...params,
includeSoftDeleted: auth.credentials.type === 'role',
viewer,
},
{ db, actorService, feedService, graphService },
),
actorService.getRepoRev(viewer),
Expand All @@ -53,12 +57,12 @@ export const skeleton = async (
params: Params,
ctx: Context,
): Promise<SkeletonState> => {
const { cursor, limit, actor, filter, viewer } = params
const { cursor, limit, actor, filter, viewer, includeSoftDeleted } = params
const { db, actorService, feedService, graphService } = ctx
const { ref } = db.db.dynamic

// maybe resolve did first
const actorRes = await actorService.getActor(actor)
const actorRes = await actorService.getActor(actor, includeSoftDeleted)
if (!actorRes) {
throw new InvalidRequestError('Profile not found')
}
Expand Down Expand Up @@ -138,6 +142,7 @@ const hydration = async (state: SkeletonState, ctx: Context) => {
const hydrated = await feedService.feedHydration({
...refs,
viewer: params.viewer,
includeSoftDeleted: params.includeSoftDeleted,
})
return { ...state, ...hydrated }
}
Expand Down Expand Up @@ -168,7 +173,10 @@ type Context = {
graphService: GraphService
}

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

type SkeletonState = {
params: Params
Expand Down
14 changes: 10 additions & 4 deletions packages/bsky/src/services/feed/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class FeedService {
async getPostInfos(
postUris: string[],
viewer: string | null,
includeSoftDeleted?: boolean,
): Promise<PostInfoMap> {
if (postUris.length < 1) return {}
const db = this.db.db
Expand All @@ -149,8 +150,12 @@ export class FeedService {
.innerJoin('actor', 'actor.did', 'post.creator')
.innerJoin('record', 'record.uri', 'post.uri')
.leftJoin('post_agg', 'post_agg.uri', 'post.uri')
.where(notSoftDeletedClause(ref('actor'))) // Ensures post reply parent/roots get omitted from views when taken down
.where(notSoftDeletedClause(ref('record')))
.if(!includeSoftDeleted, (qb) =>
qb.where(notSoftDeletedClause(ref('actor'))),
) // Ensures post reply parent/roots get omitted from views when taken down
.if(!includeSoftDeleted, (qb) =>
qb.where(notSoftDeletedClause(ref('record'))),
)
.select([
'post.uri as uri',
'post.cid as cid',
Expand Down Expand Up @@ -249,12 +254,13 @@ export class FeedService {
dids: Set<string>
uris: Set<string>
viewer: string | null
includeSoftDeleted?: boolean
},
depth = 0,
): Promise<FeedHydrationState> {
const { viewer, dids, uris } = refs
const [posts, threadgates, labels, bam] = await Promise.all([
this.getPostInfos(Array.from(uris), viewer),
this.getPostInfos(Array.from(uris), viewer, refs.includeSoftDeleted),
this.threadgatesByPostUri(Array.from(uris)),
this.services.label.getLabelsForSubjects([...uris, ...dids]),
this.services.graph.getBlockAndMuteState(
Expand All @@ -266,7 +272,7 @@ export class FeedService {
const [profileState, blocks, lists] = await Promise.all([
this.services.actor.views.profileHydration(
Array.from(dids),
{ viewer },
{ viewer, includeSoftDeleted: refs.includeSoftDeleted },
{ bam, labels },
),
this.blocksForPosts(posts, bam),
Expand Down
4 changes: 3 additions & 1 deletion packages/bsky/src/services/feed/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ export class FeedViews {
const post = posts[uri]
const gate = threadgates[uri]
const author = actors[post?.creator]
if (!post || !author) return undefined
if (!post || !author) {
return undefined
}
const postLabels = labels[uri] ?? []
const postSelfLabels = getSelfLabels({
uri: post.uri,
Expand Down
38 changes: 29 additions & 9 deletions packages/bsky/tests/views/author-feed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('pds author feed views', () => {
)
})

it('blocked by actor takedown.', async () => {
it('non-admins blocked by actor takedown.', async () => {
const { data: preBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await network.serviceHeaders(carol) },
Expand All @@ -163,11 +163,18 @@ describe('pds author feed views', () => {
},
)

const attempt = agent.api.app.bsky.feed.getAuthorFeed(
const attemptAsUser = agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await network.serviceHeaders(carol) },
)
await expect(attempt).rejects.toThrow('Profile not found')
await expect(attemptAsUser).rejects.toThrow('Profile not found')

const attemptAsAdmin = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: network.bsky.adminAuthHeaders() },
)
expect(attemptAsAdmin.data.feed.length).toBeGreaterThan(0)
expect(attemptAsAdmin.data.feed.length).toEqual(preBlock.feed.length)

// Cleanup
await agent.api.com.atproto.admin.updateSubjectStatus(
Expand Down Expand Up @@ -215,13 +222,26 @@ describe('pds author feed views', () => {
},
)

const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await network.serviceHeaders(carol) },
const [{ data: postBlockAsUser }, { data: postBlockAsAdmin }] =
await Promise.all([
agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: await network.serviceHeaders(carol) },
),
agent.api.app.bsky.feed.getAuthorFeed(
{ actor: alice },
{ headers: network.bsky.adminAuthHeaders() },
),
])

expect(postBlockAsUser.feed.length).toEqual(preBlock.feed.length - 1)
expect(postBlockAsUser.feed.map((item) => item.post.uri)).not.toContain(
post.uri,
)
expect(postBlockAsAdmin.feed.length).toEqual(preBlock.feed.length)
expect(postBlockAsAdmin.feed.map((item) => item.post.uri)).toContain(
post.uri,
)

expect(postBlock.feed.length).toEqual(preBlock.feed.length - 1)
expect(postBlock.feed.map((item) => item.post.uri)).not.toContain(post.uri)

// Cleanup
await agent.api.com.atproto.admin.updateSubjectStatus(
Expand Down

0 comments on commit 0acd6c1

Please sign in to comment.