Skip to content

Commit

Permalink
✨ Add getLists endpoint to proxied routes via ozone (bluesky-social#2493
Browse files Browse the repository at this point in the history
)

* ✨ Add getLists endpoint to proxied routes via ozone

* ✅ Add tests for getLists via ozone

* insert takedown label manually

* ✏️ Fix typo in test

* 🧹 Cleanup

---------

Co-authored-by: dholms <[email protected]>
  • Loading branch information
foysalit and dholms authored May 21, 2024
1 parent a317116 commit eeff393
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 5 deletions.
10 changes: 7 additions & 3 deletions packages/bsky/src/api/app/bsky/graph/getLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import { clearlyBadCursor, resHeaders } from '../../../util'
export default function (server: Server, ctx: AppContext) {
const getLists = createPipeline(skeleton, hydration, noRules, presentation)
server.app.bsky.graph.getLists({
auth: ctx.authVerifier.standardOptional,
auth: ctx.authVerifier.optionalStandardOrRole,
handler: async ({ params, auth, req }) => {
const viewer = auth.credentials.iss
const labelers = ctx.reqLabelers(req)
const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer })
const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth)
const hydrateCtx = await ctx.hydrator.createContext({
labelers,
viewer,
includeTakedowns,
})
const result = await getLists({ ...params, hydrateCtx }, ctx)

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/hydration/hydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class Hydrator {
ctx: HydrateCtx,
): Promise<HydrationState> {
const [lists, listViewers, labels] = await Promise.all([
this.graph.getLists(uris),
this.graph.getLists(uris, ctx.includeTakedowns),
ctx.viewer ? this.graph.getListViewerStates(uris, ctx.viewer) : undefined,
this.label.getLabelsForSubjects(uris, ctx.labelers),
])
Expand Down
40 changes: 39 additions & 1 deletion packages/dev-env/src/mock/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AtUri } from '@atproto/syntax'
import AtpAgent, { COM_ATPROTO_MODERATION } from '@atproto/api'
import { Database } from '@atproto/bsky'
import { EXAMPLE_LABELER, TestNetwork } from '../index'
import { EXAMPLE_LABELER, RecordRef, TestNetwork } from '../index'
import { postTexts, replyTexts } from './data'
import labeledImgB64 from './img/labeled-img-b64'
import blurHashB64 from './img/blur-hash-avatar-b64'
Expand Down Expand Up @@ -479,6 +479,44 @@ export async function generateMockSetup(env: TestNetwork) {
src: res.data.did,
})
}

// Create lists and add people to the lists
{
const flowerLovers = await alice.agent.api.app.bsky.graph.list.create(
{ repo: alice.did },
{
name: 'Flower Lovers',
purpose: 'app.bsky.graph.defs#curatelist',
createdAt: new Date().toISOString(),
description: 'A list of posts about flowers',
},
)
const labelHaters = await bob.agent.api.app.bsky.graph.list.create(
{ repo: bob.did },
{
name: 'Label Haters',
purpose: 'app.bsky.graph.defs#modlist',
createdAt: new Date().toISOString(),
description: 'A list of people who hate labels',
},
)
await alice.agent.api.app.bsky.graph.listitem.create(
{ repo: alice.did },
{
subject: bob.did,
createdAt: new Date().toISOString(),
list: new RecordRef(flowerLovers.uri, flowerLovers.cid).uriStr,
},
)
await bob.agent.api.app.bsky.graph.listitem.create(
{ repo: bob.did },
{
subject: alice.did,
createdAt: new Date().toISOString(),
list: new RecordRef(labelHaters.uri, labelHaters.cid).uriStr,
},
)
}
}

function ucfirst(str: string): string {
Expand Down
14 changes: 14 additions & 0 deletions packages/ozone/src/api/proxied.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,18 @@ export default function (server: Server, ctx: AppContext) {
}
},
})

server.app.bsky.graph.getLists({
auth: ctx.authVerifier.moderator,
handler: async (request) => {
const res = await ctx.appviewAgent.api.app.bsky.graph.getLists(
request.params,
await ctx.appviewAuth(),
)
return {
encoding: 'application/json',
body: res.data,
}
},
})
}
109 changes: 109 additions & 0 deletions packages/ozone/tests/get-lists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
SeedClient,
TestNetwork,
TestOzone,
basicSeed,
ModeratorClient,
RecordRef,
} from '@atproto/dev-env'
import AtpAgent, { BSKY_LABELER_DID } from '@atproto/api'
import { TAKEDOWN_LABEL } from '../src/mod-service'

describe('admin get lists', () => {
let network: TestNetwork
let ozone: TestOzone
let agent: AtpAgent
let appviewAgent: AtpAgent
let sc: SeedClient
let modClient: ModeratorClient
let alicesList: RecordRef

beforeAll(async () => {
network = await TestNetwork.create({
dbPostgresSchema: 'ozone_admin_get_lists',
})
ozone = network.ozone
agent = ozone.getClient()
appviewAgent = network.bsky.getClient()
sc = network.getSeedClient()
modClient = ozone.getModClient()
await basicSeed(sc)
alicesList = await sc.createList(sc.dids.alice, "Alice's List", 'mod')
AtpAgent.configure({ appLabelers: [ozone.ctx.cfg.service.did] })
await network.processAll()
})

afterAll(async () => {
AtpAgent.configure({ appLabelers: [BSKY_LABELER_DID] })
await network.close()
})

const getAlicesList = async () => {
const [{ data: fromOzone }, { data: fromAppview }] = await Promise.all([
agent.api.app.bsky.graph.getLists(
{ actor: sc.dids.alice },
{ headers: await ozone.modHeaders() },
),
appviewAgent.api.app.bsky.graph.getLists({ actor: sc.dids.alice }),
])

return { fromOzone, fromAppview }
}

it('returns lists from takendown account', async () => {
const beforeTakedown = await getAlicesList()
expect(beforeTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
expect(beforeTakedown.fromAppview.lists[0].uri).toEqual(alicesList.uriStr)

// Takedown alice's account
await modClient.emitEvent({
event: { $type: 'tools.ozone.moderation.defs#modEventTakedown' },
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.alice,
},
})
await network.processAll()

const afterTakedown = await getAlicesList()

// Verify that takendown list is shown when queried through ozone but not through appview
expect(afterTakedown.fromAppview.lists.length).toBe(0)
expect(afterTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)

// Reverse alice's account takedown
await modClient.emitEvent({
event: { $type: 'tools.ozone.moderation.defs#modEventReverseTakedown' },
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.alice,
},
})
await network.processAll()
})

it('returns takendown lists', async () => {
const beforeTakedown = await getAlicesList()
expect(beforeTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
expect(beforeTakedown.fromAppview.lists[0].uri).toEqual(alicesList.uriStr)

// Takedown alice's list using a !takedown label
await network.bsky.db.db
.insertInto('label')
.values({
src: ozone.ctx.cfg.service.did,
uri: alicesList.uriStr,
cid: alicesList.cidStr,
val: TAKEDOWN_LABEL,
neg: false,
cts: new Date().toISOString(),
})
.execute()

const afterTakedown = await getAlicesList()

// Verify that takendown list is shown when queried through ozone but not through appview
expect(afterTakedown.fromAppview.lists.length).toBe(0)
expect(afterTakedown.fromOzone.lists[0].uri).toEqual(alicesList.uriStr)
})
})

0 comments on commit eeff393

Please sign in to comment.